一連のコミットを選択して別のブランチにマージする方法は?


640

次のリポジトリレイアウトがあります。

  • マスターブランチ(本番)
  • 統合
  • ワーキング

私が達成したいのは、作業中のブランチからさまざまなコミットを選択し、それを統合ブランチにマージすることです。私はgitにかなり慣れていないので、リポジトリをめちゃくちゃにせずに、これを正確に行う方法(マージではなく、1つの操作でコミット範囲を簡単に選択する方法)を理解できません。これについてのポインタや考えはありますか?ありがとう!


回答:


808

さまざまなコミットに関して、チェリーピッキング です 実用的ではありませんでした。

通りの下に言及したことにより、キース・キム、Gitの1.7.2+はコミットの範囲を-選ぶ桜の機能が導入されました(それでもを認識する必要があり、将来のマージのためのピッキング桜の結果

git cherry-pickは、一連のコミット
(「cherry-pick A..B」と「cherry-pick --stdin」など)を選択することを学習したため、「」を選択しましたがgit revert、これらは「rebase [-i]」が持っているより優れたシーケンス制御をサポートしていません。

ダミアンの コメントと警告:

cherry-pick A..B」形式でAは、より古い必要がありBます。
順序が間違っている場合、コマンドは通知なしで失敗します。

範囲BD(も含めて)選択する場合は、になりますB^..D
例として「Gitは以前のコミットの範囲からブランチを作成しますか?」を参照してください。

以下のようJubobsは言及コメントで

これはBルートコミットではないことを前提としています。unknown revisionそうしないと、「」エラーが発生します。

注:Git 2.9.x / 2.10(2016年第3四半期)以降、孤立したブランチ(空のヘッド)でコミットの範囲を直接選択できます。「既存のブランチをgitで孤立させる方法」を参照してください。


元の回答(2010年1月)

Charles Baileyがここで説明したようにrebase --onto、統合ブランチの上で特定の範囲のコミットを再生する場合は、Aのほうが良いでしょう。 (また、git rebaseのmanページで「あるブランチに基づくトピックブランチを別のブランチに移植する方法は次のとおりです」の実用的な例を参照してください)
git rebase --onto

現在のブランチが統合の場合:

# Checkout a new temporary branch at the current location
git checkout -b tmp

# Move the integration branch to the head of the new patchset
git branch -f integration last_SHA-1_of_working_branch_range

# Rebase the patchset onto tmp, the old location of integration
git rebase --onto tmp first_SHA-1_of_working_branch_range~1 integration

それはすべての間を再生します:

  • first_SHA-1_of_working_branch_range(したがって~1)の親の後:再生したい最初のコミット
  • 最大 " integration"(workingブランチから、再生したい最後のコミットを指します)

tmp」へ(これintegrationは以前に指し示していた場所を指します)

これらのコミットの1つが再生されるときに競合がある場合:

  • 解決して「git rebase --continue」を実行してください。
  • またはこのパッチをスキップして、代わりに「git rebase --skip」を実行します
  • または「git rebase --abort」ですべてをキャンセルします(そしてintegrationブランチをブランチに戻しますtmp

その後rebase --ontointegration戻って最後になります(「である統合ブランチのコミットtmp」の枝+すべて再生コミットをして)

ここで説明するようにrebase --onto、チェリーピッキングまたはを使用すると、後続のマージに影響を与えることを忘れないでください


ここでは、純粋な「cherry-pick」ソリューションについて説明します。解決策は次のとおりです。

パッチアプローチを使用する場合は、「git format-patch | git am」と「git cherry」を選択できます。
現在、git cherry-pickコミットつだけを受け入れていますが、範囲を選択したい場合はB経由DということでしょうB^..Dので、gitの専門用語に

git rev-list --reverse --topo-order B^..D | while read rev 
do 
  git cherry-pick $rev || break 
done 

しかし、とにかく、一連のコミットを「リプレイ」する必要がある場合は、「リプレイ」という言葉rebaseでGitの「」機能を使用するように強制する必要があります。


1
-mオプションを必要とする親を持つコミットがある場合、それらのコミットをどのように処理しますか?または、これらのコミットを除外する方法はありますか?
2016

@aug -m-m、このチェリーピック用に選択したパラメーターによって参照されるメインラインを選択することで、それらを処理することになっています。
VonC

問題は、コミットの範囲を選択する場合、親コミットを正しく選択することになりますが、通常のコミットに到達すると失敗し、コミットはマージではないと表示されます。私の質問は-m、コミットの範囲を厳選したときに親コミットにヒットした場合にのみオプションを渡す方法をより適切に表現していると思いますか?現在、私が渡す-mようgit cherry-pick a87afaaf..asfa789 -m 1にすると、それは範囲内のすべてのコミットに適用されます。
2016

@augストレンジ、問題を再現しなかった。あなたのgitバージョンは何ですか?そしてあなたが見る正確なエラーメッセージは何ですか?
VonC

ああ、gitバージョン2.6.4(Apple Git-63)を実行しています。私が見るエラーは、error: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given.実際にあなたができると実際に気づいたようなものでgit cherry-pick --continue、それは問題ありません(ただし、親のコミットは含まれません)
Aug

136

git v1.7.2以降、cherry pickはさまざまなコミットを受け入れることができます。

git cherry-pickコミットの範囲を選択することを学びました(例:cherry-pick A..Bおよびcherry-pick --stdingit revertrebase [-i]ただし、これらはより良いシーケンス制御をサポートしていません。


103
cherry-pick A..BはコミットAを取得しないことに注意してください(必要なA~1..B場合)。競合が発生した場合、リベースのようにgitは自動的に続行されません(少なくとも1.7.3.1以降)
Gabe Moothart

3
当然のgit cherry-pick A..B Cことながら、期待どおりに機能しないことにも注意してください。範囲内のすべてを選択してA..BコミットするわけではありませんC。これを行うには、最初にgit cherry-pick A..B、次にの2行に分割する必要がありますgit cherry-pick C。したがって、範囲がある場合は常に、個別に実行する必要があります。
MicroVirus

42

2つのブランチがあるとします。

"branchA":コピーするコミットが含まれます( "commitA"から "commitB"へ)

「branchB」:コミットを「branchA」から転送するブランチ

1)

 git checkout <branchA>

2)「commitA」と「commitB」のIDを取得する

3)

