チェリーピックの作業後にgitはどのようにマージされますか?


190

masterブランチがあるとしましょう。

次に、 newbranch

git checkout -b newbranch

そして、2つの新しいコミットを作成しnewbranchます:commit1commit2

次にマスターに切り替えて cherry-pick

git checkout master
git cherry-pick hash_of_commit1

gitk調べてみると、commit1とその厳選されたバージョンではハッシュが異なるため、厳密には2つの異なるコミットであることがわかります。

最後にマージnewbranchmasterます:

git merge newbranch

同じハッシュを2回適用する必要があることを示しているため、ハッシュが異なる2つのコミットが問題なくマージされたことを確認してください。

gitはマージ中に実際にコミットの内容をスマートに分析し、変更を2回適用しないようにするか、これらのコミットが内部でリンクされているとマークされていると判断しますか?

回答:


131

短い答え

心配しないで、Gitが処理します。

長い答え

たとえばSVN 1とは異なり、Gitはコミットをデルタ形式で保存しませんが、スナップショットベースの2,3です。SVNは単純に、マージされた各コミットをパッチとして適用しようとしますが(説明したとおりの理由で失敗します)、Gitは通常、このシナリオを処理できます。

マージするとき、Gitは両方のHEADコミットのスナップショットを新しいスナップショットに結合しようとします。コードの一部またはファイルが両方のスナップショットで同一である場合(つまり、コミットが既に厳選されているため)、Gitはそれに触れません。

出典

1 Subversionのスキップデルタ
2 Gitの基本
3 Gitオブジェクトモデル


40
実際には、マージについて心配する必要があると私は言います。「gitが処理する」というのは良い経験則ではありません。
cregox 2013

4
実際、マージによってコンテンツが重複する場合があります。Gitはそれを処理する場合とそうでない場合があります。
ドンキホーテ2013

これはひどく間違っています。かなりの形式でファイルを保存しています。そして、私がスナップショットを保存するために使用されたSVNを正しく思い出すと
he_the_great

2
@he_the_great、いいえ。SVNのスキップデルタストレージ形式(!=スナップショット)は、マニュアルに詳しく記載されています。そして、私は本当にあなたがcondevableによって意味するものを理解していません。私はネイティブスピーカーではありませんが、それが本当の言葉ではないことは確かです。
helmbert 2016年

2
@he_the_greatしかし、packfilesとしても、ファイルの特定のハッシュは完全なファイルになります。はい、それはデルタを使用して圧縮しますが、コミットの変更のデルタではなく、ファイルのハッシュ間のデルタです。コミットオブジェクトに関する限り、それはファイル全体のハッシュを参照しているツリーを参照しています。内部でデータが圧縮されていることは、gitの動作に影響しません。Gitはコミットに関する限り完全なファイルを保存し、SVNは私が理解しているようにコミットのデルタを保存します。
Peter Olson

43

そのようなマージの後、あなたは歴史の中で厳選されたコミットを二度持つかもしれません。

これを防ぐための解決策は、複製(チェリーピック)コミットのブランチを推奨する記事から引用し、マージ前にリベースを使用します。

git cherry-pick後のgit merge:重複したコミットを回避する

マスターブランチとブランチbがあるとします。

   o---X   <-- master
    \
     b1---b2---b3---b4   <-- b

ここで、マスターのコミットb1とb3が緊急に必要ですが、bの残りのコミットは必要ありません。マスターブランチをチェックアウトし、cherry-pickでb1とb3をコミットします。

$ git checkout master
$ git cherry-pick "b1's SHA"
$ git cherry-pick "b3's SHA"

結果は次のようになります。

   o---X---b1'---b3'   <-- master
    \
     b1---b2---b3---b4   <-- b

マスターで別のコミットを実行すると、次のようになります。

   o---X---b1'---b3'---Y   <-- master
    \
     b1---b2---b3---b4   <-- b

ブランチbをマスターにマージする場合:

$ git merge b

次のようになります。

   o---X---b1'---b3'---Y--- M  <-- master
     \                     /
      b1----b2----b3----b4   <-- b

つまり、b1とb3によって導入された変更は履歴に2回表示されます。これを回避するには、マージの代わりにリベースできます。

$ git rebase master b

これは次のようになります:

   o---X---b1'---b3'---Y   <-- master
                        \
                         b2'---b4'   <-- b

最後に:

$ git checkout master
$ git merge b

私たちに与える:

   o---X---b1'---b3'---Y---b2'---b4'   <-- master, b

EDIT David Lemonのコメントが想定する修正


1
リベースについての素晴らしいヒント!自動的に選択されたすべてのコミットを「スキップ」します。
iTake 2017

2
正直言って、それは本当だとは思えないほどいいですね。目で見なければなりません。また、リベース修正のコミット、あなたの最後のタイムラインがあるべき---Y---b2'---b4'
デヴィッド・レモン

完璧に動作します。史上最高のコミットを2度取得したくない場合に非常に役立ちます。
user2350644

1
リベースは美しいですが、それを使用することの危険性は、古いbから作成されたフォークまたはブランチが同期しなくなることであり、ユーザーはgit reset --hardやgitなどの手段に頼る必要があるかもしれません。 push -f?
JHH

1
@JHHそれが、私たちがここでローカルブランチをリベースする理由です
ephemerr
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.