Keep your Git history clean - 101
Tags: git, basics
Published: 2019-09-01 19:19:40

---
title: Keep your Git history clean - 101
published: true
description:
tags: git, basics
cover_image: https://thepracticaldev.s3.amazonaws.com/i/aesbj2gp5sszra3o32gd.jpg
---

> Cover photo by [JESHOOTS.COM][1] on [Unsplash][2]

I've been working with Git for a few years now and admittedly, a clean commit history wasn't always that important to me. As we know from Back to the Future, rewriting history could have some bad consequences. I can only assume that's why it's such a scary topic. In this post I'd like to share the basic commands, which will help you to keep your Git history clean and take away your trauma from Back to the Future ;-)

## First things first
All commands in this post will generate new commit hashes and therefore diverge your branch from origin. That means you have to do a force push to overwrite the history on remote with `git push --force` or `git push -f`
This in turn means: NEVER change the git history on a shared branch.
There's a safer option that will reject even a force push if the remote branch contains newer commits than your local branch:
`git push --force-with-lease`

## Scenario 1: Add something to your last commit
Everyone has been in a situation like this. You add your changes to the staging area, commit it and wait for a green build. Unfortunately your build fails. Of course, you forgot to add file x. What's coming next? Add file x to the staging area, commit with a message like "stupid me forgot to add file x" aaaaaaaaand STOP! Let me introduce you to our first command:
`git commit --amend`
This command adds your changes from the staging area to the last commit and gives you the opportunity to change your last commit message. You can also do that with one single command:
`git commit --amend -m "new message"`
If you don't want to change your last commit message, you can just add the _--no-edit_ parameter:
`git commit --amend --no-edit`
So no more "stupid me forgot" commits!

## Git interactive rebase
For the next few scenarios we're going to use interactive git rebase. This tool helps us to modify changes that are further back in history. You simply start an interactive rebase with the following command:
`git rebase -i <base>`
Where `<base>` means how far back you'd like to rewrite your history.
For instance, rewrite last three commits:
`git rebase -i HEAD~3`
Another example, rewrite history to a specific commit:
`git rebase -i 63a2356`
The command now shows you the commits and options in your configured editor:

```
pick bcfd87e add sql scripts for database
pick 9fb0b9c prevent users from changing their email
pick e0b46b9 cleanup web config

# Rebase dfef724..e0b46b9 onto dfef724 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
```

Let's jump into the first rebase scenario.

## Scenario 2: Add something to an arbitrary commit in your history
Like in scenario 1 you forgot to add something to your commit. But instead of adding it to your last commit, you'd like to amend that change to a commit further away in history. For such a case, interactive rebase provides us the edit option.
We start with the interactive rebase command (see above) and set the edit command on the commit we'd like to change:
```
pick bcfd87e add sql scripts for database
edit 9fb0b9c prevent users from changing their email
pick e0b46b9 cleanup web config
```

Interactive rebase now stops at this commit and you're able to add your changes:
```
Stopped at 9fb0b9c...  prevent users from changing their email
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
```

After you're done you simply add the changes to the staging area and commit them with the amend command:
`git commit --amend`
Almost done! Now we have to continue the interactive rebase:
`git rebase --continue`

## Scenario 3: Merge commits
I try to commit as often as possible. That means my git history in a feature branch usually looks something like this:
```
9edf77a more review findings
67b5e01 review findings
940778d enable users to change their name
dc6b0db enable users to change their name
dfdd77d wip
```

That may be useful during feature development, but it's not for the overall git repository history. Therefore I'd like to merge all commits into one. There are two options to do this: squash and fixup. Both commands meld a commit into the previous one. The only difference is that squash lets you provide an option to write a new commit message while fixup discards the message.
So let's squash some commits. We start the interactive rebase like we did before and set the appropriate options:
```
pick dfdd77d wip
squash dc6b0db enable users to change their name
squash 940778d enable users to change their name
squash 67b5e01 review findings
squash 9edf77a more review findings

# Rebase 63a2356..9edf77a onto 63a2356 (5 commands)
```

Git now provides an overview with all messages:
```
# This is a combination of 5 commits.
# This is the 1st commit message:

wip

# This is the commit message #2:

enable users to change their name

# This is the commit message #3:

enable users to change their name

# This is the commit message #4:

review findings

# This is the commit message #5:

more review findings

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
```

We remove all lines and write just the message we'd like to have in the end:
```
enable users to change their name

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
```

And voilà, we have squashed our commits into one:
```
cb88cf6 enable users to change their name
```

We can do the same with the fixup command. In the scenario below I'm happy with the commit message where I'd like to meld my commits into:
```
pick dc6b0db enable users to change their name
fixup dfdd77d wip
fixup 940778d enable users to change their name
fixup 67b5e01 review findings
fixup 9edf77a more review findings

# Rebase 63a2356..9edf77a onto 63a2356 (5 commands)
```

The result is the same like the squash example above:
```
b8e76d1 enable users to change their name
```

