Git Stash — way to safely split your commits

Git Stash — way to safely split your commits

A junior dev in my team was regularly committing 15 to 20+ files in one go, with useless messages like “added files”, “changed files” or even better “change 1” etc.
Figured out that the reason was, “how to commit until everything is working”.

Turns out, he was treating Git as SVN or IBM-clearcase. If you have 10 files edited, how to compile and test 1 file assuming nothing else was edited, and only commit that file.

I had to explain to him the concept of staging and stashing but kind of agreed that for someone with SVN experience, this was not obvious. Hence decided to blog about this.

Before I go on, this is separate from creating a different branch. This approach is for something quick and handy.

Say, I am working on a feature, only to discover that I need to extend another part of the code first. If I was disciplined, I would create another branch at that point. But I’m not. I end up with both the extended utility class and the actual feature as pending changes. With git it is simple to make two separate commits while ensuring that every commit compiles.

I’m working on my new big thing; the command line calculator. I’ve already done addition and am quite happy with that and I’m now implementing subtraction. Half way through the subtraction implementation I discover that I need to make some changes to the terminal output formatter class. It has the + sign hard coded and now needs to take that as a parameter. I do that and end up with a working solution.

Doing a git status however shows a mess.

~/Python/projects/calculator [master +1 ~2 -0 !]> git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
 
        modified:   terminal_formatter.py
        modified:   main.py
 
Untracked files:
  (use "git add <file>..." to include in what will be committed)
 
        subtraction.py
 
no changes added to commit (use "git add" and/or "git commit -a")
~/Python/projects/calculator [master +1 ~2 -0 !]>

I’ve got both the updated terminal_formatter.py, the updated main.py and the new subtraction.py. The first one contains the updated terminal formatting features that are independent of the added functionality. I want to commit the terminal_formatter.py separately. And not only commit it. I want to compile and test the exact code I’m going to commit, by hiding the other files from view. With git this can be done with just a few commands.

Staging the Wanted Changes

The first step is to stage the changes I want in the first commit.

~/Python/projects/calculator [master +1 ~2 -0 !]> git add terminal_formatter.py
~/Python/projects/calculator [master +0 ~1 -0 | +1 ~1 -0 !]>

Doing a git status shows that the terminal_formatter.py file is now ready to be committed.

~/Python/projects/calculator [master +1 ~2 -0 !]> git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
 
        modified:   terminal_formatter.py

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
 
        modified:   main.py
 
Untracked files:
  (use "git add <file>..." to include in what will be committed)
 
        subtraction.py
 
no changes added to commit (use "git add" and/or "git commit -a")
~/Python/projects/calculator [master +1 ~2 -0 !]>

Hiding the Other Changes

Now it’s time to hide the other changes from view with git stash.

~/Python/projects/calculator [master +0 ~1 -0 | +1 ~1 -0 !]> git stash -u -k

The -k switch tells stash to keep the files that are staged intact. The -u switch tells stash to include untracked files (those that are new and not yet added to git).

Doing a git status again shows only the terminal_formatter.py file I want to commit in the first step. This is not just a git status. This is the actual contents of the working directory. At this point I can compile and test the code I’m going to check in, to see that it indeed works without the updated main.py file.

~/Python/projects/calculator [master +0 ~1 -0 !]> git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
 
        modified:   terminal_formatter.py

~/Python/projects/calculator [master +0 ~1 -0 !]>

Making Two Commits

My tests show that the staged changes indeed compile and work without the updated main.py so the first changes are ready to be committed.
git commit -m "Updated terminal_formatter"
Time to get the other changes back.
git stash pop
And stage them
git add -all
And commit them
git commit -m "subtraction implementation"

The Result

The result is a nice commit history, where the separate changes are in separate commits.
git log --stat