プッシュする前に複数のコミットを1つに結合する


130

この質問は、このタスクを達成する方法だけでなく、Gitでそれを行うことが良いか悪いかに関するものです。

ローカルではほとんどの作業をmasterブランチで行いますが、「topical_xFeature」と呼ぶトピックブランチを作成したことを考慮してください。「topical_xFeature」で作業し、マスターブランチで他の作業を行うために切り替えを行っている過程で、「topical_xFeature」ブランチで複数のコミットを行ったことがわかりましたが、各コミットの間に、押す。

最初に、この悪い習慣を考えますか?プッシュごとにブランチごとに1つのコミットを続けるのは賢明ではないでしょうか?プッシュが行われる前に、ブランチで複数のコミットを行うとどのような場合に適していますか?

次に、プッシュのためにtopical_xFeatureブランチの複数のコミットをマスターブランチに取り込むにはどうすればよいですか?それを心配せずに複数のコミットがプッシュされるプッシュを実行するのは面倒ですか、それともコミットを1つにマージしてからプッシュするほうが煩わしくないですか?繰り返しますが、これを行う方法は?

回答:


139

最初の質問については、いいえ、一度に複数のコミットをプッシュすることに問題はありません。多くの場合、作業をいくつかの小さな論理的なコミットに分解したいかもしれませんが、シリーズ全体の準備が整ったと感じたときにのみ、それらをプッシュしてください。または、切断されている間にローカルで複数のコミットを行っている可能性があり、再び接続されたらそれらをすべてプッシュします。プッシュごとに1つのコミットに制限する理由はありません。

私は通常、各コミットを、動作するために必要なすべてを含む単一の論理的で首尾一貫した変更にしておくことをお勧めします(したがって、コードが壊れた状態のままになることはありません)。2つのコミットがあり、最初のコミットのみを適用した場合にコードが壊れる原因となる場合、2番目のコミットを最初のコミットに押しつぶすことをお勧めします。しかし、それぞれが合理的な変更を行う2つのコミットがある場合、それらを別々のコミットとしてプッシュすることは問題ありません。

複数のコミットをまとめて潰したい場合は、を使用できますgit rebase -i。ブランチtopical_xFeatureにいる場合は、実行しgit rebase -i masterます。エディタウィンドウが開き、一連のコミットの前にが付いていpickます。最初以外のすべてをに変更できますsquash。これにより、Gitにすべての変更を保持するように指示しますが、最初のコミットにそれらを押しつぶします。それが終わったら、チェックしmasterて機能ブランチにマージします。

git checkout topical_xFeature
git rebase -i master
git checkout master
git merge topical_xFeature

あなただけではスカッシュすべてにしたい場合は代わりに、topical_xFeaturemaster、あなただけの次のことを行うことができます:

git checkout master
git merge --squash topical_xFeature
git commit

どちらを選ぶかはあなた次第です。一般的に、私は複数の小さなコミットを心配していませんが、余分なマイナーコミットに煩わされたくないので、それらを1つにまとめます。


1
--squashとマージした後、でトピックブランチを削除できませんgit branch -d topic。すべての変更がマージされたことをgitが識別できないのはなぜですか?
balki

7
@balki Gitは、パッチがマージされるかどうかを、指定されたブランチの履歴に表示されるかどうかに基づいて検出するためです。コミットをコミットすると、それらが変更されます。それらは新しいコミットになり、その新しいコミットはたまたま他のコミットと同じことを行いますが、Gitはそれを知ることができません。同じコミットID(SHA-1)がある場合にのみ、コミットが同じであるかどうかを知ることができます。だからそれを押しつぶしたら、それgit branch -D topicを強制的に削除するために古いブランチを削除するようにgitに指示する必要があります。
ブライアンキャンベル

66

これは、コードをプッシュする前に、通常、複数のコミットを1つのコミットに結合する方法です。

これを実現するには、GITが提供する「スカッシュ」の概念を使用することをお勧めします。

以下の手順に従ってください。

1)gitのリベース-iマスターの代わりに、(マスターあなたもコミット特定を使用することができます)

リベースのインタラクティブエディターを開き、すべてのコミットを表示します。基本的に、単一のコミットにマージしたいコミットを特定する必要がある場合。

これらがあなたのコミットで、エディタでこのようなものが表示されていると想像してください。

pick f7f3f6d changed my name a bit    
pick 310154e updated README formatting and added blame   
pick a5f4a0d added cat-file  

これらのコミットは、logコマンドを使用して通常表示されるのとは逆の順序でリストされることに注意することが重要です。つまり、古いコミットが最初に表示されます。

2)最後にコミットされた変更について、「選択」を「スカッシュ」に変更します。以下に示すようなもの。そうすることで、最後の2つのコミットが最初のコミットとマージされます。

pick f7f3f6d changed my name a bit         
squash 310154e updated README formatting and added blame   
squash a5f4a0d added cat-file

組み合わせるコミットが多い場合は、短い形式を使用することもできます。

p f7f3f6d changed my name a bit         
s 310154e updated README formatting and added blame   
s a5f4a0d added cat-file

編集用に「i」を使用すると、エディターが挿入できるようになります。結合する前のコミットがないため、最上位(最も古い)コミットを押しつぶすことはできません。したがって、選択するか「p」にする必要があります。挿入モードを終了するには、「Esc」を使用します。

3)次に、以下のコマンドでエディター保存します。 :wq

これを保存すると、以前の3つのコミットすべての変更を導入する単一のコミットがあります。

これがお役に立てば幸いです。


