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


130

私は、Pro GitでThe Perils of Rebasingに関するシナリオを理解しています。著者は基本的に、コミットの重複を回避する方法を説明します。

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

私の特定の状況について説明します。ProGitシナリオに正確には適合しないと思いますが、それでもコミットが重複することになります。

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するC5'と、2つの同一のコミットとC6' Gitリポジトリに異なっています。これを変更する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'

多分私はプロGitの説明を完全に理解していないので、私は2つのことを知りたいです:

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

回答:


86

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

  • C5C6一時的に引き出されますdev
  • C7 に適用されます dev
  • C5C6上で再生されC7、新しい差分が作成されるため、新しいコミットが作成されます

そのため、devブランチにはC5C6事実上存在しなくなります。それらは現在C5'C6'です。あなたはにプッシュするとorigin/dev、Gitは見ているC5'C6'、新しいコミットと歴史の終わりまでに鋲それらをなど。確かに、あなたは間の違いを見ればC5C5'origin/dev、あなたが内容は同じですが、行番号はおそらく異なっていることがわかります-異なるコミットのハッシュを行いいます。

私はPro Gitルールを言い換えますローカルリポジトリ以外の場所に存在したコミットをリベースしないでください。代わりにマージを使用してください。


私は同じ問題を抱えていますが、リモートブランチの履歴をどのように修正できますか?ブランチを削除してチェリーピッキングで再作成する以外に他のオプションはありますか?
Wazery、2012年

1
@xdsy:これこれを見てください。
ジャスティン

2
あなたは「C5とC6は一時的に開発から引き抜かれます... C7が開発に適用されます」と言います。この場合、origin / devでのコミットの順序でC5とC6がC7の前に表示されるのはなぜですか?
KJ50 2014年

@ KJ50:C5とC6はすでににプッシュされているためorigin/dev。場合devリベースされ、その履歴が変更される(C5 / C6は、一時的に除去し、C7後に再適用します)。何をしているのかを理解していない限り、プッシュされたリポジトリの履歴を変更することは、一般にReally Bad Idea™です。この単純なケースでは、リベース後からdevにプッシュすることで、問題が解決する可能性がorigin/devありorigin/dev、作業中の誰かにおそらく悪い日が来ることを通知します。より良い答えは、「そうしないでください...代わりにマージを使用してください」
Justinᚅᚔᚈᚄᚒᚔ

3
注意すべき点:C5とC5 'のハッシュは確かに異なりますが、行番号が異なるためではありませんが、次の2つの事実については、いずれか1つで違いが十分です。1)話しているハッシュコミット後のソースツリー全体のハッシュであり、差分差分のハッシュではありません。したがって、C5 'はC7からではなく、C7からのものをすべて含みます。2)C5'の親はC5とは異なり、この情報ハッシュ結果に影響を与えるコミットツリーのルートノードにも含まれます。
Ozgur Murat

113

短い答え

実行したという事実を省略しgit push、次のエラーを受け取ってから、実行を続行しましたgit pull

To git@bitbucket.org:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org: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」アドバイスは、おそらくあなたがやりたいことではありません

あなたがいる場合:

  • 「機能ブランチ」または「開発者ブランチ」だけで作業している場合は、実行git push --forceして、リベース後のコミットでリモートを更新できます(user4405677の回答に従って)。
  • 同時に複数の開発者がいるブランチで作業する場合、おそらくgit rebase最初から使用するべきではありませんdevからの変更で更新するにはmaster、を実行するのではなくgit rebase master dev、実行git merge master中に実行する必要がありますdevジャスティンの回答に従って)。

少し長い説明

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

コミットを並べ替えると、コミットハッシュが変更されます。リベース(何かを行う場合)は、コミットハッシュを変更します。これにより、との同期がとれていないgit rebase master dev場所でのの実行結果は、オンと同じ内容であるが、その前にコミットオンが挿入された新しいコミット(およびハッシュ)を作成します。devmasterdevmaster

複数の方法でこのような状況になる可能性があります。私が考えることができる2つの方法:

  • master自分のdev作業のベースにしたいことにコミットすることができます
  • あなたdevはすでにリモートにプッシュされているコミットを持っている可能性があり、それから次に変更に進みます(コミットメッセージの書き換え、コミットの並べ替え、コミットのスカッシュなど)

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

リポジトリがあります:

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

