Assume I have a branch with the following history:
A - B - C - D
Between B and C I modified a bunch of files including a particular file, call it foo.txt.
Then, on revision D, I modified that same file, foo.txt.
Meanwhile, a friend of mine took a snapshot of my directory at B and decided he would tweak foo.txt in some way. Let's call that revision E. If they were part of the same repository, they would look like this:
A - B - C - D \ E
However, they're not so we just have A - B - C - D in my repository and then he sends me a patch for the differences between B and E.
Since I have messed with foo.txt as well, I can't just apply the patch directly to D, the context for the diff won't match, it's expecting me to be at or around B.
It seems like I would want to kind of create the hypothetical repository tree I mentioned above, then do a merge between D and E in this repository in order to play back my changes C and D together in the right order.
So my questions:
You're correct that you do want to take advantage of git's merge faculties. There are still a few possibilities. The most indicative of what actually happened is probably the merge, but a method resulting in a modified version of E
applied on top of D
isn't so bad either. Let's talk about how to do that!
If the patch contains the blobs it applies to (the output from git format-patch
does), then git am
is capable of attempting a three-way merge! The blob SHA1 records in a diff look like this:
diff --git a/foo.txt b/foo.txt
index ca1df77..2c98844 100644
If you've got that, you're in luck. Just use git am --3way <patch>
. Unfortunately, git am
does require the email-style header that format-patch produces, so if the patch came from git diff
instead of git format-patch
, you have to kludge a little. You can add it yourself:
From: Bobby Tables <[email protected]>
Date: 2 Nov 2010
Subject: [PATCH] protect against injection attack
<original diff>
git am
should be able to work with that, and if you don't get it all exactly how you wanted it, you can always git commit --amend
to fix it. (You could also try using git apply --build-fake-ancestor=foo.txt <patch>
, but it really doesn't work in a user-friendly way. I think tricking git am
is easier.)
If the patch doesn't contain any blob SHA1s (i.e. it was created with diff, not a git command), again, tell your friend how to use git, and I'm pretty sure you can still kludge it. You know what version of foo.txt the patch is supposed to apply to! To get its SHA1 from commit B, use:
git ls-tree <SHA1 of B>:<directory containing foo.txt>
It'll be one of the blobs listed. (I know there's a direct way, but I just can't think of it right now.) You can then add a fake git diff header. Suppose its hash is abcdef12:
diff --git a/foo.txt b/foo.txt
index abcdef12..
Git doesn't actually need anything but that first hash; though the git diff output would have the final hash and a mode, am
isn't looking for it, so you can get away with leaving it off. (And yes, I just tested this. I hadn't done this before!)
This will result in a history like A - B - C - D - E'
, where E'
is your friend's patch, but applied to D
; it's a result of a three-way merge between the content in D
, B
, and your friend's patch.
Finally, if you don't want to muck around with any of that, you can do as you said:
git checkout -b bobby <SHA1 of B>
# apply the patch
git commit --author="Bobby Tables <[email protected]>"
git checkout master
git merge bobby
# or `git cherry-pick bobby` to grab the single commit and apply to master
# or `git rebase bobby master` to rebase C and D onto B
I've had a look around—I swear there must be a way to do this automagically in git, but for now your best bet is probably to do what you've said.
Assuming your commit at B
has ID 12345...
:
$ git checkout 12345
Note: checking out '12345....'.
You are in 'detached HEAD' state.
[...]
$ git checkout -b friends_change
$ git apply < patchfile.patch
$ git status
[... something interesting ...]
$ git commit -m "Applying friend's patches to foo"
$ git checkout master
$ git merge friends_change
Of course, you could rebase instead, if you haven't published history to anyone:
$ git rebase friends_change
This'll create a tree like this:
A - B - C - D (old master)
\
E - C' - D' (new master)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With