## Scenario 4: Split commit
Now and then we tend to commit two or more different topics into one:
```
7080968 add sql scripts for database
172db2b prevent user from changing their email and cleanup web config
```

In this case it would be a lot cleaner to separate the cleanup of our web config, wouldn't it? At this point we can take advantage from the edit command again. Let's start the interactive rebase session and do that:
```
pick 7080968 add sql scripts for database
edit 172db2b prevent user from changing their email and cleanup web config

# Rebase dfef724..172db2b onto dfef724 (2 commands)
```

As you expect, Git stops at our commit. Now we can take back our changes into the working area:
`git reset HEAD~`
The rest is straightforward, just create two commits for our changes:
`git add [files]`
`git commit -m "prevent user from changing their email"`
`git add [files]`
`git commit -m "cleanup web config"`
Don't forget to continue the rebase session:
`git rebase --continue`
Let's see the result:
```
9fb0b9c prevent user from changing their email
dfef724 cleanup web config
7080968 add sql scripts for database
```

Neat!

## Scenario 5: Reorder commits
What if you'd like to merge two or more commits and they're not in order?
```
09f43c9 validate user inputs
62490ed import user data
c531f57 validate user inputs
```

You simply reorder the commits inside the rebase editor. After that you can use the fixup or squash option to merge them.
```
pick 62490ed import user data
pick c531f57 validate user inputs
fixup 09f43c9 validate user inputs

# Rebase 6c70ff2..09f43c9 onto 6c70ff2 (3 commands)
```

## Scenario 6: Change commit message
Not satisfied with a specific commit message? With help of the reword command you can always change it:
```
reword c531f57 validate user inputs
pick 62490ed import user data

# Rebase 6c70ff2..09f43c9 onto 6c70ff2 (3 commands)
```

Git will stop at this commit and you have the option to change the message:
```
validate user inputs
  
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
```

## Scenario 7: Drop commit
Need to revert a specific commit completely? For such a scenario we can use the drop command:
```
pick bcfd87e add sql scripts for database
drop 9fb0b9c prevent users from changing their email
pick e0b46b9 cleanup web config

# Rebase dfef724..e0b46b9 onto dfef724 (3 commands)
```

Here, at the latest, here you can see why it's important to keep your Git history clean. Imagine you've put different features into one commit or split up a feature into multiple (pointless) commits. A revert would be much more complicated.

## When something's going wrong
Don't worry about breaking something when rewriting your Git history.
First, you can always abort an interactive rebase session with `git rebase --abort`
This command will stop the rebase session and revert all changes you already made.
Second, even if you've completed a rebase and messed it up somehow, you can revert it. Git (luckily) keeps track of all your commands that you execute. You open this log with `git reflog`
```
e0b46b9 (HEAD -> feature/my2, feature/my3) HEAD@{34}: rebase -i (finish): returning to refs/heads/feature/my2
e0b46b9 (HEAD -> feature/my2, feature/my3) HEAD@{35}: commit: cleanup web config
9fb0b9c HEAD@{36}: commit: prevent users from changing their email
bcfd87e HEAD@{37}: reset: moving to HEAD~
3892bc7 HEAD@{38}: rebase -i: fast-forward
bcfd87e HEAD@{39}: rebase -i (start): checkout master
3892bc7 HEAD@{40}: rebase -i (finish): returning to refs/heads/feature/my2
3892bc7 HEAD@{41}: commit (amend): prevent user from changing their email and cleanup web config
39262b5 HEAD@{42}: rebase -i: fast-forward
bcfd87e HEAD@{43}: rebase -i (start): checkout master
39262b5 HEAD@{44}: rebase -i (abort): updating HEAD
39262b5 HEAD@{45}: rebase -i (abort): updating HEAD
bcfd87e HEAD@{46}: reset: moving to HEAD~
39262b5 HEAD@{47}: rebase -i: fast-forward
bcfd87e HEAD@{48}: rebase -i (start): checkout master
39262b5 HEAD@{49}: rebase -i (finish): returning to refs/heads/feature/my2
39262b5 HEAD@{50}: rebase -i (pick): prevent user from changing their email and cleanup web config
bcfd87e HEAD@{51}: commit (amend): add sql scripts for database
7080968 HEAD@{52}: rebase -i: fast-forward
dfef724 (master) HEAD@{53}: rebase -i (start): checkout master
172db2b (feature/my) HEAD@{54}: checkout: moving from feature/my to feature/my2
172db2b (feature/my) HEAD@{55}: commit: prevent user from changing their email and cleanup web config
7080968 HEAD@{56}: commit: add sql scripts for database
dfef724 (master) HEAD@{57}: checkout: moving from master to feature/my
dfef724 (master) HEAD@{58}: commit (initial): init
```

Now you can search for your last action before starting the interactive rebase session and revert back to that state. For instance:
`git reset --hard HEAD@{18}`

So, let's fight against our fear and keep our Git history clean! :-)

[1]: https://unsplash.com/@jeshoots?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
[2]: https://unsplash.com/search/photos/cleaning?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText