web-dev-qa-db-ja.com

Git:マージ用のパッチを作成するにはどうすればよいですか?

git format-patchを使用すると、マージが含まれていないようです。マージを実行して、パッチのセットとして誰かに電子メールで送信するにはどうすればよいですか?

たとえば、2つのブランチをマージし、マージの上に別のコミットを実行するとします。

git init

echo "initial file" > test.txt
git add test.txt
git commit -m "Commit A"

git checkout -b foo master
echo "foo" > test.txt
git commit -a -m "Commit B"

git checkout -b bar master
echo "bar" > test.txt
git commit -a -m "Commit C"

git merge foo
echo "foobar" > test.txt
git commit -a -m "Commit M"

echo "2nd line" >> test.txt
git commit -a -m "Commit D"

これにより、次のツリーが作成されます。

    B
  /   \
A       M - D 
  \   /
    C

ここで、最初のコミットをチェックアウトして、上記の変更を再生しようとします。

git checkout -b replay master
git format-patch --stdout master..bar | git am -3

これにより、マージの競合が発生します。このシナリオでは、git format-patch master..barは「CommitM」を省略して、3つのパッチのみを生成します。どうすればこれに対処できますか?

-ジェフリー・リー

29
geofflee

最初の2つのパッチの内容を調べると、次の問題が発生します。

diff --git a/test.txt b/test.txt
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-initial file
+foo

diff --git a/test.txt b/test.txt
index 7c21ad4..5716ca5 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-initial file
+bar

当時作業していたブランチ(fooとbar)の観点から、これらのコミットは両方とも「初期ファイル」行を削除し、それを完全に別のものに置き換えました。ちなみに、変更が重複する非線形進行のパッチを生成する場合、この種の競合を回避する方法はありません(この場合、ブランチはBとCをコミットします)。

人々は通常、パッチを使用して、既知の良好な以前の作業状態から単一の機能またはバグ修正を追加します。パッチプロトコルは、Gitがネイティブに行うように、マージ履歴を処理するのに十分なほど洗練されていません。誰かにマージを見てもらいたい場合は、diff/patchをドロップバックせずにブランチ間でプッシュ/プルする必要があります。

5
omnisis

git format-patchのように個々のコミットを生成するソリューションはないようですが、FWIWでは、git amと適切/互換性のある効果的なマージコミットを含むパッチをフォーマットできます。

どうやら、 Git Reference ガイドは最初のヒントを提供します:

git log -p各コミットで導入されたパッチを表示

[...]つまり、どのコミットでも、プロジェクトに導入されたコミットのパッチを入手できます。これを行うには、特定のコミットSHAでgit show [SHA]を実行するか、git log -pを実行して、各コミットの後にパッチを適用するようにGitに指示します。 [...]

さて、 git-log のマニュアルページは2番目のヒントを与えます:

git log -p -m --first-parent

...変更の差分を含む履歴を表示しますが、「メインブランチ」の観点からのみ、マージされたブランチからのコミットをスキップし、マージによって導入された変更の完全な差分を表示します。これは、単一の統合ブランチにとどまるときにすべてのトピックブランチをマージするという厳格なポリシーに従う場合にのみ意味があります。

これは、具体的なステップで意味します。

# Perform the merge:
git checkout master
git merge feature
... resolve conflicts or whatever ...
git commit

# Format a patch:
git log -p --reverse --pretty=email --stat -m --first-parent Origin/master..HEAD > feature.patch

そして、これは意図したとおりに適用できます。

git am feature.patch

繰り返しますが、これには個々のコミットは含まれませんが、マージコミットからgit am互換のパッチが生成されます。


もちろん、そもそもgit am互換のパッチが必要ない場合は、はるかに簡単です。

git diff Origin/master > feature.patch

しかし、私はあなたがすでに多くを理解していると思います、そしてあなたがここでこのページにたどり着いたなら、あなたは実際に私が上で説明した回避策/解決策を探しています。 ;)

28
sun

裸のgit log -pは、マージコミット「M」のパッチコンテンツを表示しませんが、git log -p -cを使用するとそれがだまされてしまうことに注意してください。ただし、git format-patchは、-cが受け入れる--combined(または-ccgit log)に類似した引数を受け入れません。

私も困惑したままです。

9
seh

Sunの答えを拡張すると、可能であればgit format-patchが生成するものと同様の一連のパッチを生成でき、git amにフィードして履歴を生成できるコマンドに到達しました。個々のコミットで:

git log -p --pretty=email --stat -m --first-parent --reverse Origin/master..HEAD | \
csplit -b %04d.patch - '/^From [a-z0-9]\{40\} .*$/' '{*}'
rm xx0000.patch

パッチの名前はxx0001.patchからxxLAST.patchになります。

5

Philippe De Muyterのソリューションを使用して、パッチをgit-format-patchと同じ方法でフォーマットするバージョンを作成しました(私が知る限り)。 RANGEを目的のコミット範囲(Origin..HEADなど)に設定して、次の手順を実行します。

LIST=$(git log --oneline --first-parent --reverse ${RANGE});
I=0;
IFS=$'\n';
for ITEM in ${LIST}; do
    NNNN=$(printf "%04d\n" $I);
    COMMIT=$(echo "${ITEM}" | sed 's|^\([^ ]*\) \(.*\)|\1|');
    TITLE=$(echo "${ITEM}" | sed 's|^\([^ ]*\) \(.*\)|\2|' | sed 's|[ -/~]|-|g' | sed 's|--*|-|g' | sed 's|^\(.\{52\}\).*|\1|');
    FILENAME="${NNNN}-${TITLE}.patch";
    echo "${FILENAME}";
    git log -p --pretty=email --stat -m --first-parent ${COMMIT}~1..${COMMIT} > ${FILENAME};
    I=$(($I+1));
done

これをgit-quiltimportで使用している場合は、空のマージコミットを除外する必要があります。そうしないと、「パッチが空です。分割が間違っていましたか?」というエラーが表示されます。

0
Compholio