On-the-floor

On the Floor

Applying / Merging Changes From One Git Repository To Another

This tutorial demonstrates how to apply changes made to a project hosted in one git repository onto a completely separate project in a different git repository: essentially, merging changes between two totally separate git repositories. If you don't use git, this will likely not interest you much.

Consider two projects that are both managed with git that do not share a commit history of any kind, i.e., one is not a branch of the other. It may be the case, however, that in real life they have a common ancestry.

For example, if you have a project in git that you copy into a new git repository, you will end up with two projects that as far as git is concerned are totally separate. However at the moment of copying they are identical to one another, and over time still share many similarities.

When changes are made in one repository, you may want an easy way to transfer them from that repository to the other, similarly to the way in which you merge changes from one branch to another. You can do that with patches.

We'll call the first project the "source" and the second project the "target". The source project is the one in which there are changes you also want in the target project.

Start by going to your source project's directory. Use:

git log

To find the commit you want to create a patch since. Use the commit that is the one immediately before the first one you want included in the patch (i.e. the commit you choose is not included in the patch). For instance, if your log looks like this (abbreviated):

commit 29cebfa985b3621b79b6d462393a91b97fea14bb 
Author: Adrian Duyzer <adrian@Macbook-Pro.local>
Date: Thu May 20 00:00:38 2010 -0400
fix issue with levels

commit 8a5cfe37cdc838c055e38a4783c724996a71d15f
Author: Adrian Duyzer <adrian@Macbook-Pro.local>
Date: Wed May 19 23:27:27 2010 -0400
freezes and thaws

commit 5ebe34bcb343a272ffda331db2f30dfffa52240f
Author: Adrian Duyzer <adrian@Macbook-Pro.local>
Date: Wed May 19 16:15:34 2010 -0400
notes, date report error fix

And you want a patch for the commits "freezes and thaws" and "fix issue with levels", you will want to start with commit 5ebe34bcb343a272ffda331db2f30dfffa52240f, which is the one right before the two that we want.

In this case, we're dealing with a different series of log messages, where the commit hash immediately before the changes we want is actually 34382279d69e676e7739c9c225ada307a864288b. Using that commit hash we now proceed to create a series of patches like so:

git format-patch 34382279d69e676e7739c9c225ada307a864288b 

This will create a series of patches that are each named according to the particular commit that was created, e.g. like:

0001-removed-forgery-protection-from-application-controll.patch 
0002-cleaning-up-updating-spawn.patch
0003-new-version-of-spawn.patch
0004-dispatches-are-now-sent-in-the-background-via-spawn.patch
0005-new-help-button.patch 0006-minor-help-text.patch

Each of these files contains the information needed to apply your changes to the other project. They are not difficult to read and in fact, you can make changes to them if you want (incidentally, Textmate will also automatically syntax-highlight the patch files and I'm sure it's not the only IDE that does so).

Now, copy these files to the folder where your target project lives, then cd to that folder.

For each of the patch files you want to:

  1. Check what files are affected by the patch
  2. See if you can apply the patch cleanly
  3. If you can, apply it
  4. If you cannot, you may need to edit the patch to apply it cleanly.

To check what files are affected:

 git apply --stat 0005-new-help-button.patch 

To see if you can apply the patch cleanly:

 git apply --check 0005-new-help-button.patch  

If nothing is returned, you can apply the patch with:

 git apply 0005-new-help-button.patch  

Otherwise, you'll get something like this:

error: patch failed: app/models/mailer.rb:14 
error: app/models/mailer.rb: patch does not apply
error: patch failed: config/forge_config.yml:1
error: config/forge_config.yml: patch does not apply
error: patch failed: config/routes.rb:33
error: config/routes.rb: patch does not apply

If you have errors, the entire patch will fail to apply. However, if you want to apply just the parts that will work, you can do so with the --reject flag:

git apply --reject 0004-dispatches-are-now-sent-in-the-background-via-spawn.patch 

This will create files with a .rej extension in the same folder as the files that it failed to patch. You can find these with:

find . -name *.rej 

Take a look at those files to figure out how to proceed. Alternatively, just look at the output of git when it complains about stuff. It should be fairly easy to make the changes you want manually. Once you're done making those changes, delete the .rej files.

You can also manually edit the patch files as stated above.

When I did this, although I'm not sure if this is a best practice or not, each time I successfully applied a patch (or made the adjustments manually), I committed my changes and then moved on to the next patch. That way, if a patch applied really messily, I had an easy way of determining which files were changed or added (git status) and could revert if needed.

Incidentally, the more atomic your commits are, the easier this process is, because you can skip patches that you don't want. For example, you will often get rejected patches for things like configuration file changes, but if those were committed separately, it would not be a problem as you could just skip them. Yet another reason to create clean commits (with sensible commit messages!)

When you're done, delete your .patch files. You've successfully patched your app!

Final tip: you can also create a single patch with all changes in it. If you use the --reject flag when applying it, this is probably the easiest and fastest way to patch things. Do this like so:

 git format-patch REVISION --stdout > PATCH_NAME.patch 

Comments

On May 28 2010 at 4:07PM e40 said:

I do this all the time:

git fetch ssh://user@gerrit.foo.com:29418/reponame refs/changes/70/270/3 && git cherry-pick FETCH_HEAD

Yes, it's an example that uses Gerrit, but it applies to git in general.

Add a Comment

Your Comment:

Your Name:

Your Email Address: (Won't be published)

Your Website: (If you've got one)