Git: Rewriting history
Tags: git, history, squash
Published: 2020-07-19 05:30:41

# Background

As software engineers, we have always been looking for better ways to manage our codebases. We have used different source control systems like **Git**, and we may need advanced techniques from time to time to resolve some new issues we face, specially when working on bigger software solutions with huge codebases. 

One such situation is when we need to re-write Git history of a repository for any reason including:
* update (**amend**) a commit message 
* remove (**drop**) a specific commit
* combine (**squash**) some commits with a specific commit
* **re-order** commits

Let's do it!

# Hands on terminal

We can create a new Git repository and add some files and make some changes in those files and then make some commits. Suppose we need to update the last 7 commits (crazy I know!). Let's run this command:

```bash
git rebase -i HEAD~7
```

This will open an interactive window where we can choose the proper action to apply to the repository history. Note that Git adds the commit messages in reverse order, so commit `11454df test something!` is actually our last commit:

```bash
pick 58bc9d0 file.txt created
pick 121c507 file2.txt created
pick 64a2c99 file2.txt updated
pick 2ef342a file2.txt updated step 1!
pick e84f687 file2.txt updated step 2!
pick 16c8448 file2.txt updated step 3!
pick 11454df test something!

# Rebase e84f687..11454df onto e84f687 (7 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
# d, drop <commit> = remove commit
# 
# REMOVED FOR BREVITY
```

You will see we have a couple of options to choose including:

* select (**pick**) the same commit
* update (**reword**) a commit message in-place
* update (**edit**) a commit message (This will open another window to edit the commit message)
* combine (**squash**) commit messages
* remove (**drop**) a commit

All we need to do is select the required action (e.g. `pick` or `squash`) and replace the current `pick` actions with the new action:

```bash
reword 58bc9d0 file.txt created and updated
pick 121c507 file2.txt created
pick 64a2c99 file2.txt updated
squash 2ef342a file2.txt updated step 1!
squash e84f687 file2.txt updated step 2!
squash 16c8448 file2.txt updated step 3!
drop 11454df test something!

# 
# REMOVED FOR BREVITY
```

After saving the above (e.g. using `:wq` in `vi`), as we asked Git to squash some of our commits, it will open another window so we can finalize the commit message:

```bash
# This is a combination of 4 commits.
# This is the 1st commit message:

file2.txt updated

# This is the commit message #2:

file2.txt updated step 1!

# This is the commit message #3:

file2.txt updated step 2!

# This is the commit message #4:

file2.txt updated step 3!

# Please enter the commit message for your changes.
# 
# REMOVED FOR BREVITY
```

We can update this to have our desired message (e.g. remove all except `file2.txt updated`) or we can save it just as it is. Note that lines starting with a hash `#` will not be added to the final commit message. 

Now if you run `git log`, you will see the applied changes which are:

* update the message of the 1st commit
* retain the 2nd and the 3rd commits
* squash the 4th, 5th and the 6th commits into the 3rd commit (This opens another window so we can make the final change)
* remove the 7th commit

Note that instead of `squash` we could choose `fixup` for commits we want to combine and Git would choose the message of the `pick` commit before those commits (i.e. `64a2c99`) as the final commit message for the combined commits.

Also, for **re-ordering** commits, we can simply change the line arrangement of the commits and put a specific commit before or after another commit:

```bash
pick 121c507 file2.txt created
pick 58bc9d0 file.txt created
```


Happy coding!