5
おそらくこれは他の人には明らかですが、「git rebase -i」と言うときは、どのコミットから開始するかも指定する必要があります。これは、この例を試しても気づかなかったものです。したがって、この例では、「git rebase -i xxxxx」となります。xxxxxは、年代順にf7f3f6dの直前のコミットです。私がそれを理解すると、すべてが上記のとおり正確に機能しました。
nukeguy

それは興味深い@nukeguyで、特定のコミットを指定しないという問題はありませんでした。それは単にそこにあったものにデフォルトを設定しました。
JCrooks 2017年

多分@nukeguyのようにgit rebase -i HEAD~2、私が始めるのに役立つ場所でした。次に、この回答は役に立ちました。次に、git status「あなたのブランチと 'origin / feature / xyz'は分岐しており、それぞれ1つと1つの異なるコミットがあります。」したがって、stackoverflow.com / a / 59309553/470749およびfreecodecamp.org/forum/t/…git push origin feature/xyz --force-with-lease参照する必要がありました
Ryan

11

まず、プッシュごとにブランチごとにコミットが1つだけであることを示すものはありません。プッシュは、リモートリポジトリでローカル履歴(つまり、コミットのコレクション)を公開できる公開メカニズムです。

2つ目git merge --no-ff topical_xFeatureプッシュする前に、トピックの作業を1つのコミットとしてマスターに記録しますmaster
(そうすれば、あなたは続けるtopical_xFeatureあなたが録音できることを、さらに進化するために周りのmasterニューシングルは、次のマージ--no-FFにコミットとして。
退治する場合にはtopical_xFeature目標があり、その後git merge --squashで詳述するように、右のオプションであるブライアン・キャンベル答え。)


私はそれを考えて--squashいない、--no-ffあなたが望むものです。--no-ffマージコミットを作成しますが、からのコミットもすべて残しますtopical_xFeature
ブライアンキャンベル

@ブライアン:私はあなたの答えに同意して賛成しましたが、topical_featureブランチを維持し、ブランチで単一のコミットを記録したかったので、最初に--no-ffオプションを考えましたmaster
VonC、2011

8

masterブランチに切り替えて、最新の状態であることを確認します。

git checkout master

git fetch これは、オリジン/マスターの更新を受信するために必要です(git構成によって異なります)。

git pull

機能ブランチをマスターブランチにマージします。

git merge feature_branch

マスターブランチをオリジンの状態にリセットします。

git reset origin/master

Gitはすべての変更をステージングされていない変更と見なします。これらの変更を1つのコミットとして追加できます。を追加しています。追跡されていないファイルも追加されます。

git add --all

git commit

参照:https : //makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


3
この答えはわかりやすく、視覚化も非常に簡単です。
jokab 2017

6
  1. 最初に、すべての後に来るコミットを選択します。

    git reflog
    5976f2b HEAD@{0}: commit: Fix conflicts
    80e85a1 HEAD@{1}: commit: Add feature
    b860ddb HEAD@{2}: commit: Add something
    
  2. 選択した頭にリセットします(私は選択しましたHEAD@{2}

    git reset b860ddb --soft
    
  3. git status (念のために)

  4. 新しいコミットを追加する

    git commit -m "Add new commit"
    

注:HEAD@{0}HEAD@{1}は1つのコミットにマージされるようになりました。これは、複数のコミットに対しても実行できます。

git reflog 再び表示する必要があります:

git reflog
5976f2b HEAD@{0}: commit: Add new commit
b860ddb HEAD@{1}: commit: Add something

0

複数のコミットを1つに自動化するツール

Kondal Kolipakaが言うように。「git rebase -i」を使用する

「git rebase」のロジック

「git rebase -i」を使用すると、gitは現在の.git / rebase-mergeディレクトリにgit-rebase-todoファイルを生成し、次にgitエディターを呼び出して、ユーザーがgit-rebase-todoファイルを編集して処理できるようにします。したがって、ツールは次の条件を満たす必要があります。

  1. gitエディターを、提供したツールに変更します。
  2. ツールはgit-rebase-todoファイルを処理します。

デフォルトのgitエディターを変更する

git config core.editor #show current default git editor
git config --local --replace-all  core.editor NEW_EDITOR # set the local branch using NEW_EDITOR as git editor

そのため、ツールはgitエディターを変更し、git-rebase-todoファイルを処理する必要があります。以下のpythonを使用するツール:

#!/usr/bin/env python3
#encoding: UTF-8

import os
import sys

def change_editor(current_file):
    os.system("git config --local --replace-all  core.editor " + current_file) # Set current_file as git editor
    os.system("git rebase -i") # execute the "git rebase -i" and will invoke the python file later with git-rebase-todo file as argument
    os.system("git config --local --replace-all core.editor vim") # after work reset the git editor to default

def rebase_commits(todo_file):
    with open(todo_file, "r+") as f:
        contents = f.read() # read git-rebase-todo's content
        contents = contents.split("\n")
        first_commit = True
        f.truncate()
        f.seek(0)
        for content in contents:
            if content.startswith("pick"):
                if first_commit:
                    first_commit = False
                else:
                    content = content.replace("pick", "squash") # replace the pick to squash except for the first pick
            f.write(content + "\n")

def main(args):
    if len(args) == 2:
        rebase_commits(args[1]) # process the git-rebase-todo
    else:
        change_editor(os.path.abspath(args[0])) # set git editor

if __name__ == "__main__":
    main(sys.argv)

参照:https : //liwugang.github.io/2019/12/30/git_commits_en.html


4
あなたのウェブサイトの宣伝をトーンダウンしてください。スパマーにならない方法
tripleee
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.