I am trying to move files from one local git repository to another local git repository for a different project while preserving history from the original repository. So far I have this, which is working fine if the file was never moved or renamed in the source repo:
# Executed from a directory in the target repository
( cd $SOURCE_REPOSITORY_DIRECTORY && git format-patch -B -M --stdout --root $SOURCE_FILENAME) | git am --committer-date-is-author-date
This happens to work because the directory structures of the two repositories are the same. If they were different, I'd have to create patch files and fix up the directory names using sed
or something.
Anyway, this is all swell until I hit a file that has been renamed. Even though I'm specifying the -B -M
(and get the same results with -B -M -C --find-copies-harder
) I do not get the patches from before the move, even though the file was cleanly moved (similarity index 100%).
This is particularly odd since git log --follow
shows all the commits and git log --follow -p
provides all the diffs. Except it provides them in reverse order so I cannot feed them into git am
.
Note also that git log --follow -p filename
puts out the following "patch" to show the rename:
diff --git a/old_dir_name/dir1/dir2/filename b/new_dir_name/dir0/dir1/dir2/filename
similarity index 100%
rename from old_dir_name/dir1/dir2/filename
rename to new_dir_name/dir0/dir1/dir2/filename
Now if git log
would display the patches in the right format and right order for git am
to apply them, I could just use that, but such is not the case. Using git log --reverse --follow -p filename
only outputs the name change patch, nothing else.
So, how do I get git format-patch
to really follow renames the way the help file/man page says it should while at the same time only outputting patches for a single file? Alternately, how do I get git log -p
to produce patches in a way I can feed them into git am
to recreate a file with history?
I'm using git version 1.8.4.3.
The first rule takes precedence in the case of a single <commit>. To apply the second rule, i.e., format everything since the beginning of history up until <commit>, use the --root option: git format-patch --root <commit> . If you want to format only <commit> itself, you can do this with git format-patch -1 <commit> .
Just make a new local branch and commit your changes there. You can always delete the branch later if you don't want it anymore, or you can keep the branch and use it for working on whatever you're doing, then merge (or rebase) it into the master branch when it's ready.
The patch file (also called a patch for short) is a text file that consists of a list of differences and is produced by running the related diff program with the original and updated file as arguments. Updating files with patch is often referred to as applying the patch or simply patching the files.
I was recently faced with the same use case as this question and I implemented a solution using Bash, so I wanted to share it as this code could be useful for other people.
It consists of a script git-format-patch-follow
available on
https://github.com/erikmd/git-scripts, which can be used as follows for the OP's question:
( cd "$SOURCE_REPOSITORY_DIRECTORY" && git format-patch-follow -B -M --stdout --root --follow -- "$SOURCE_FILENAME" ) | git am --committer-date-is-author-date
More generally, the syntax is:
git format-patch-follow <options/revisions...> --follow -- <paths...>
This Bash script can thus be viewed as an automated way to run the algorithm outlined by @OldPro, and I took special care to cope with corner cases, such as filenames with whitespace, multiples files passed on the CLI, or running the script from a sub-directory of the Git source repo.
Finally as pointed by this blog post, it suffices to put such a script in one's PATH
for Git to integrate the script like a git subcommand git format-patch-follow
.
Disclaimer: git format-patch
, and thereby git-format-patch-follow
, can't be applied to a non-linear history (involving merge commits).
I've made some progress, but it's much more manual now.
log
with and without --follow
to see which files have been renamed/moved/copied (calling them all "renamed" for simplicity).log
output.format-patch
but give all the old names as well as the current name on the command line.So now I have something like this:
git format-patch -B -M -o /tmp/patches --root -- old_dir_name/dir1/dir2/filename new_dir_name/dir0/dir1/dir2/filename
which creates the patches to create the old file, rename it to the new name, and then continue patching the file. Of course the problem there for me is that the old directory doesn't exit in the new repo and the directory level has changed, so there is still some mucking about to do with getting the directory names to work.
This should be easier....
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