web-dev-qa-db-ja.com

リベースを実行した後、Gitコミットが同じブランチで複製されます

git rebaseのリスク について、Pro Gitで提示されているシナリオを理解しています。作者は基本的に重複したコミットを避ける方法を教えてくれます:

パブリックリポジトリにプッシュしたコミットをリベースしないでください。

Pro Gitシナリオに正確に適合せず、コミットが重複することになるため、特定の状況を説明します。

ローカルに対応する2つのリモートブランチがあるとします。

Origin/master    Origin/dev
|                |
master           dev

4つのブランチすべてに同じコミットが含まれており、devで開発を開始します。

Origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

Origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

数回のコミットの後、変更をOrigin/devにプッシュします。

Origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

Origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git Push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

masterに戻って簡単に修正する必要があります。

Origin/master : C1 C2 C3 C4 C7  # (2) git Push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

Origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6

devに戻り、変更をリベースして、実際の開発にクイックフィックスを含めます。

Origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

Origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

GitX/gitkでコミットの履歴を表示すると、Origin/devにGitとは異なる2つの同一のコミットC5'C6'が含まれていることに気付きます。これで、変更をOrigin/devにプッシュすると、これが結果になります。

Origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

Origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git Push
dev           : C1 C2 C3 C4 C7 C5' C6'

たぶん、Pro Gitの説明を完全に理解していないので、2つのことを知りたいと思います。

  1. リベース中にGitがこれらのコミットを複製するのはなぜですか? C5の後にC6C7を単に適用する代わりに、それを行う特別な理由はありますか?
  2. どうすればそれを回避できますか?それを行うのは賢明でしょうか?
103
elitalon

ここではリベースを使用しないでください。単純なマージで十分です。リンクしたPro Gitブックは、基本的にこの正確な状況を説明しています。内部の仕組みは少し異なるかもしれませんが、ここでそれを視覚化する方法を示します。

  • C5およびC6は一時的にdevから引き出されます
  • C7devに適用されます
  • C5C6C7の上で再生され、新しいdiffを作成するため、新しいコミットが作成されます

したがって、devブランチには、C5C6が事実上存在しなくなりました。これらは現在C5'C6'です。 Origin/devにプッシュすると、gitはC5'およびC6'を新しいコミットとして認識し、履歴の最後に追加します。実際、C5C5'Origin/devの違いを見ると、内容は同じですが、行番号が異なる可能性があり、コミットのハッシュが異なることがわかります。

Pro Gitルールを再度説明します。ローカルリポジトリ以外に存在するコミットをリベースしないでください。代わりにマージを使用してください。

簡潔な答え

git Pushを実行したという事実を省略し、次のエラーを受け取った後、git pullを実行しました。

To [email protected]:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to Push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git Push --help' for details.

Gitは役立つように努めていますが、「git pull」アドバイスは、おそらくあなたがしたいことではありません