git checkout <branchB>

4)

git cherry-pick <commitA>^..<commitB>

5)競合がある場合は、解決して入力します

git cherry-pick --continue

チェリーピックプロセスを続行します。


魅力のように働いた!どうもありがとう!
snukone

28

実際にブランチをマージしたくないですか?作業中のブランチに不要な最近のコミットがある場合は、目的の時点でHEADを使用して新しいブランチを作成できます。

ここで、何らかの理由で本当に一連のコミットを選択したい場合は、エレガントな方法として、パッチセットをプルして新しい統合ブランチに適用するだけです。

git format-patch A..B
git checkout integration
git am *.patch

これは本質的にとにかくgit-rebaseがやっていることですが、ゲームをプレイする必要はありません。マージする必要--3wayがあるgit-am場合は、に追加できます。指示どおりに実行する場合は、これを行うディレクトリに他の* .patchファイルがないことを確認してください...


1
他のリビジョン範囲と同様に、A^を含める必要があることに注意してくださいA
Gino Mempin

9

簡単に実行できるように、私はVonCのコードを短いbashスクリプトにラップしましたgit-multi-cherry-pick

#!/bin/bash

if [ -z $1 ]; then
    echo "Equivalent to running git-cherry-pick on each of the commits in the range specified.";
    echo "";
    echo "Usage:  $0 start^..end";
    echo "";
    exit 1;
fi

git rev-list --reverse --topo-order $1 | while read rev 
do 
  git cherry-pick $rev || break 
done 

同じsvnトランクでサードパーティのコードとカスタマイズの両方が混在しているプロジェクトの履歴を再構築するため、現在これを使用しています。今後、カスタマイズの理解を深めるために、サードパーティのコアコード、サードパーティのモジュール、カスタマイズをそれぞれのgitブランチに分割しています。git-cherry-pick同じリポジトリに2つのツリーがありますが、共有の祖先がないため、この状況で役立ちます。


3

