Git Tricks for Clean Histories

As much as I love the concept of cheap branching and revision control, I’m a bit of a “code first, ask questions later” kind of guy. Most of my best ideas only come to me once I’m elbow-deep in code and they tend to arrive in bits small enough to make planning my branching and committing impossible.

For this reason, I get a lot of use out of git commands which help me to code first and then sort it out later. Here are my favorites:

IMPORTANT: None of these should be used on commits which have already been shared. Clean up your repository, then “git push”. Eagerness to push is one bad habit you’ll just have to break the hard way.

Temporarily get rid of uncommitted changes
git stash
[do your thing]
git stash pop
The manpage gives some examples for where this can be helpful. The most obvious is “boss interrupts you demanding a bugfix immediately”.
Use a GUI to stage and commit only certain lines
git gui

I use this to untangle unrelated changes into clean commits after the fact. The “git citool” alias makes it automatically close when you commit.

Add the staged changes to the last commit you made instead of a new one
git commit --amend

…or just use “git gui”.

Merge “oops” commits within the last 12 into what they should have been part of
git rebase -i HEAD~12

…and then move each “oops” commit below its parent and change “pick” to “fixup”.

Interactive rebase is a very powerful function which can also be used to apply arbitrary changes to any commit you haven’t yet shared. For details, check out the “reword”, “edit”, and “exec” commands in the instructions it displays.

Note: Rebase can sometimes get confused, but if you ever find yourself confused by its requests for clarification and just want to change your mind, simply use git rebase --abort

Move the last 8 commits to a new branch
git branch my_new_branch
git reset HEAD~8
git checkout my_new_branch

Use this when you you find yourself thinking “Dammit! This should be a feature branch.” It works by telling git to create a new branch with its HEAD here and then to move the current branch’s HEAD 8 commits into the past.

Move the last 8 commits to an existing branch
git stash
git checkout -b tmp
git rebase --onto right_branch HEAD~8
git checkout right_branch
git merge tmp
git branch -d tmp
git checkout wrong_branch
git reset --hard HEAD~8
git checkout right_branch
git stash pop

This complex string of commands is for when you make some commits, then realize you’re on the wrong branch. I wish I could offer something simpler, but moving a string of commits to a branch that could’ve diverged quite far is a complex task.

Basically, you stash away your uncommitted changes, use a temporary branch to rebase and merge the changes into the new branch, and then remove them from the old branch, and apply the uncommitted changes to the new branch.

Undo a merge and rebase instead after accidentally running “git pull” instead of “git fetch”
This is useful when you want to keep a nice, linear development history, you’ve developed something, you try to push it, it fails, and you respond with git pull rather than git fetch. (I tend to forget to pull before developing when switching machines)
The trick is to use git’s reflog feature (git’s Undo history). If your branch is named mybranch and the remote is origin/mybranchand `git pull` is the last thing that made changes, you can undo it like this:

git stash
git reset --merge mybranch@{1}
git rebase origin/mybranch
git stash pop

(You can omit the git stash lines if you don’t have any uncommitted changes)

This feature isn’t limited to undoing merges either. Use this command to see what else you can feed to git reset:

git reflog show
And so on…
Got a type of “oops” I missed? Tell me so I can update this post.

UPDATE: Justin Hileman has an excellent flowchart which you’ll probably also want to check out.

CC BY-SA 4.0 Git Tricks for Clean Histories by Stephan Sokolow is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

This entry was posted in Geek Stuff. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

By submitting a comment here you grant this site a perpetual license to reproduce your words and name/web site in attribution under the same terms as the associated post.

All comments are moderated. If your comment is generic enough to apply to any post, it will be assumed to be spam. Borderline comments will have their URL field erased before being approved.