Reputation: 6233
I have two repositories. In one, I make changes to file ./hello.test
. I commit the changes and create a patch from that commit with git format-patch -1 HEAD
. Now, I have a second repository that contains a file that has the same contents as hello.test but is placed in a different directory under a different name: ./blue/red/hi.test
. How do I go about applying the aforementioned patch to the hi.test
file? I tried git am --directory='blue/red' < patch_file
but that of course complains that the files are not named the same (which I thought Git didn't care about?). I know I could probably edit the diff to apply to that specific file but I'm looking for a command solution.
Upvotes: 158
Views: 96423
Reputation: 481
./repo-a
and ./repo-b
, which we must assume do not share any history.hello.test
from repo-a onto a file named blue/red/hi.test
in repo-b.hello.test
are recorded as commits in repo-a.repo-a
└── hello.test
repo-b
└── blue
└── red
└── hi.test
repo-a/hello.test
are now reflected in repo-b/blue/red/hi.test
.repo-b
minimally modified. See solution (2) below.git diff
+ GNU patch
This is essentially @georgebrock's answer as well.
git -C repo-a diff --patch HEAD~1 -- 'hello.test' > a.patch
patch repo-b/blue/red/hi.test a.patch
Or as a one-liner:
patch repo-b/blue/red/hi.test <(git -C repo-a diff HEAD~1 -- 'hello.test')
This one works so nicely because we explicitly specify our target file on the command line for patch
, which lets us ignore any filename discrepancies stemming from the patchfile.
The --patch
switch for git diff
is optional in most cases, but it costs ya nothing and increases portability in oddball cases.
Finally, you can apply patches with git apply
, but then you would need to fixup the patchfile to contain correct filepaths as an intermediate step.
sed -Ei 's|(\S)/hello.test|\1/blue/red/hi.test|g' a.patch
cd repo-b
git apply ../a.patch
It seems to me like OP's original approach sought to preserve commit metadata, based on the use of git format-patch
and git am
.
So let's see if we can fulfill that without running into filename mismatch errors 😄
Aside: The reason this is such a challenge is because fundamentally,
git format-patch
andgit am
were designed to be used on repos having shared histories, a.k.a. the same repository (or forks thereof). Using these utilities to apply patches onto unrelated repositories does work, but it's like trying to use a newspaper to make a toothbrush—the results vary extremely widely, and you'll probably experience an icky feeling after all's said and done.
git -C repo-a format-patch -p1 --stdout \
| sed -E 's|(\S)/hello.test|\1/blue/red/hi.test|g' \
> b.patch
cd repo-b
git am -3 ../b.patch
The -3
switch to git am
enables 3-way merging, which IMO especially with today's mergetools, is the easiest way to fix any broken patches that might remain (aside from trying again with git am -C2
in trivial cases). If you don't anticipate any patch/merge issues, -3
is a precautionary no-op.
patch -F3
) or reducing the -C
option to git apply
(e.g. git apply -C2
), but your mileage may vary.-U5
) can save your skin when line numbers seldom match anymore, but it's not a cure-all. In fact, more context can sometimes even worsen the frequency of patch rejections / merge conflicts. If this happens to you, then you probably need to start looking into different methods of splicing your text files...git add --patch
instead of any of the previously mentioned tools.format-patch
/ am
/ apply
sed
, ...# (preconditions still hold!)
cp repo-a/hello.test repo-b/blue/red/hi.test
cd repo-b
git add --patch .
# begin interactive patching experience <3
# it's over 😥 no more hunks
git commit -m 'patched in changes from repo-a'
?
to get a handy legend for the interactive commands 🤑s
for "split" is underrated~)Personally, this is the solution I would choose almost every time. Especially if it's code you're patching, or any other irregular language (i.e. markup, JSON), interactively patching changes in this way takes you and your collaborators a lot further—specifically in terms of ensuring correctness and generally catching bugs before they're committed.
git checkout --patch
= great if the changes already exist within the current repo, in a different file or/and in a different commit-ish (branch, tag, etc).Upvotes: 1
Reputation: 1117
There is a simple solution that does not involve manual patch editing nor external script.
In the first repository (this may also export a range of commit, add the -1
flag if you want to select only one commit) :
git format-patch --relative <committish> --stdout > ~/patch
In the second repository :
git am --directory blue/red/ ~/patch
Instead of using --relative
in git format-patch
, another solution is to use -p<n>
option in git am
to strip n
directories from the path of the patches, as mentioned in a answer to a similar question.
It is also possible to run git format-patch --relative <committish>
without the --stdout
, and it will generate a set of .patch
files. These files can then be fed directly to git am
with git am --directory blue/red/ path/to/*.patch
.
Upvotes: 100
Reputation: 8995
FYI: I recently had problems trying to download a patch from Github and applying it to a local file (which was an "override" in a new location).
git am
wouldn't apply the patch either because the file was "not in index" or "dirty." But, I found that the simple patch
command could apply the patch. It did prompt me for the name of the file to be patched.
Got the job done, anyway ...
Upvotes: 0
Reputation: 6791
Building upon the answer by @georgebrock, here's a solution I used:
First, create the patch files as usual (eg. git format-patch commitA..commitB
).
Then make sure that your target repository is clean (there should be no changed or untracked files) and apply the patches like this:
cd second-repo
git am ~/00*.patch
For every patch file you will get an error like "error: XYZ does not exist in index". You can now apply this patch file manually:
patch --directory blue/red < ~/0001-*.patch
git add -a
git am --continue
You have to do these three steps for each patch file.
This will preserve the original commit message etc. without requiring any special git format-patch
command or editing the patch files.
Upvotes: 9
Reputation:
I understand the two files are exactly the same in your situation, thus the patch is likely to succeed.
However, in case you want to apply a patch to a similar, but not exactly the same file, or you want to do an interactive patching, you will use three way merge.
Say you modified File A
, let's denote A~1
as the previous version, and you want to apply the diff between A~1
to A
to File B
.
Open a three way merge tool, for instance Beyond Compare, the path of left panel is A
, middle panel is the common ancestor so the path is A~1
, the path of right panel is B
. Then, the lower panel shows the result of applying the diff between A~1
to A
to File B
.
The following figure illustrates the idea.
Upvotes: 1
Reputation: 30203
You could create the patch using git diff
and then apply it using the patch
utility, which allows you to specify the file you want to apply the diff to.
For example:
cd first-repo
git diff HEAD^ -- hello.test > ~/patch_file
cd ../second-repo
patch -p1 blue/red/hi.test ~/patch_file
Upvotes: 155