web-dev-qa-db-ja.com

Gitワークフローとリベース対マージの質問

私はもう一人の開発者と一緒のプロジェクトで数ヶ月間Gitを使っています。私は _ svn _ で数年の経験があるので、関係に多くの手荷物を持っていくと思います。

Gitは分岐やマージに優れていると聞いたことがありますが、これまでのところ、私は見ていません。確かに、分岐は単純なことですが、マージしようとすると、すべてが問題になります。今、私はSVNから慣れ親しんでいますが、あるサブパーバージョン管理システムを別のバージョン管理システムに交換しただけのようです。

私のパートナーは、私の問題は何もしないでマージするという私の願望から生じていること、そして多くの状況でマージの代わりにリベースを使用するべきであると私に話しています。たとえば、これが彼が定めたワークフローです。

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

基本的には、機能ブランチを作成し、必ずマスターからブランチにリベースし、ブランチからマスターにマージします。注意が必要なのは、ブランチは常にローカルのままであるということです。

これが私が始めたワークフローです。

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature Origin/my_new_feature
..work, commit, Push to Origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, Push to Origin/my_new_feature
git merge master
..finish my_new_feature, Push to Origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

2つの重要な違いがあります(私は思います):私はリベースの代わりにマージを使用します。そして私の機能ブランチ(と機能ブランチのコミット)をリモートリポジトリにプッシュします。

私がリモートブランチをしている理由は、仕事中に自分の仕事をバックアップしてほしいということです。私たちのリポジトリは自動的にバックアップされ、問題が発生した場合は復元することができます。私のラップトップはそうではない、またはそれほど徹底的ではない。それゆえ、私は自分のラップトップに他のどこにも反映されていないコードがあるのを嫌います。

リベースではなくマージを私が推論したのは、マージは標準的で、リベースは高度な機能のようです。私の直感は、私がやろうとしているのは高度な設定ではないので、リベースは不要なはずです。私はGitに関する新しいPragmatic Programmingの本を熟読したことさえありますが、それらは広範囲にわたるマージをカバーしており、リベースについてはほとんど触れていません。

とにかく、私は最近のブランチで私のワークフローをたどっていました、そしてそれをマスターにマージしようと試みたとき、それはすべて地獄に行きました。問題になってはならないこととの衝突がたくさんありました。衝突は私には意味がありませんでした。私のローカルマスターはすべての衝突を解決したので、リモートマスターへの強制プッシュで最終的に最高潮に達しました、しかしリモートのものはまだ幸せではありませんでした。

このようなものに対する「正しい」ワークフローは何ですか? Gitは分岐とマージを非常に簡単にすることになっています、そして私はそれを見ていません。

更新2011-04-15

これは非常に人気のある質問のように思われるので、私は私が最初に尋ねてから私の2年間の経験で更新したいと思った。

少なくとも私たちの場合、元のワークフローは正しいことがわかりました。言い換えれば、これは私たちがしていることであり、うまくいきます:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

実際のところ、生のマージの代わりにsquash mergeを実行する傾向があるため、ワークフローは少し異なります。 ( 注:これは物議を醸しています。以下を参照してください。 )これにより、機能ブランチ全体をmaster上の単一のコミットに変えることができます。それから、機能ブランチを削除します。これにより、ブランチ上で少し面倒な場合でも、マスター上でコミットを論理的に構造化することができます。だから、これは私たちがすることです:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

スカッシュマージ論争 - スカッシュマージはあなたの機能ブランチのすべての歴史を捨てるでしょう。その名前が示すように、コミットをすべて1つにまとめます。小さな機能の場合、これは単一のパッケージにまとめるので意味があります。より大きな機能のためには、おそらくあなたの個々のコミットがすでにアトミックであるなら、それはおそらく素晴らしい考えではありません。それは本当に個人的な好みに帰着します。

GithubとBitbucket(その他?)プルリクエスト - マージ/リベースがプルリクエストとどのように関連しているか疑問に思う場合は、マスターにマージする準備ができるまで上記のステップをすべて実行することをお勧めします。手動でgitとマージするのではなく、単にPRを受け入れます。これはスカッシュマージを実行しません(少なくともデフォルトではありません)が、(私の知る限りでは)非スカッシュ、非早送りがプルリクエストコミュニティで認められているマージ規約です。具体的には、このように動作します。

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git Push # May need to force Push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git Push # Will probably need to force Push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote Prune Origin

私はGitを愛するようになり、決してSVNに戻りたくありません。あなたが苦労しているならば、ただそれに固執するだけで、結局あなたはトンネルの終わりに光を見るでしょう。

924
Micah

「矛盾」とは、「同じ内容の並行した進化」を意味します。そのため、マージ中に「万力」になった場合は、同じファイルセットが大幅に進化したことになります。

