Git: How to create patches for a merge?

27,403

Solution 1

If you examine the content of the first two patches you'll see the issue:

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

from the perspective of the branch you were working on at the time (foo and bar) both of these commits have removed the "initial file" line and replaced it with something else entirely. AFAIK, there's no way to avoid this kind of conflict when you generate a patch of a non-linear progression with overlapping changes (your branch commits B and C in this case).

People normally use patches to add a single feature or bug fix off a known good prior work state -- the patch protocol is simply not sophisticated enough to handle merge history like Git does natively. If you want someone to see your merge then you need to push/pull between branches not drop back diff/patch.

Solution 2

There does not seem to be a solution producing individual commits à la git format-patch, but FWIW, you can format a patch containing the effective merge commit, suitable/compatible with git am:

Apparently, the Git Reference guide provides the first hint:

git log -p show patch introduced at each commit

[...] That means for any commit you can get the patch that commit introduced to the project. You can either do this by running git show [SHA] with a specific commit SHA, or you can run git log -p, which tells Git to put the patch after each commit. [...]

Now, the manual page of git-log gives the second hint:

git log -p -m --first-parent

... Shows the history including change diffs, but only from the "main branch" perspective, skipping commits that come from merged branches, and showing full diffs of changes introduced by the merges. This makes sense only when following a strict policy of merging all topic branches when staying on a single integration branch.

Which in turn means in concrete steps:

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

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

And this can be applied as intended:

git am feature.patch

Again, this won't contain the individual commits, but it produces a git am compatible patch out of a merge commit.


Of course, if you don't need a git am compatible patch in the first place, then it's way simpler:

git diff origin/master > feature.patch

But I guess you already figured as much, and if you landed on this page here, you are actually searching for the workaround/solution I've described above. ;)

Solution 3

Note that a bare git log -p won't show any patch content for the merge commit "M", but using git log -p -c does coax it out. However, git format-patch doesn't accept any arguments analogous to the -c (or --combined, -cc) accepted by git log.

I too remain stumped.

Solution 4

Expanding sun's answer, I came to a command that can produce a series of patches similar to what git format-patch would produce if it could, and that you can feed to git am to produce an history with the individual commits :

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

Patches will be named xx0001.patch to xxLAST.patch

Share:
27,403
geofflee
Author by

geofflee

Updated on January 21, 2022

Comments

  • geofflee
    geofflee over 2 years

    When I use git format-patch, it doesn't seem to include merges. How can I perform a merge and then e-mail it to someone as a set of patches?

    For example, let's say that I merge two branches and perform another commit on top of the merge:

    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"
    

    This creates the following tree:

        B
      /   \
    A       M - D 
      \   /
        C
    

    Now I try to checkout the initial commit and replay the above changes:

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

    This produces a merge conflict. In this scenario, git format-patch master..bar only produces 3 patches, omitting "Commit M". How do I deal with this?

    -Geoffrey Lee

  • JasonSmith
    JasonSmith over 14 years
    It's tough to prove a negative but like seh, I took a whack at this problem and I get the feeling you are right.
  • geofflee
    geofflee over 14 years
    Yes, I understand the issues with the patch files. I was hoping there would be a workaround, because one would assume that the Linux or Git projects have encountered similar situations, and they rely entirely on submitting patches via e-mail rather than push/pull. I'll ping the Git mailing list and see if they have any additional feedback. Thanks.
  • omnisis
    omnisis over 14 years
    if you had instead replaced the merge line above with $ git merge --squash foo $ git commit -a -m"Commit M" your patch would have applied cleanly...
  • geofflee
    geofflee over 14 years
    Yes, I could have squashed the commits, but that would destroy history and generally isn't a good way to approach distributed version control, imho. Fortunately, someone on the Git mailing list pointed me to "git bundle", which allows you to package and transfer Git objects manually. This seems to be the best solution.
  • subrat71
    subrat71 over 14 years
    The aforementioned suggestion by Jeff King regarding git bundle: thread.gmane.org/gmane.comp.version-control.git/140321/…
  • John Lehmann
    John Lehmann almost 12 years
    Thank you, this was the exact answer I was looking for to why git log -p didn't show patches for merge commits.
  • Compholio
    Compholio almost 7 years
    If you call csplit with -z and -f '' then you don't need to delete the first patch and it won't have a "xx" prefix." I really wish that git-format-patch had an integrated option to do something like this...
  • Darko Maksimovic
    Darko Maksimovic almost 7 years
    Great answer, thank you very much! I'm afraid it's incomplete, as you also need --reverse in git log, otherwise "git am feature.patch" won't work. So, like this: git log --reverse -p --pretty=email --stat -m --first-parent origin/master..HEAD > feature.patch
  • sun
    sun almost 7 years
    Thanks @DarkoMaksimovic, updated the solution accordingly. --reverse wasn't included previously, because the described procedure only consisted of a single commit. But you're absolutely right!
  • Cyril Duchon-Doris
    Cyril Duchon-Doris almost 5 years
    csplit: illegal option -- b on macOS
  • testing
    testing almost 4 years
    use --binary if you want to include images and so on
  • alper
    alper over 3 years
    diff: unrecognized option '--git'
  • sun
    sun over 2 years
    Thanks @testing, good point! I have the binary flag in my global git config already, so I didn't think of it. Added to the answer.