あなたがいる場合:

  • 「機能ブランチ」または「開発者ブランチ」aloneで作業している場合、git Push --forceを実行して、ポストリベースでリモートを更新できます。コミット( ser4405677の回答による )。
  • 同時に複数の開発者がいるブランチで作業している場合、最初の場所でおそらくgit rebaseを使用しないでください。 devからの変更を使用してmasterを更新するには、git rebase master devを実行する代わりに、devを実行しながらgit merge masterを実行する必要があります(- Justin's answer )。

少し長い説明

Gitの各コミットハッシュはいくつかの要因に基づいており、その1つはその前にあるコミットのハッシュです。

コミットの順序を変更すると、コミットハッシュが変更されます。リベース(何かを行うとき)はコミットハッシュを変更します。それにより、git rebase master devを実行した結果、devmasterと同期しなくなり、同じコンテンツでnewコミット(つまりハッシュ)が作成されます。 devにあるものと同じですが、masterにコミットが挿入されています。

このような状況に陥るには、さまざまな方法があります。私が考えることができる2つの方法:

  • masterにコミットして、devに基づいて作業することができます。
  • すでにリモートにプッシュされているdevのコミットを使用して、変更を続行できます(コミットメッセージの書き換え、コミットの並べ替え、コミットのスカッシュなど)。

何が起こったのかをよりよく理解しましょう。例を示します。

リポジトリがあります:

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

Initial set of linear commits in a repository

次に、コミットの変更に進みます。

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(これはあなたが私のWordを受け入れなければならない場所です。Gitでコミットを変更する方法はいくつかあります。この例では、C3の時間を変更しましたが、新しいコミットを挿入し、コミットを変更しますメッセージ、コミットの順序変更、コミットのスカッシュなど)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

The same commits with new hashes

ここで、コミットハッシュが異なることに注意することが重要です。あなたはそれらについて何か(何でも)を変更したので、これは予想される動作です。これは大丈夫ですが、しかし:

A graph log showing that master is out-of-sync with the remote

プッシュしようとすると、エラーが表示されます(git pullを実行する必要があるというヒントも表示されます)。

$ git Push Origin master
To [email protected]:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to Push some refs to '[email protected]:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git Push --help' for details.

git pullを実行すると、次のログが表示されます。

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (Origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

または、別の方法を示します:

A graph log showing a merge commit

そして今、ローカルに重複したコミットがあります。 git Pushを実行する場合、サーバーに送信します。

この段階に到達しないようにするには、git Push --force(代わりにgit pullを実行しました)を実行することもできます。これにより、新しいハッシュを使用したコミットが問題なくサーバーに送信されます。この段階で問題を修正するには、git pullを実行する前にリセットします。

Reflog(git reflog)を見て、コミットハッシュが何であったかを確認しますbefore実行しましたgit pull

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/Origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

上記では、ba7688agit pullを実行する前にコミットしていたことがわかります。そのコミットハッシュを手に入れたら、その(git reset --hard ba7688a)にリセットしてからgit Push --forceを実行できます。

これで完了です。

しかし、待って、私は重複したコミットの作業を続けました

コミットが重複していることに気づかず、重複したコミットの上で作業を続行した場合は、本当に混乱していることになります。混乱のサイズは、重複の上にあるコミットの数に比例します。

これはどのように見えるか:

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (Origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Git log showing linear commits atop duplicated commits

または、別の方法を示します:

A log graph showing linear commits atop duplicated commits

このシナリオでは、重複したコミットを削除しますが、それらに基づいたコミットは保持します。C6〜C10を保持します。ほとんどの場合と同様に、これについてはいくつかの方法があります。

どちらか:

  • 最後に複製されたコミットで新しいブランチを作成します1cherry-pick その新しいブランチへの各コミット(C6からC10を含む)、およびその新しいブランチを正規として扱います。
  • git rebase --interactive $commitを実行します。ここで、$commitはコミットですprior両方の重複したコミットに対して2。ここで、重複する行を完全に削除できます。

1 ba7688a2a2e220のどちらを選択しても問題ありません。

2 この例では、85f59abになります。

TL; DR

advice.pushNonFastForwardfalseに設定:

git config --global advice.pushNonFastForward false
85
Whymarrh

手順を説明するときに重要な詳細を省略したと思います。より具体的には、通常、非早送りの変更をプッシュできないため、最後のステップであるgit Push on devは、実際にエラーを表示します。

したがって、最後のプッシュの前にgit pullを実行すると、C6とC6 'が親としてマージコミットされ、両方がログにリストされたままになります。よりきれいなログ形式は、それらが重複したコミットのマージされたブランチであることをより明らかにしたかもしれません。

または、代わりにgit pull --rebaseを作成しました(または、構成で暗黙的に示されている場合は明示的な--rebaseなし)。これにより、元のC5およびC6がローカルdevに戻されました(さらに、新しいハッシュ、C7 'C5' 'C6' ')。

これの1つの方法は、エラーが発生したときにプッシュを強制し、OriginからC5 C6をワイプするgit Push -fでしたが、ワイプする前に他の誰かがそれらを引っ張った場合は、全体が入りますさらに多くのトラブル...基本的にC5 C6を持つすべての人はそれらを取り除くために特別な手順を行う必要があります。既に公開されているものはリベースしないでくださいと彼らが言うのはまさにそのためです。ただし、「パブリッシング」が小さなチーム内で行われていれば、それでも実行可能です。

12
user4405677

私の場合、これはGit構成の問題の結果であることがわかりました。 (プルとマージを含む)

問題の説明:

Sympthoms:リベース後の子ブランチで重複したコミット。リベース中およびリベース後の多数のマージを意味します。

Workflow:私が実行していたワークフローのステップは次のとおりです。

  • 「機能ブランチ」の作業(「開発ブランチ」の子)
  • 「機能ブランチ」での変更のコミットとプッシュ
  • 「開発ブランチ」(機能の母ブランチ)をチェックアウトし、それを操作します。
  • 「開発ブランチ」の変更をコミットおよびプッシュする
  • 「機能ブランチ」をチェックアウトし、リポジトリから変更をプルします(他の誰かが作業をコミットした場合)
  • 「機能ブランチ」を「開発ブランチ」にリベースします
  • 「機能分岐」の変更のプッシュ力

このワークフローの結果として、以前のリベース以降の「機能ブランチ」のすべてのコミットの複製... :

---(この問題は、リベース前の子ブランチの変更のプルによるものでした。 Gitのデフォルトのプル設定は「マージ」です。これは、子ブランチで実行されたコミットのインデックスを変更しています。

解決策:Git構成ファイルで、リベースモードで動作するようにプルを構成します。

...
[pull]
    rebase = preserve
...

JN Grxに役立つことを願って

1
JN Gerbaux