6 min read

On Patching

One of the simple pleasures in life is patching code. Most days, it’s done using Git, but sometimes I still do it the old-fashioned way. In fact, I used the our old friend patch just the other day. Weeeeeeeeeeeeeeeeeeeeeeeeee

I’ll be using “patch” interchangeably as both the patch tool and as the set of changes provided by a diff tool.

If using Git, the simplest way to produce a patch is to use the git-diff tool:

$ git diff
$ git diff foo.c
$ git diff foo.c > foo.patch

Also, I’ll use the git-show tool to see the differences that made up the last commit:

$ git show

Of course, we don’t need to email patches to project maintainers like they used to in the bad old days. Now, since everyone used distributed systems, that method of communicating patches has been obsoleted, right? All I need to know is git-pull and git-push and where to click on GitHub!

Um, no. So, even if your little JavaScript team doesn’t feel the need to distribute patches, that doesn’t mean that others aren’t doing it. Yes, there are even Git tools for that (git-format-patch, git-am).

Anyway, let’s look at a quick example of using diff and patch to create and apply a patch, respectively, like they did back in the 16th century.

A Quick One, While He’s Away

Here’s a nice program:

foo.c

#include <stdio.h>

void main(int argc, char **argv) {
    printf("I'm a little foo, hear me roar\n");
}

And it’s second version:

foo2.c

#include <stdio.h>

void main(int argc, char **argv) {
    char *name = "foo";
    printf("I'm a little %s, hear me roar\n", name);
}

The diff

Now, let’s print the difference using a normal diff:

$ diff foo.c foo2.c

4c4,5
<     printf("I'm a little foo, hear me roar\n");
---
>     char *name = "foo";
>     printf("I'm a little %s, hear me roar\n", name);

The first line refers to the prescriptive changes that must be made to the left file to make it look like the right. The numbers on the right of the letter c refer to the left file and the numbers (actually, it’s a range) on the right to the right file. It can be interpreted thusly:

“Line 4 in the left file must be changed to match lines 4 and 5 in the right file.”

Does this look familiar? ed commands!

There are three different actions, depending upon the changes to the file:

  • c = change
  • a = add
  • d = delete

Then, diff shows us the changes in the files (separated by dashes ---). The < symbol denotes the changes that happened to the file on the left (the order of the files given to the diff command matters) and ‘>’ denotes the file on the right.

This is great, but let’s save the diff using a different format. We’ll output what is known as a unified diff, which simply means that it prints the differences as a single chunk of text, instead of distinct chunks, which is what what saw from a normal diff.

Let’s see the difference by printing a unified diff:

$ diff -u projects/sandbox/foo.c projects/sandbox/foo2.c

--- projects/sandbox/foo.c       2019-07-07 12:52:54.676192440 -0400
+++ projects/sandbox/foo2.c      2019-07-07 12:55:21.155537240 -0400
@@ -1,6 +1,7 @@
 #include <stdio.h>
 
 void main(int argc, char **argv) {
-    printf("I'm a little foo, hear me roar\n");
+    char *name = "foo";
+    printf("I'm a little %s, hear me roar\n", name);
 }

Here, we see that the differences have been combined with some extra instructions at the top for the patch tool. The @@ -1,6 +1,7 @@ stipulates that diff shows the lines one through six of the file on the left and lines one through seven on the right.

patch may not replace the hunk at exactly those lines. It will do its best, but the applied patch lines may differ. From the man page:

With context diffs, and to a lesser extent with normal diffs, patch can detect
when the line numbers mentioned in the patch are incorrect, and attempts to find
the correct place to apply each hunk of the patch.  As a first guess, it takes
the line number mentioned for the hunk, plus or minus any offset used in applying
the previous hunk.  If that is not the correct place, patch scans both forwards
and backwards for a set of lines matching the context given in the hunk.

If it can’t find a correct place to patch, it will give up with a rejection file.

Now, let’s save that patch and send it to our friend Chester:

$ diff -u projects/sandbox/foo.c foo2.c > foo.patch

Note, there are other formats outputted by the diff command that are interesting and worthwhile to see.

For instance, I didn’t talk about the -e flag to the diff command. It’s so cool I can barely contain myself. See the link in the References section for more information (and the patch man page, of course, which was already linked in the above article).

The patch

Ok, Chester has received our patch file, and now he’ll happily apply it to his source file. First, he puts on his special merge hat, the one with the propellers, and then he gets down to business.

One of the most important things to know when patching a source file(s) is where in the file system the patch was made in relation to Chester’s directory structure. If the path of the file(s) in the patch file doesn’t match Chester’s directory structure, he can use the -pN flag to strip off path prefixes.

I’ll show examples from the man page to illustrate the point:

For example, supposing the file name in the patch file was

          /u/howard/src/blurfl/blurfl.c

setting -p0 gives the entire file name unmodified, -p1 gives

          u/howard/src/blurfl/blurfl.c

without the leading slash, -p4 gives

          blurfl/blurfl.c

and not specifying -p at all just gives you blurfl.c.

Patch will try to determine the type of the diff when it starts up, but this of course can be set explicitly on the command line, i.e., -u for a unified diff.

Ok, let’s finally have Chester apply that bad boy. He does indeed have a foo.c file, but it’s the directory derp/, which of course differs from the path in the patch file.

Chester enters the following commands:

$ cd derp
$ patch -p2 < foo.patch
patching file foo.c

And all is right with the world!

More patch fun

Here are some more sweet things you can do with patch:

  • Create a backup of the file when patching by suppling the -b or --backup switch.

    • This will backup the original file with the .orig extension.
  • Create a versioned number of the file by giving the -V switch.

    • Must be used with the backup flag.
    • Will create sequentially-versioned files with the .~N~ extension.
  • Revert the applied patch with the -R or --reverse switch.

    $ patch -p2 < foo.patch
    $ patch -R < foo.patch
    
  • Run the command without actually applying the patch with the --dry-run switch.

References