Up your git game with --patch
Tags: git, productivity, tutorial, beginners
Published: 2018-10-22 16:49:13

---
title: Up your git game with --patch
published: true
description: Easily create clean, single purpose commits by utilizing git's interactive command line menu for staging code changes.
tags: git, productivity, tutorial, #beginner
---

We all strive for clean, single purpose commits with meaningful messages. This can be difficult in practice if you’ve done a lot of debugging since your last commit. Many people I know use `git commit -A` or `git commit .` when developing and maintaining features. This is fine when making small changes, but I don’t typically like to use these options when committing more than a line or two. When I suggest my favorite git add option instead, many people tell me that they’ve never heard of or used it.

# `git add --patch`

Rather than staging all your recent changes, `git add --patch` (or `git add -p`) allows you to stage changes in related hunks. It pops you into an [interactive menu](https://git-scm.com/docs/git-add#_interactive_mode) that allows granular control over staging changes. The interactive interface initially shows you the same hunks of code that `git diff` outputs. As you see each change in the interactive menu, you can choose `y` to stage the hunk, `n` to skip, or `s` to separate the hunk out into even more granular pieces.

## Example

Say I'm starting a new project and I want a basic User class:

```python
class User:
    """User class for my app"""

    def __init__(self, fist_name, last_name, role):
        self.first_name = fist_name
        self.last_name = last_name
        self.role = role
   
    def display_name(self):
        print("{} {}".format(self.fist_name, self.last_name))

    def display_role(self):
        print("User is a {}".format(self.role))
```

After I commit this code and come back to it, I notice there are a few things I want to change.

First, I realize that I've spelled "first" as "fist" a number of times, and that needs to be corrected. Second, I realize that I don't like having "role" be a string variable on my class. I would rather have different user roles be subclasses of User. So I change the file to look like this.

```python
class User:
    """Base User class for my app"""
 
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
   
    def display_name(self):
        print("{} {}".format(self.first_name, self.last_name))
 
 
class Admin(User):
	'Admin level user'
 
	def __init__(self, first_name, last_name, email):
		super(Admin, self).__init__(first_name, last_name)
		self.email = email
 
 
class Guest(User):
	'Guest user'
 
	def display_name(self):
		print("Guest {} {}".format(self.first_name, self.last_name))
```

These are two separate thoughts I want to capture, so I should save these changes in separate commits. Using `git add -p` makes this easy even though the two separate thoughts occur in the same file.

When I type in `git add -p` I see the following.

```bash
diff --git a/user.py b/user.py
index e0bb61a..1b59b32 100644
--- a/user.py
+++ b/user.py
@@ -1,13 +1,24 @@
class User:
-    """User class for my app"""
+    """Base User class for my app"""
 
-    def __init__(self, fist_name, last_name, role):
-        self.first_name = fist_name
+    def __init__(self, first_name, last_name):
+        self.first_name = first_name
         self.last_name = last_name
-        self.role = role
    
     def display_name(self):
-        print("{} {}".format(fist_name, last_name))
+        print("{} {}".format(self.first_name, self.last_name))
 
-    def display_role(self):
-        print "User is a {}".format(role)
+
+class Admin(User):
+	"""Admin level user"""
+
+	def __init__(self, first_name, last_name, email):
+		super(Admin, self).__init__(first_name, last_name)
+		self.email = email
+
+
+class Guest(User):
+	"""Guest user"""
+
+	def display_name(self):
+		print("Guest {} {}".format(self.first_name, self.last_name))
Stage this hunk [y,n,q,a,d,/,s,e,?]?
```

Since my `user.py` file is relatively short, it all appears in one hunk. I can separate this out by choosing the `s` option.

```bash
Stage this hunk [y,n,q,a,d,/,s,e,?]? s
Split into 5 hunks.
@@ -1,3 +1,3 @@
class User:
-    """User class for my app"""
+    """Base User class for my app"""
 
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? n
@@ -3,4 +3,4 @@
 
-    def __init__(self, fist_name, last_name, role):
-        self.first_name = fist_name
+    def __init__(self, first_name, last_name):
+        self.first_name = first_name
         self.last_name = last_name
Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]? e
@@ -6,4 +6,3 @@
         self.last_name = last_name
-        self.role = role
    
     def display_name(self):
Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]? n
@@ -8,4 +7,4 @@
    
     def display_name(self):
-        print("{} {}".format(fist_name, last_name))
+        print("{} {}".format(self.first_name, self.last_name))
 
 
Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]? y
@@ -11,3 +10,15 @@
 
-    def display_role(self):
-        print("User is a {}".format(role))
+
+class Admin(User):
+	"""Admin level user"""
+
+	def __init__(self, first_name, last_name, email):
+		super(Admin, self).__init__(first_name, last_name)
+		self.email = email
+
+
+class Guest(User):
+	"""Guest user"""
+
+	def display_name(self):
+		print("Guest {} {}".format(self.first_name, self.last_name))
 
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n
```
```bash
git commit -m "Fix typos: 'fist' -> 'first'"
[master f91e0eb] Fix typos: 'fist' -> 'first'
 1 file changed, 3 insertions(+), 3 deletions(-)
```

By splitting the diff of this file into 5 distinct hunks, I was able to choose only the changes that related to fixing the `fist` -> `first` typo without impacting the functionality of the code. 

One tricky bit you might notice is that there are two separate changes in the `User.__init__()` method that I don't want in the same commit. The first is to correct the `fist -> first` typo. The second is to remove the `role` parameter from the function declaration.  Even using `s` to hunk out the changes won't solve this problem, since the changes are on the same line. 

You'll notice that instead of `y`, `n`, or `s`, I used the `e` option. The `e` option pops you into your default terminal editor (probably vim unless you've changed it to something else), where you can manually edit the file to reflect the changes you want to stage. Save the file and exit, and you can see the changes that are staged for commit with `git diff --cached`.