上記のすべてのオプションでは、マージの競合を解決するように求められます。チームに対してコミットされた変更をマージする場合、開発者からマージの競合を解決して続行することは困難です。ただし、「git merge」は1回でマージを実行しますが、リビジョンの範囲を引数として渡すことはできません。リビジョンのマージ範囲を行うには、「git diff」および「git apply」コマンドを使用する必要があります。パッチファイルの差分が多すぎると「git apply」が失敗するので、ファイルごとにパッチを作成して適用する必要があることを確認しました。スクリプトは、ソースブランチで削除されたファイルを削除できないことに注意してください。これはまれなケースです。そのようなファイルをターゲットブランチから手動で削除できます。パッチを適用できない場合、「git apply」の終了ステータスはゼロではありませんが、

以下はスクリプトです。

enter code here



  #!/bin/bash

    # This script will merge the diff between two git revisions to checked out branch
    # Make sure to cd to git source area and checkout the target branch
    # Make sure that checked out branch is clean run "git reset --hard HEAD"


    START=$1
    END=$2

    echo Start version: $START
    echo End version: $END

    mkdir -p ~/temp
    echo > /tmp/status
    #get files
    git --no-pager  diff  --name-only ${START}..${END} > ~/temp/files
    echo > ~/temp/error.log
    # merge every file
    for file in `cat  ~/temp/files`
    do
      git --no-pager diff --binary ${START}..${END} $file > ~/temp/git-diff
      if [ $? -ne 0 ]
      then
#      Diff usually fail if the file got deleted 
        echo Skipping the merge: git diff command failed for $file >> ~/temp/error.log
        echo Skipping the merge: git diff command failed for $file
        echo "STATUS: FAILED $file" >>  /tmp/status
        echo "STATUS: FAILED $file"
    # skip the merge for this file and continue the merge for others
        rm -f ~/temp/git-diff
        continue
      fi

      git apply  --ignore-space-change --ignore-whitespace  --3way --allow-binary-replacement ~/temp/git-diff

      if [ $? -ne 0 ]
       then
#  apply failed, but it will fall back to 3-way merge, you can ignore this failure
         echo "git apply command filed for $file"
       fi
       echo
       STATUS=`git status -s $file`


       if [ ! "$STATUS" ]
       then
#   status is null if the merged diffs are already present in the target file
         echo "STATUS:NOT_MERGED $file"
         echo "STATUS: NOT_MERGED $file$"  >>  /tmp/status
       else
#     3 way merge is successful
         echo STATUS: $STATUS
         echo "STATUS: $STATUS"  >>  /tmp/status
       fi
    done

    echo GIT merge failed for below listed files

    cat ~/temp/error.log

    echo "Git merge status per file is available in /tmp/status"

2

私は数日前、Voncの非常に明確な説明を読んだ後、それをテストしました。

私の歩み

開始

  • 支店dev:ABCDEFGHIJ
  • 支店target:ABCD
  • 私も欲しくEないH

ブランチにステップEとHなしでフィーチャーをコピーするステップ dev_feature_wo_E_H

  • git checkout dev
  • git checkout -b dev_feature_wo_E_H
  • git rebase --interactive --rebase-merges --no-ff Dどこで置くdropのフロントEHリベースエディタで
  • 競合を解決し、続行して commit

ブランチdev_feature_wo_E_Hをターゲットにコピーする手順。

  • git checkout target
  • git merge --no-ff --no-commit dev_feature_wo_E_H
  • 競合を解決し、続行して commit

いくつかの備考

  • 私はcherry-pick前の日にあまりにも多くのためにそれをやった
  • git cherry-pick 強力でシンプルですが

    • 重複したコミットを作成します
    • merge最初のコミットと重複するコミットの競合を解決する必要がある場合は、1つまたは2つの場合cherry-pickは「チェリーピック」でも大丈夫ですが、それ以上の場合は冗長すぎてブランチが複雑になります
  • 私の心の中で私が行ったステップは、 git rebase --onto

1
良いポスト。賛成。stackoverflow.com/a/38418941/6309を思い出します。2012年にチェリーピッキングの欠点を要約しました:stackoverflow.com/a/13524494/6309
VonC

1

別のオプションは、範囲の前のコミットに戦略をマージし、その範囲の最後のコミット(または最後のコミットの場合はブランチ)と「通常の」マージを行うことです。したがって、機能ブランチにマージされるマスターの2345および3456コミットのみを想定します。

主人:
1234年
2345
3456
4567

機能ブランチ:

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