I am trying to move files from one local git repository to another local git repository for a different project while preserving history fr
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....
Yuck. I think the problem is some broken logic. In particular, when you combine --reverse
and --follow
you must specify the old file name:
[rename foo to bar]
$ git log --follow bar # works
$ git log --follow --reverse -- foo # requires "--" because foo is not in HEAD
This ... sort of works. Except, it then treats the file as deleted when it hits the rename, and everything stops there.
tree-diff.c
contains this function:
static void try_to_follow_renames(...)
{
...
/* Remove the file creation entry from the diff queue, and remember it */
choice = q->queue[0];
q->nr = 0;
which is called if diff_might_be_rename
returns true:
static inline int diff_might_be_rename(void)
{
return diff_queued_diff.nr == 1 &&
!DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
}
...
int diff_tree_sha1(...)
{
...
if (!*base && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) {
I'm making some large assumptions here, but when you go in the other order, instead of "file bar
was just created, let's see if we can find a foo
from which it was renamed", if the log is reversed you need to have "file foo
deleted, let's see if we can find a bar
to which it was renamed", and that's just ... missing, if the comment is accurate.
If you have a lot of these to do, I'd suggest attempting to add something here to remember if the diff is reversed (as it is for both format-patch
and log --reverse
) and change the diff_might_be_rename()
and try_to_follow_renames()
code as needed.
If you just have one, well, manually hacking up some diffs is probably easier. :-)
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).