In this instance, I modified this hunk

```bash
-    def __init__(self, fist_name, last_name, role):
-        self.first_name = fist_name
+    def __init__(self, first_name, last_name):
+        self.first_name = first_name
```

to look like this before committing

```bash
- def __init__(self, fist_name, last_name, role):
-     self.first_name = fist_name
+ def __init__(self, first_name, last_name, role):
+     self.first_name = first_name
```

I can use `git add -p` again to review the changes for the next commit, or I can use `git add user.py` since I know I'm staging all of the remaining changes in the file anyway. For the sake of finishing out the example, I'll use `git add -p` again.

```bash
diff --git a/user.py b/user.py
index 2a09e35..1b59b32 100644
--- a/user.py
+++ b/user.py
@@ -1,13 +1,24 @@
class User:
-    """User class for my app"""
+    """Base User class for my app"""

-     def __init__(self, first_name, last_name, role):
+     def __init__(self, first_name, last_name):
         self.first_name = first_name
         self.last_name = last_name
-        self.role = role
    
     def display_name(self):
         print("{} {}".format(self.first_name, self.last_name))
 
-    def display_role(self):
-        print("User is a {}".format(role))
+
+class Admin(User):
+	"""Admin level user"""
+
+	def __init__(self, first_name, last_name, email):
+		super(Admin, self).__init__(first_name, last_name)
+		self.email = email
+
+
+class Guest(User):
+	"""Guest user"""
+
+	def display_name(self):
+		print("Guest {} {}".format(self.first_name, self.last_name))
Stage this hunk [y,n,q,a,d,/,s,e,?]? y
```

```bash
git commit -m "Break user roles out into different classes."
[master dea4c79] Break user roles out into different classes.
 1 file changed, 16 insertions(+), 5 deletions(-) 
```

When I look back at my `git log` I see two separate, concise commits. If I decide I need to revert the commit that breaks out the `User.role` into `Admin` and `Guest` classes, I won't lose the typo fixes in the process.

I use this option to stage all of my changes for commit, even if they are only one or two lines. It helps me catch typos. I never have to worry about mixing typo fixes or de-linting with important functionality changes. It also helps me keep my ideas separate, even if I developed those ideas at the same time.