リベースがマージよりも優れている理由は、次のとおりです。

  • あなたはあなたのローカルコミット履歴をマスターのもので書き換えます(そしてそれからあなたの仕事を再適用し、それからどんな衝突も解決します)
  • 最後のマージは確かに「早送り」のマージになります。マスターのすべてのコミット履歴と、再適用するための変更だけがあるからです。

その場合の正しいワークフロー(共通のファイルセットの進化)が 最初にリベースし、次にmerge であることを確認します。

ただし、バックアップのためにローカルブランチをプッシュした場合、そのブランチは他の誰かに引っ張られる(または少なくとも使用される)べきではありません(コミット履歴は連続するリベースによって書き換えられるため)。


そのトピック(リベースしてからワークフローをマージする)で、 barraponto がコメントに2つの興味深い投稿を述べています。両方とも randyfay.com からのものです。

このテクニックを使うことで、あなたの仕事は常に最新のHEADを最新のものにしたパッチのようにパブリックブランチの上に行きます。

(同様のテクニック Bazaarにも存在する

358
VonC

TL; DR

Git Disasters:A Gory Story で提案されているように、Gitリベースワークフローは、競合の解決が苦手な人やSVNワークフローに慣れている人からあなたを保護しません。それは彼らにとって紛争解決をより退屈にし、悪い紛争解決からの回復をより困難にするだけです。代わりに、diff3を使用して、そもそもそれほど難しくないようにします。


リベースワークフローは、競合解決には向いていません!

私は歴史をきれいにするために非常に親しいです。ただし、競合が発生した場合、すぐにリベースを中止して代わりにマージを実行します!リベースワークフローを競合解決のためのマージワークフローに代わる優れた選択肢(これがまさにこの質問に関するものでした)。

マージ中に「すべて地獄」になると、リベース中に「すべて地獄」になり、潜在的にはさらに多くの地獄になります!その理由は次のとおりです。

理由1:コミットごとに1回ではなく、1回競合を解決する

マージの代わりにリベースする場合、同じ競合に対して、リベースするコミットがある限り、競合解決を実行する必要があります!

実際のシナリオ

マスターから分岐して、ブランチ内の複雑なメソッドをリファクタリングします。リファクタリング作業は、リファクタリングとコードレビューの取得に取り組んでいるため、合計15のコミットで構成されています。私のリファクタリングの一部には、以前にmasterに存在していた混合タブとスペースの修正が含まれます。これは必要ですが、残念ながらmasterでこのメソッドに加えられた変更と競合します。案の定、この方法に取り組んでいる間に、誰かがmasterブランチの同じ方法に単純で正当な変更を加えて、それを私の変更とマージする必要があります。

ブランチをmasterにマージするとき、2つのオプションがあります。

git merge:競合が発生します。彼らがマスターに加えた変更を見て、それを私のブランチ(の最終製品)にマージします。できた.

git rebase:firstコミットと競合が発生します。競合を解決し、リベースを続行します。 secondコミットと競合します。競合を解決し、リベースを続行します。私のthirdコミットと競合します。競合を解決し、リベースを続行します。 fourthコミットと競合が発生します。競合を解決し、リベースを続行します。 fifthコミットと競合が発生します。競合を解決し、リベースを続行します。 sixthコミットと競合が発生します。競合を解決し、リベースを続行します。私はseventhコミットと競合します。競合を解決し、リベースを続行します。 eighthコミットと競合が発生します。競合を解決し、リベースを続行します。 ninthコミットと競合が発生します。競合を解決し、リベースを続行します。 tenthcommitと競合します。競合を解決し、リベースを続行します。 11番目のコミットと競合が発生します。競合を解決し、リベースを続行します。 12番目のコミットと競合が発生します。競合を解決し、リベースを続行します。私はthirteenthコミットと競合します。競合を解決し、リベースを続行します。 14番目のコミットと競合します。競合を解決し、リベースを続行します。私はfifteenthコミットと競合します。競合を解決し、リベースを続行します。

thisが望ましいワークフローである場合、あなたは私をからかわなければなりません。必要なのは、マスターで行われた1つの変更と競合する空白の修正であり、すべてのコミットが競合し、解決する必要があります。そして、これは空白の競合のみを伴うsimpleシナリオです。 天国では、ファイル間の大きなコード変更を伴う実際の競合を禁止し、thatを複数回解決する必要があります。

あなたがする必要があるすべての余分な競合解決により、間違いを犯す可能性が高まります。でも、元に戻すことができるので、gitの間違いは問題ありませんか?もちろん…を除いて.

理由#2:リベースでは、元に戻すことはできません!

紛争の解決は困難であり、一部の人々はそれが非常に苦手であるという点で全員が同意できると思います。間違いが非常に発生しやすいため、gitを使用すると簡単に元に戻すことができるほど素晴らしいのです。

ブランチをマージすると、gitはマージコミットを作成します。マージコミットは、競合の解決が不十分な場合に破棄または修正できます。すでに悪いマージコミットをパブリック/権限のあるリポジトリにプッシュしている場合でも、git revertを使用して、マージによって導入された変更を取り消し、新しいマージコミットでマージを正しくやり直すことができます。

ブランチをリベースすると、競合の解決が間違っている可能性が高いイベントで、あなたは台無しになります。すべてのコミットに不正なマージが含まれるようになりました。リベースだけをやり直すことはできません*。せいぜい、戻って、影響を受ける各コミットを修正する必要があります。楽しくない。

リベース後、もともとコミットの一部であったものと、不適切な競合解決の結果として導入されたものを判別することは不可能です。

* gitの内部ログから古いrefを掘り出すことができる場合、またはリベース前に最後のコミットを指す3番目のブランチを作成する場合、リベースを取り消すことができます。

競合解決から地獄を取り去る:diff3を使用する

たとえば、この競合を考えてみましょう。

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

競合を見ると、各ブランチが何を変更したか、またはその意図が何であったかを知ることは不可能です。私の意見では、これが紛争解決が混乱し困難である最大の理由です。

diff3が助けになります!

git config --global merge.conflictstyle diff3

Diff3を使用すると、新しい競合ごとに3番目のセクション、つまり共通の祖先がマージされます。

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

最初に、マージされた共通の祖先を調べます。次に、各側を比較して、各ブランチの意図を判断します。 HEADがEmailMessageをTextMessageに変更したことがわかります。その目的は、TextMessageに使用されるクラスを変更し、同じパラメーターを渡すことです。また、機能ブランチの意図は、:include_timestampオプションにtrueではなくfalseを渡すことであることがわかります。これらの変更をマージするには、両方の意図を組み合わせます。

TextMessage.send(:include_timestamp => false)

一般に:

  1. 共通の祖先と各ブランチを比較し、どのブランチに最も単純な変更があるかを判断します
  2. その単純な変更を他のブランチのバージョンのコードに適用して、より単純な変更とより複雑な変更の両方が含まれるようにします
  3. 変更を一緒にマージした部分以外の競合コードのすべてのセクションを削除します

代替:ブランチの変更を手動で適用して解決する

最後に、diff3を使用しても、いくつかの競合を理解するのは恐ろしいことです。これは、特にdiffがセマンティック的に一般的ではない共通の行を見つけた場合に発生します(たとえば、両方のブランチで同じ場所に空白行があることがあります!)。たとえば、あるブランチはクラスの本体のインデントを変更したり、同様のメソッドを並べ替えたりします。これらの場合、より良い解決戦略は、マージのいずれかの側からの変更を調べて、手動で差分を他のファイルに適用することです。

Origin/feature1がどこでlib/message.rb競合するかをマージするシナリオで競合を解決する方法を見てみましょう。

  1. 現在チェックアウトされているブランチ(HEAD、または--ours)またはマージするブランチ(Origin/feature1、または--theirs)のどちらを適用するのが簡単かを決定します。 diffをトリプルドット(git diff a...b)で使用すると、bからの最後の分岐以降にaで発生した変更が表示されます。つまり、aとbの共通の祖先をbと比較します。

    git diff HEAD...Origin/feature1 -- lib/message.rb # show the change in feature1
    git diff Origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. より複雑なバージョンのファイルを確認してください。これにより、すべての競合マーカーが削除され、選択した側が使用されます。

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if Origin/feature1's change is more complicated
    
  3. 複雑な変更をチェックアウトし、単純な変更の差分を取得します(手順1を参照)。この差分の各変更を競合ファイルに適用します。

374
Edward Anderson

私のワークフローでは、可能な限りリベースします(そして、頻繁にやり直します。不一致が累積しないようにすることで、ブランチ間の衝突の量と重大度を大幅に減らすことができます)。

ただし、ほとんどリベースベースのワークフローでも、マージする場所があります。

マージは実際には2つの親を持つノードを作成することを思い出してください。次のような状況を考えてみましょう。私は2つの独立した機能ブランチAとBを持っていて、AとBの両方に依存している機能ブランチCを開発したいと思います。

私がしていることは、次のとおりです。

  1. Aの上にブランチCを作成(そしてチェックアウト)します。
  2. Bとマージする

ブランチCには、AとBの両方からの変更が含まれています。その上で開発を継続できます。 Aを変更した場合は、次のようにして分岐のグラフを再構築します。

  1. aの新しい上にブランチTを作成する
  2. tをBとマージする
  3. cをTにリベースする
  4. ブランチTを削除

このようにして私は実際に任意の枝のグラフを維持することができますが、親が変わったときに自動的にリベースするツールがないので、上で説明した状況よりももっと複雑なことをするのはすでに複雑すぎます。

30
Alex Gontmakher

git Push Originを使用しないでください。--mirror UNDER ANY CIRCUMSTANCE。

ローカルボックスにないすべてのリモートブランチが消去されるため、これを実行するかどうかを確認するメッセージは表示されません。

http://Twitter.com/dysinger/status/1273652486

21
Scott Brown

私はあなたの説明を読んだ後に一つ質問があります:それはあなたが決してしなかったということでしょうか?

git checkout master
git pull Origin
git checkout my_new_feature

機能ブランチで 'git rebase/merge master'を実行する前に

your masterブランチはあなたの友人のリポジトリから自動的に更新されないからです。あなたはgit pull Originでそれをしなければなりません。すなわち多分あなたはいつも変わらないローカルマスターブランチからリベースするでしょうか?そしてプッシュ時が来たら、あなたは今まで見たことがない(ローカルの)コミットを持つリポジトリをプッシュしているので、プッシュは失敗する。

14
knweiss

あなたの状況では、私はあなたのパートナーは正しいと思います。リベースの利点は、部外者から見れば、変更はすべてき​​れいな順序で行われたように見えることです。これの意味は

  • 変更内容を確認するのは非常に簡単です
  • あなたは、ニースの小さなコミットを作り続けることができますが、それらのコミットのセットを(マスターにマージすることによって)一度に公開することができます。
  • public masterブランチを見ると、異なる開発者によって異なる機能に対する異なる一連のコミットが見られますが、それらすべてが混在するわけではありません。

バックアップのためにプライベート開発ブランチをリモートリポジトリにプッシュし続けることはできますが、リベースするので、他の人はそれを「パブリック」ブランチとして扱うべきではありません。ところで、これを行うための簡単なコマンドはgit Push --mirror Originです。

記事 Gitを使ったソフトウェアのパッケージ化 はマージとリベースのトレードオフを説明する、かなりいい仕事をしています。状況は少し異なりますが、原則は同じです。基本的には、ブランチがパブリックかプライベートか、そしてどのようにしてそれらをメインラインに統合するかを決定します。

13
Pat Notz

とにかく、私は最近のブランチで私のワークフローをたどっていました、そしてそれをマスターにマージしようと試みたとき、それはすべて地獄に行きました。問題になってはならないこととの衝突がたくさんありました。衝突は私には意味がありませんでした。私のローカルマスターはすべての衝突を解決したので、リモートマスターへの強制プッシュで最終的に最高潮に達しました、しかしリモートのものはまだ幸せではありませんでした。

あなたのパートナーやあなたが提案するワークフローのどちらにおいても、意味のない競合に出くわすべきではありません。たとえあなたが持っていたとしても、あなたが提案されたワークフローに従っていれば、解決後に '強制'プッシュは要求されるべきではありません。それはあなたが実際にプッシュしていたブランチをマージしていないが、リモートチップの子孫ではないブランチをプッシュしなければならなかったことを示唆しています。

何が起こったのか注意深く見る必要があると思います。ローカルブランチを作成してから、ローカルブランチにマージしようとした時点までに、他の誰かが(意図的かどうかにかかわらず)リモートマスターブランチを巻き戻していませんか?

他の多くのバージョン管理システムと比較して、Gitを使用するとツールとの戦いが少なくて済み、ソースストリームの根本的な問題に取り組むことができることがわかりました。 Gitはマジックを実行しないので、矛盾する変更は矛盾を引き起こしますが、コミットの親子関係を追跡することで書き込みを簡単に行うことができます。

12
CB Bailey

私が観察したことから、g​​it mergeはマージ後もブランチを別々に保つ傾向がありますが、rebase then mergeはそれを1つのブランチにまとめます。前者の場合、マージした後でも、どのコミットがどのブランチに属しているかを見つけるのが簡単です。

7
Pepe

「あなたが少数のブランチしか持っていない単一の開発者であっても、リベースを使用して適切にマージする習慣を身につける価値があります。基本的な作業パターンは次のようになります。

  • 既存の支店Aから新しい支店Bを作成する

  • ブランチBに変更を追加/コミットする

  • ブランチAから更新を再配置する

  • ブランチBからの変更をブランチAにマージする」

https://www.atlassian.com/git/tutorials/merging-vs-rebasing/ /

6
Rakka Rage

Gitには「正しい」ワークフローはありません。あなたのボートを浮かべるものは何でも使ってください。ただし、ブランチをマージするときに常に競合が発生する場合は、他の開発者とよりうまく連携する必要がありますか。二人は同じファイルを編集し続けているように聞こえます。また、空白やSubversionのキーワード(「$ Id $」など)にも注意してください。

2
Bombe