Creating and Applying Patch Files in Git

How to create a patch file for a set of changes in Git and apply it elsewhere.

Image

In a pre­vi­ous arti­cle, I talked about how to use git-cher­ry-pick to pluck a com­mit out of a repos­i­to­ry branch and apply it to anoth­er branch.

It’s a very handy tool to grab just what you need with­out pulling in a bunch of changes you don’t need or, more impor­tant­ly, don’t want.

This time the sit­u­a­tion is the same. We have a com­mit we want to pull out of a branch and apply to a dif­fer­ent branch. But our solu­tion will be different. 

Instead of using git-cherry-pick we will cre­ate a patch file con­tain­ing the changes and then import it. Git will replay the com­mit and add the changes to the repos­i­to­ry as a new commit.

What is git-fomat-patch?

git-format-patch exports the com­mits as patch files, which can then be applied to anoth­er branch or cloned repos­i­to­ry. The patch files rep­re­sent a sin­gle com­mit and Git replays that com­mit when you import the patch file. 

git-format-patch is the first step in a short process to get changes from one copy of a repos­i­to­ry to anoth­er. The old style process, when Git was used local­ly only with­out a remote repos­i­to­ry, was to email the patch­es to each oth­er. This is handy if you only need to get some­one a sin­gle com­mit with­out the need to merge branch­es and the over­head that goes with that.

The oth­er step you have to take is to import the patch. There are a cou­ple options for that but we’ll use the sim­plest one available.

Let’s cre­ate our patch file.

Using git-for­mat-patch

I am on the repos­i­to­ry the-commits, which is the sam­ple repos­i­to­ry I used in my Git ver­sion con­trol cours­es. I have the experimental_features branch checked out.

This experimental_features branch has an impor­tant change in it that I want to bring to a fea­ture branch I have going. This fea­ture branch is going to be merged into the devel­op­ment branch (and even­tu­al­ly the mas­ter branch) so I only want to include non-exper­i­men­tal changes. Because of that I don’t want to do a merge because I’d like to not pull in the oth­er fea­tures that are half-baked and would mess up my pro­duc­tion-path branches.

Here’s the lat­est when I run git-log:

$ git log
commit 4c7d6765ed243b1dbb11d8ca9a28548561e1e2ef
Author: Ryan Irelan <[email protected]>
Date:   Wed Aug 24 08:08:59 2016 -0500

another experimental change that I don't want to allow out of this branch

commit 1ecb5853f53ef0a75a633ffef6c67efdea3560c4
Author: Ryan Irelan <[email protected]>
Date:   Mon Aug 22 12:25:10 2016 -0500

a nice change that i'd like to include on production

commit 4f33fb16f5155165e72b593a937c5482227d1041
Author: Ryan Irelan <[email protected]>
Date:   Mon Aug 22 12:23:54 2016 -0500

really messed up the content and markup and you really don't want to apply this commit to a production branch

commit e7d90143d157c2d672276a75fd2b87e9172bd135
Author: Ryan Irelan <[email protected]>
Date:   Mon Aug 22 12:21:33 2016 -0500

rolled out new alpha feature to test how comments work

The com­mit with the hash 1ecb5853f53ef0a75a633ffef6c67efdea3560c4 is the one I’d like to pull into my fea­ture branch via a patch file.

We do that using the com­mand git-format-patch. Here’s the command:

$ git format-patch a_big_feature_branch -o patches

We pass in the branch with which we want Git to com­pare against to cre­ate the patch files. Any com­mits that are in our cur­rent branch (experimental_features) but not in the a_big_feature_branch will be export­ed as patch files. One patch file per com­mit. We used the -o flag to spec­i­fy the direc­to­ry where we want those patch­es saved. If we leave that off, Git will save them to the cur­rent work­ing directory.

When we run it we get this:

$ git format-patch a_big_feature_branch
patches/0001-rolled-out-new-alpha-feature-to-test-how-comments-wo.patch
patches/0002-really-messed-up-the-content-and-markup-and-you-real.patch
patches/0003-a-nice-change-that-i-d-like-to-include-on-production.patch
patches/0004-another-experimental-change-that-I-don-t-want-to-all.patch

Those four patch files (named sequen­tial­ly and with a hyphen­at­ed ver­sion of the com­mit mes­sage excerpt) are the com­mits that are in the cur­rent branch but not the a_big_feature_branch.

