Working With Patches in Git

git guide

As software developers, we often make local code changes for debugging, logging, and more. Oftentimes we are unable to commit these changes to the master code as they may make the application run slower, or even render it unusable for certain use cases. For lots of people that means the hacks have to be re-added every time they wish to debug something. This is time consuming and error-prone; fortunately, git contains all the tools needed to solve this problem.

Let’s start by creating a new repository for adder, a simple tool that adds two numbers:

git init adder

Now let’s add main.py the file containing the full source of our application:

#!/usr/bin/env python

import os
import sys

def add(a, b):
    return a + b

def main(args):
    try:
        sum = add(int(args[0]), int(args[1]))
        print(f'the sum is {sum}')
    except:
        print('invalid arguments')
        sys.exit(2)

if __name__ == '__main__':
    main(sys.argv[1:])

Next, we stage, and commit the change:

git add main.py
git commit -m "Add application to add numbers"

Now let’s pretend that there is some sort of regression and we must add logging to investigate the issue. First, we need to add our custom logging library located in a new file, logging.py:

def info(msg):
    print(msg)

We must also update the existing main.py file to call into this library:

#!/usr/bin/env python

import os
import sys
import logging

def add(a, b):
    logging.info(f'adding numbers {a} and {b}')
    return a + b

def main(args):
    try:
        sum = add(int(args[0]), int(args[1]))
        print(f'the sum is {sum}')
    except:
        print('invalid arguments')
        sys.exit(2)

if __name__ == '__main__':
    main(sys.argv[1:])

With our local changes complete, let’s review the differences against the master branch.

git diff

The output of the above command is a valid patch file in of itself:

index 7efa5d6..d3aadba 100755
--- a/main.py
+++ b/main.py
@@ -2,8 +2,10 @@
 
 import os
 import sys
+import logging
 
 def add(a, b):
+    logging.info(f'adding numbers {a} and {b}')
     return a + b
 
 def main(args):

While the changes to main.py are shown, the contents of the newly added logging.py file are not. To fix this, simply add it with:

git add --intent-to-add logging.py

With this, both files are now correctly shown as changes:

diff --git a/logging.py b/logging.py
new file mode 100644
index 0000000..39b4bbc
--- /dev/null
+++ b/logging.py
@@ -0,0 +1,2 @@
+def info(msg):
+    print(msg)
diff --git a/main.py b/main.py
index 7efa5d6..d3aadba 100755
--- a/main.py
+++ b/main.py
@@ -2,8 +2,10 @@
 
 import os
 import sys
+import logging
 
 def add(a, b):
+    logging.info(f'adding numbers {a} and {b}')
     return a + b
 
 def main(args):

With the changes looking good, it’s time to generate the patch file:

git diff --output ~/logging.patch

Now that we have performing the required debugging and saved the day, we can revert all of our local debugging changes:

git reset --hard

Let’s now pretend that some time has passed and we must debug the add function once again. Furthermore, as a result of feature creep, the adder tool now also has to handle subtraction. The updated main.py file is shown below:

import os
import sys

def add(a, b):
    return a + b

def main(args):
    try:
        op = args[0]
        a = int(args[1])
        b = int(args[2])
        ops = {'+': add, '-': subtract}
        result = ops[op](a, b)
        print(f'the result is {result}')

    except:
        print('invalid arguments')
        sys.exit(2)

def subtract(a, b):
    return a - b

if __name__ == '__main__':
    main(sys.argv[1:])

As there are no conflicts in the modified chunks, we can now apply the patch:

git apply ~/logging.patch

We can see that the logging.py file is created and main.py has been updated with the logging from before:

import os
import sys

def add(a, b):
    return a + b

def main(args):
    try:
        op = args[0]
        a = int(args[1])
        b = int(args[2])
        ops = {'+': add, '-': subtract}
        result = ops[op](a, b)
        print(f'the result is {result}')

    except:
        print('invalid arguments')
        sys.exit(2)

def subtract(a, b):
    return a - b

if __name__ == '__main__':
    main(sys.argv[1:])

If you do run into conflicts, you can use the reject flag to apply as many changes as possible, without failing the entire operation.