Friday, March 08, 2013

Formatting git patches for partially transplanting a repository

So I wanted to move a subdirectory inside a git repository into its own repo, keeping all the history of the changes in the original repository in the new one. With git, copying partial history around is as easy as using git format-patch --root -o directory/to/put/patches/in -- path/to/subdirectory, which will create a numbered patch file for every commit that touched the subdirectory in question. Applying all the patches in the new repository is just a questions of doing git am -3 *.patch.

The problem is, format-patch skips merge commits, which means that there might be missing changes in the patches, which sorta makes things not work.

The alternative way is then to do git log --pretty=email, which outputs a commit in the same format and actually handles merge commits properly. But, of course, I need to do that for every commit that I want to export (and there's a bunch), and I hate doing things by hand.

To that effect, here's a few lines that do the job properly, exporting a list of commit hashes in the proper order and then going through them one by one and exporting each one to a directory, numbered appropriately so they're correctly sorted:


Export the list of interesting commits in the correct order (older to newer)

git log --oneline --reverse -- path/to/subdirectory|cut -d' ' -f1>../patches/list

Create a patch file for each commit on the list

c=`wc -l ../patches/list|cut -d' ' -f6`;for j in $(eval echo {1..$c});do n=`printf "%04d\n" $j` && a=`head -n $j ../patches/list|tail -1` && git log -p --pretty=email --stat -m --first-parent $a~1..$a -- path/to/subdirectory >../patches/new/$n-$a.patch;done

Apply all the patches in the new git repository

git am -3 -p3 ../patches/new/*.patch

The subdirectory I'm taking things out of is 3 levels deep in the original repository, and I don't want to keep the parent directories, so I'm passing -p3 to have git (and the patching process) remove them when applying.

If git am fails to apply a patch, it's very likely that this patch is a merge commit with changes that are already applied. I can check this by doing patch -p3 .git/rebase-apply/##, where ## is the failed patch number reported by git am. Patch will either apply the change or report that a change has already been applied (and do I want to revert it? just say no). If any changes needed applying with patch, I can then add the changed files with git add and do git am --resolved, which will create the commit and continue applying the patches. If there are no changes to be applied, I can just skip it with git am --skip (which is most likely to happen) and continue.

3 comments :

Måns Rullgård said...

git filter-branch is the command you are looking for.

Andreia Gaita said...

@Måns
Interesting, thanks!

Anonymous said...

Thanks, this helped!