Let’s look at the guts of one of them.

From 4c7d6765ed243b1dbb11d8ca9a28548561e1e2ef Mon Sep 17 00:00:00 2001
From: Ryan Irelan <[email protected]>
Date: Wed, 24 Aug 2016 08:08:59 -0500
Subject: [PATCH 4/4] another experimental change that I don't want to allow out of this branch

---
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/index.html b/index.html
index f92d848..46e4eb2 100644
--- a/index.html
+++ b/index.html
@@ -9,7 +9,7 @@
   &lt;!-- Set the viewport width to device width for mobile --&gt;
   &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot; /&gt;
 
 &lt;title&gt;Little Git &amp;amp; The Commits&lt;/title&gt;
 &lt;title&gt;Little Git &amp;amp; The Commits FEATURING ELVIS BACK FROM THE DEAD&lt;/title&gt;
 
   &lt;!-- Included CSS Files (Uncompressed) --&gt;
-- 
2.7.4 (Apple Git-66)

It looks like an email, doesn’t it? That is because all patch files are for­mat­ted to look like the UNIX mail­box for­mat. The body of the email is the diff that shows which files have changed (in our case just index.html) and what those changes are. Using this file, Git will recre­ate the com­mit in our oth­er branch.

Spec­i­fy­ing a Sin­gle Commit

In this sit­u­a­tion, I don’t need all of those patch files. All but one are com­mits I don’t want in my tar­get branch. Let’s improve the git-format-patch com­mand so it only cre­ates a patch for the one com­mit we do want to apply.

Look­ing back at the log, I know that the com­mit I want to apply has the hash of 1ecb5853f53ef0a75a633ffef6c67efdea3560c4. We include that hash as an argu­ment in the com­mand, but pre­cede it with a -1 so Git only for­mats the com­mit we spec­i­fy (instead of the entire his­to­ry since that commit).

$ git format-patch a_big_feature_branch -1 1ecb5853f53ef0a75a633ffef6c67efdea3560c4 -o patches 
  outgoing/0001-a-nice-change-that-i-d-like-to-include-on-production.patch

Now we get a sin­gle patch file, which is much safer because there’s no change we’ll acci­den­tal­ly apply patch­es of changes we don’t want!

We have the patch file, now how do we apply it to our branch?Using git-am.

What is git-am?

git-am is a com­mand that allows you to apply patch­es to the cur­rent branch. The am stands for apply (from a) mail­box” because it was cre­at­ed to apply emailed patch­es. The handy thing about git-am is that it applies the patch as a com­mit so we don’t have to do any­thing after run­ning the com­mand (no git-add, git-commit etc.).

The name git-am is a lit­tle strange in the con­text of how we’re using it but fear not: the result is exact­ly what we want.

Let’s apply a patch and see how it works.

Using git-am

The first thing we need to is switch over to our tar­get branch. For this exam­ple we’ll move to the branch we com­pared against in the git-format-patch command.

$ git checkout a_big_feature_branch

After that we’re ready to apply the patch file with the com­mit we want to include. 

Note: I’m work­ing in the same repos­i­to­ry on the same com­put­er. When I switch branch­es, the patch file comes with me because it is still an untracked file. If I staged and com­mit­ted the patch file then I’d need to find anoth­er way to make it acces­si­ble. You could do this by mov­ing the patch file out of your repos­i­to­ry to where you can access it when on the des­ti­na­tion branch.

Because we refined the git-format-patch we only have one patch file in the patch­es directory:

patches/0001-a-nice-change-that-i-d-like-to-include-on-production.patch

To apply the patch to the cur­rent branch, we use git-am and pass in the name of the patch we want to apply.

$ git am patches/0001-a-nice-change-that-i-d-like-to-include-on-production.patch

And the we get con­fir­ma­tion that the patch was suc­cess­ful­ly applied:

Applying: a nice change that i'd like to include on production

Look­ing at the log now we see our change is replayed as a com­mit in the cur­rent branch:

$ git log
commit 69bb7eb757b2356e365934fdbea744877c3092bb
Author: Ryan Irelan <[email protected]>
Date:   Mon Aug 22 12:25:10 2016 -0500

a nice change that i'd like to include on production

And now our change is there! 

Note that the new com­mit has a dif­fer­ent hash because it is part of a dif­fer­ent work­ing tree than the one we for­mat­ted as a patch.