Create Git patches for two files across several renames
Asked Answered
P

3

8

I want to move two files from one repository to another. The files were originally added as:

  1. /src/init/Price.cs
  2. /tests/init/PriceTests.cs

The two files were later renamed to:

  1. /src/init/PriceValue.cs
  2. /tests/init/PriceValueTests.cs

And then moved to:

  1. /src/moved/PriceValue.cs
  2. /tests/moved/PriceValueTests.cs

I've tried to go by this description to create a set of patches for these files, but I'm unsure how to pass in the six different paths the files have existed on.

I've managed to find all the commit IDs affecting PriceValue.cs (across renames and moves), but passing those IDs to Git fails with the following error message:

$ git format-patch -o /tmp/pricevaluepatches $(git log --all dfeeb 6966b 9f882 …)
-bash: /usr/local/bin/git: Argument list too long

So, how do I create a set of patches for this that only contains the changes to the mentioned files, but contains it across one rename and one move of each file?

Polariscope answered 27/4, 2017 at 11:25 Comment(7)
Have you tried putting all commit IDs in a file ids.txt (one per line) and running cat ids.txt | xargs git format-patch -o /tmp/pricevaluepatches?Wongawonga
Also, you don't have to run git log --all ... in your command. A simple git format-patch -o /tmp/pricevaluepatches dfeeb 6966b 9f882 … should suffice.Wongawonga
@NilsWerner, thanks for the suggestions. That only fixes half the problem, though. From the commits recognised by the IDs I have, how do I apply only the changes that affect PriceValue.cs and PriceValueTests.cs across the rename and the move?Execration
As comments say below format-patch can not preserve all history. If anyone is interested, there is an easier way to preserve history and move files to another repo: Merge this repo into that other repo. That means all the history is there, no need to juggle individual commits.Spindlelegs
@JanZerebecki, won't that make a hot mess, including all changes made to all other files as well? Or did you mean doing a --filter-branch on the source repository before merging it with the target?Execration
Yes, if you don't filter it, it will include the history of all files. However by adding a new commit that deletes all other files, before the merge, it shouldn't bother anyone. If you do this more than once, possibly also in the other direction, not filtering makes the history stay cleaner as the common history only occurs once. However a linearised view as opposed to the graph view probably becomes more confusing as the number of unrelated commits gets greater.Spindlelegs
@JanZerebecki: Although the bounty has been awarded to Nils Werner because I've been too swamped to actually test and verify a solution, would you mind writing up your own answer on the steps I need to perform to accomplish this? If your answer works, I'll accept it as the solution.Execration
W
6

You can get the patches of a few specific files but not earlier than commit sha1 using

git format-patch sha1 -- file1 file2 ...

Any of the files can be an old file (prior to a rename) or an existing file.

If you want all commits until commit sha1 you can use

git format-patch --root sha1 -- file1 file2 ...

So in your case, all commits until now (HEAD) of your six files:

git format-patch --root HEAD -- /src/init/Price.cs /src/init/PriceValue.cs /src/moved/PriceValue.cs /src/init/PriceTests.cs /src/init/PriceValueTests.cs /src/moved/PriceValueTests.cs
Wongawonga answered 3/5, 2017 at 5:28 Comment(7)
So I would need to know all commit IDs affecting all the files at all locations first?Execration
No, just the first one you want. If you want all commits of those files, just enter origin as the sha1.Wongawonga
@AsbjørnUlsberg: format-patch will generate a sequence of patches, which will allow to recreate one linear history. It will not allow you to "transplant" the history of a branch with merges -- one individual patch does not contain the information about its parents.Parasang
@AsbjørnUlsberg: so the input of format-patch is geared towards "describe a single linear history" : git format-patch [hash] will take the sequence of commits from [hash] up to your current commit, git format-patch a..b will take the sequence of commits from a to b. It will try to interpret other input as well, but the results will probably not match what you expect.Parasang
origin is the last commit of upstream, not the first, so if you want the first you need to look it up with e.g. git log --oneline |tail -1.Spindlelegs
Yes I want the patches to be linear in terms of these files and I also want the patches to contain every commit in the entire history of the repository affecting just these files. I think I've found the initial commit adding the file, so I should just provide it then. I'll test and report back.Execration
Ah, I misread the docs. But you can try git format-patch --root HEAD ...Wongawonga
B
1

Hmm..... Assuming I want to keep the patch files the way they are, what I would do is get the patch file applied on a branch so that I can then cherry-pick it on the right branch.

So, suppose I have on my master branch a file named /tests/moved/PriceValueTests.cs and I want to apply on it a patch that has the name /tests/init/PriceTests.cs instead. Assuming I don't want to hack the patch file, what I would do is:

  • create a temporary branch from my master
  • checkout temp branch
  • rename the file to the same path that the patch file has (and commit, of course)
  • apply patch file on temporary branch (should work now that the file path has a matching file)
  • commit on temporary branch
  • checkout master
  • cherry-pick last revision from temporary branch

This is so that git can track the name change and be able to apply it successfully. I've done it a number of times and git's file rename algorithm tends to get it right.

Biphenyl answered 2/5, 2017 at 18:45 Comment(1)
This answers something else but not this question. The question doesn't have files that need to be patched, only ones moved across from one repository to another.Spindlelegs
S
1

To achieve the goal from the question via a merge (instead of individual patch moving via format-patch as the question asked) one can remove all other files in a new commit and then merge that commit across repositories into the target repository (adapted from https://mcmap.net/q/13528/-how-do-you-merge-two-git-repositories ):

cd path/to/project-a
git checkout -b for-b master # or whichever branch you want to merge from
git rm -r .
git checkout HEAD -- src/moved/PriceValue.cs tests/moved/PriceValueTests.cs
git commit
cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a
git merge --allow-unrelated-histories project-a/for-b
git remote remove project-a
cd path/to/project-a
git branch -D for-b

This has the advantage that all the history is there, the commit IDs stay the same and there is no need to juggle individual commits nor find them. This may have the disadvantage that a linearised view (like git log) as opposed to the graph view (like gitk) probably becomes more confusing as the number of unrelated commits gets greater.

One could additionally filter repository project-a before the merge, to hide unrelated files or commits. However the downside of that are: If you do this across a repository merge more than once, possibly also in the other direction, it makes the history less clean as the common history only occurs multiple times (once for each merge). This would also make this solution more difficult than your initially tried one. This would also have the downside that the commit IDs don't stay the same and thus it would not be as easy to find which were the same commit across repositories.

Spindlelegs answered 14/5, 2017 at 14:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.