Make patches with Git

Many times it happens, especially when working in code development, that we modify software (eg something as simple as a script or several files from the source code of a program) and we want to share that modification or just save it to have that "differentiation" in the form of a plain text file for later application when the program on which we are based is updated. Well that's the role patches play.

Wikipedia says the following about patches:

A patch is a set of changes to a computer program or its supporting data designed to update, fix, or improve it. This includes fixing security vulnerabilities and other bugs, with such patches usually being called bugfixes or bug fixes, and improving the functionality, usability or performance.

Well there are several methods to create patches, the most used are diff and git diff. In this tutorial the use of git diff will be taught, as it is more complete.

First step: create directories

This is a very important step, that most of the tutorials skip, why will be explained later.

If you look closely, in Git, every time a commit is made a patch is created, and when it shows a modified file the command diff --git a/path/to/file/modified.sh b/path/to/file/modified.sh where modified.sh is, in this case, a script that was modified (.❛ ᴗ ❛.)

So, to modify our script, text or source code, we must first create the directory a and b

$ mkdir a b

In directory a we will put the unmodified file or files, and in directory b the modified one.

Step two: create patch

Run:

$ git diff --no-prefix --no-index --no-renames --binary a b > patched.patch
  • --no-prefix: Do not show any source or destination prefix.
  • --no-index: It is used to compare the two paths given in the file system.
  • --no-remanes: Turn off file renaming detection.
  • --binary: Create a binary diff that can be applied with git apply.

You have your patch ready. Simple, right? Well, now is the time to try it.

Step three: apply patch

Once we have our patch as a .diff or .patch file (although in general any extension can be used), we will apply it with patch or git apply depending on the case.

  1. Plain text only: If your patch only modifies plain text, such as scripts, C/C ++ source files, Python, Pascal, Javascript, PHP, HMTL, etc. then we will use this command:

    $ patch -p1 -i /ruta/del/parche.diff
    
  2. With binary files: That is, things like already compiled executable programs, PNG images, JPEG, Gif, etc. other than plain text. In general you will be able to identify when a binary is patched when in patch it says something like "GIT binary patch". In this case we will apply the patch as follows:

    $ git apply -v /ruta/del/parche.diff
    

The issue with diff and not making directories a and b

Now, going back to what I said earlier about why this is important, it is because in many guides, wikis, etc. I have found that instead of creating these directories, they create a file (eg) script.sh and script.sh.new and then based on that they run diff -u scripts.sh script.sh.new. It turns out that there are two problems in this:

  • By doing that, in the patch instead of saying something like diff --options a/path/to/file/modified.sh b/path/to/file/modified.sh says (in this case) diff --options script.sh script.sh.new, but it turns out that you want to patch b/script.sh, not script.sh.new (because inside b/ are the modified files).

  • If diff is used, when it detects a file that didn't originally exist in a/ (surely because you created one in b/), it won't add it in the patch, and if you deleted one inside the original tree, it won't remove that file either.

  • diff cannot patch binaries.

To better understand it, I will exemplify each case with two examples. In the first one, I will create the files that I put as an example (worth the redundancy) and I will use diff:

script.sh:

1
2
#!/bin/bash
echo "Hello world"

script.sh.new:

1
2
3
#!/bin/sh
echo "Hello world"
echo "This is a patched file :D"

Now we will do what most internet tutorials tell you to do:

$ diff -u script.sh script.sh.new

And it looks like this:

1
2
3
4
5
6
7
--- script.sh   2018-03-16 15:52:49.887087539 -0300
+++ script.sh.new       2018-03-16 15:53:02.490420209 -0300
@@ -1,2 +1,3 @@
-#!/bin/bash
+#!/bin/sh
echo "Hello world"
+echo "This is a patched file :D"

Everything apparently fine, but now let's apply that patch

$ diff -u script.sh script.sh.new | patch -p1 -i /dev/stdin
1
2
3
4
5
6
7
8
can't find file to patch at input line 3
Perhaps you used the wrong -p or --strip option?
The text leading up to this was:
--------------------------
|--- script.sh  2018-03-16 15:52:49.887087539 -0300
|+++ script.sh.new      2018-03-16 15:53:02.490420209 -0300
--------------------------
File to patch:

It fails being that I am in the same directory as script.sh{.new}, so this is fixed using the create directories a/ and b/ hack. However, this does not turn out point 2 and 3. Let's go for it.

Suppose we have this inside a/ and b/:

a: script.sh

b: binary_file.bin script.sh

Okay, now let's make the patch with diff:

$ diff -ur a b
1
2
3
4
5
6
7
8
9
Only in b: binary_file.bin
diff -ur a/script.sh b/script.sh
--- a/script.sh 2018-03-16 15:37:27.513802777 -0300
+++ b/script.sh 2018-03-16 15:41:17.717123987 -0300
@@ -1,2 +1,3 @@
-#!/bin/bash
+#!/bin/sh
echo "Hello world"
+echo "This is a patched file :D"

And what is said in point 2 is true, it does not put the new file, it tells you "Only in b" or if there is a file that is in a/ but not in b/ (that is to say, surely you removed it from your fork), you will get the message "Only in a" instead of deleting or creating it. If we apply this patch it will only affect the plain text files, and even if it did its job and created this new file it would not work because binary_file.bin is a binary, which is not supported by diff but it is supported by git which leads us to third point. See what happens if I use git instead of diff:

$ git diff --no-prefix --no-index --no-renames --binary a b
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
diff --git b/binary_file.bin b/binary_file.bin
new file mode 100644
index 0000000000000000000000000000000000000000..1ce3c1c596d7a7f400b0cc89bda5a41eed2780c5
GIT binary patch
literal 73
pcmd-HXHZUIU{c}EWl|AfLZWk+R0P|Ad@#)bSHb~R0-{lr003gr3L5|b

literal 0
HcmV?d00001

diff --git a/script.sh b/script.sh
index da049c4..3d351f5 100644
--- a/script.sh
+++ b/script.sh
@@ -1,2 +1,3 @@
-#!/bin/bash
+#!/bin/sh
echo "Hello world"
+echo "This is a patched file :D"

Now I did consider the non-existent binary file in a/ but tangible in b/. Note that in this particular case, as I explained earlier, when dealing with binary files that only git supports (see the message "GIT binary patch") you must use git apply. But I recommend using it only when it is mandatory, not always (in general, not many binaries are used in software that is 100% free, unless they are cases such as firmware for the kernel or precompiled libraries, but free software blobbeado It usually has proprietary binaries in its code, although just because it's binary doesn't necessarily mean it's proprietary.)

If you have doubts about the use of diff and git diff or patch and git apply, remember that you can leave them in the comments, as well as read their manpages and consult their web pages for more information.

Post a comment on "Make patches with Git"

You can format your comment with Markdown. Avatar from Libravatar