リポジトリ内の線形コミットの初期セット

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

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

(ここで私の言葉を言う必要があります。Gitでコミットを変更する方法はいくつかあります。この例ではの時間を変更しましたがC3、新しいコミットの挿入、コミットメッセージの変更、コミットの並べ替え、コミットをまとめてスカッシュするなど)

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

新しいハッシュで同じコミット

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

マスターがリモートと同期していないことを示すグラフログ

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

$ git push origin master
To git@bitbucket.org:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org: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

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

マージコミットを示すグラフログ

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

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

(REFLOGを見てgit reflogハッシュだったコミットかを確認する)前に、私たちが走りました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

上記ba7688aは、実行前のコミットでしたgit 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ログ

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

重複したコミットの上に線形コミットを示すロググラフ

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

どちらか:

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

1それはあなたが選択した2のどの、いずれかの問題ではありませんba7688aか、2a2e220仕事の罰金を。

2この例では、次のようになります85f59ab

TL; DR

セットadvice.pushNonFastForwardfalse

git config --global advice.pushNonFastForward false

1
省略記号が "--rebase"オプション(別名 "-r")を隠していることに気づく限り、 "git pull ..."のアドバイスに従うことは問題ありません。;-)
G. Sylvie Davies

4
私が使用することをお勧めしますgit push--force-with-leaseそれはより良いデフォルトだとして、今日
Whymarrh

4
それはこの答えかタイムマシンです。ありがとう!
ZeMoon 2018年

非常にきちんとした説明...繰り返しリベースを試みた後、コードを5-6回複製した同様の問題に遭遇しました...マスターでコードが最新であることを確認するためだけ...私のブランチへの新しいコミット、私のコードも複製します。私がブランチで作業している唯一の開発者である場合、ここで強制プッシュ(リースオプション付き)が安全かどうか教えていただけますか?または、リベースする代わりにマスターを鉱山にマージする方が良い方法ですか?
Dhruv Singhal

12

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

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

または、代わりにgit pull --rebase(または--rebaseそれが構成に含まれている場合は明示せずに)作成し、元のC5とC6をローカル開発に戻しました(さらに、次のハッシュを新しいハッシュにリベースしました、C7 'C5' 'C6' ')。

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


1
の省略git pullは重要です。の推奨はgit push -f危険ですが、おそらく読者が探しているものです。
Whymarrh 2015年

確かに。私が実際に行った質問を書いたときはgit push --force、Gitが何をするのかを確認するためだけでした。それ以来、Gitについて多くのことを学びましたが、今日rebaseは私の通常のワークフローの一部です。ただし、私はgit push --force-with-lease他人の作品を上書きしないようにします。
elitalon

を使用--force-with-leaseすることをお
勧め

2

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

問題の説明:

Sympthoms:リベース後の子ブランチでコミットが複製され、リベース中およびリベース後に多数のマージが行われることを意味します。

ワークフロー: 私が実行していたワークフローの手順は次のとおりです。

  • 「機能ブランチ」(「開発ブランチ」の子)に取り組みます
  • 「機能ブランチ」の変更のコミットとプッシュ
  • 「開発ブランチ」(機能の別のブランチ)をチェックアウトして、それを操作します。
  • 「開発ブランチ」の変更をコミットしてプッシュする
  • 「機能ブランチ」をチェックアウトし、リポジトリから変更をプルします(他の誰かが作業をコミットした場合)
  • 「機能ブランチ」を「開発ブランチ」にリベース
  • 「機能ブランチ」の変更のプッシュフォース

このワークフローの結果として、前回のリベース以降の "Feature-branch"のすべてのコミットの複製... :-(

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

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

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

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


1

現在とは異なるリモートブランチからプルした可能性があります。たとえば、ブランチが開発を追跡しているときにマスターからプルした可能性があります。追跡されていないブランチからプルされた場合、Gitは重複したコミットを忠実にプルします。

これが発生した場合は、次の操作を実行できます。

git reset --hard HEAD~n

どこ n == <number of duplicate commits that shouldn't be there.>

次に、正しいブランチからプルしていることを確認してから、実行します。

git pull upstream <correct remote branch> --rebase

でプルする--rebaseと、コミット履歴を混乱させる可能性のある無関係なコミットを追加することがなくなります。

これはgit rebaseの手持ちのビットです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.