CraftQuest Articles

Tutorials, tips, and thoughts on Craft CMS, modern web development, and more.

Image

Creating and Applying Patch Files in Git

Posted on Aug 23, 2016 by Ryan Irelan

Reading time: 2 minutes, 38 seconds

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 <ryan@mijingo.com>
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 <ryan@mijingo.com>
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 <ryan@mijingo.com>
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 <ryan@mijingo.com>
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 <ryan@mijingo.com>
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 <ryan@mijingo.com>
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.

© 2018 Mijingo, LLC