DEV Community

Cover image for Get Control of Your Git History
Deepak Ahuja πŸ‘¨β€πŸ’»
Deepak Ahuja πŸ‘¨β€πŸ’»

Posted on • Edited on

Get Control of Your Git History

We all have been familiar with the following kind of git history:
https://xkcd.com/1296/
The following things immediately makes it categories into the "bad" category of git history:

  • Bad commit messages
  • Large commits (containing unrelated changes)
  • Small commits (Scattered liked changes)

A clean readable history tells a story of how a project is built. It makes it easier to take control of each step done along the way and when needed alter it without much chaos. A cleaner history also allows us to generate change notes, automate the boring stuff and so on. Each commit should be a working copy of the project.

  1. If you have small related commits, we can "squash" them into one
  2. If you have a large commit we can split that commit into bunch of smaller commits that represent a logical changeset
  3. If you have a bad commit message, we can "reword" it
  4. If commits are not needed, we "drop" them
  5. If contents of a commit needs to be modified, we can "amend" it We'll look at knowledge needed to do each of these.

Let's git started:

Undo the commit

The commit that you want to undo could be at either two places:

Commit is already pushed (and being consumed by others)

In this case we can use the following command:

git revert HEAD
Enter fullscreen mode Exit fullscreen mode

This will create a new commit on top of old commit, that would contain the changes opposit of last commit.

an option --no-commit that would not auto commit, but introduce change in staging area.

Commit is not pushed (only exists in local repository)

git reset --hard HEAD~1 // Go back in time, throwing away changes
Enter fullscreen mode Exit fullscreen mode

Here HEAD~1 represents the target commit to goto, i.e. one step back from HEAD. The following three options are available with reset

--soft (removes the commit and bring it back to staging area, working directory untouched)
--mixed (removes the commit from staging area (i.e. unstage) to working directory, the default option)
--hard (removes the commit completely, even from working directory)

Recover a lost commit

With git we don't really lose a commit unless it is garbage collected. There's a magic command to see all commits ever created in history by:

git reflog
Enter fullscreen mode Exit fullscreen mode

Basically wherever your HEAD has traveled inside of Git, you can find it inside of the reflog. The reflog is an ordered list of the commits that HEAD has pointed to: it's undo history for your repo. The reflog isn't part of the repo itself (it's stored separately to the commits themselves) and isn't included in pushes, fetches or clones; it's only local.
So let's say you have just reset the HEAD to point to a wrong commit, did an unexpected rebase or accidentaly deleted a branch. You can recover back your work using the history given in this command.

Alt Text

We now just git checkout using the unique identifier of each commit or cherry-pick them in our current branch. If we checkout the commit we can again reset out head back to it.
This command can be used with branches, or even using time as a filter like

git reflog HEAD@{1.day.ago} 
Enter fullscreen mode Exit fullscreen mode

Timed reflog

The time filter can be passed to any git command
The time filters supported are:
a. 1.minute.ago
b. 1.hour.ago
c. 1.day.ago
d. yesterday
e. 1.week.ago
f. 1.month.ago
g. 1.year.ago

git diff master@{0} master@{1.day.ago}
Enter fullscreen mode Exit fullscreen mode

This command will diff the current master branch against master 1 day ago. This is very useful if you want to know changes that have occurred within a time frame.

Change the last commit

Either you want to change contents of last commit or just change the commit message, we can use the following git command:

git commit --amend
Enter fullscreen mode Exit fullscreen mode

To add a change in last commit

  1. Make your required changes
  2. Stage your changes
  3. git commit --amend

To add a file not included in last commit

  1. Stage the file
  2. git commit --amend

To delete a file accidently included in last commit

  1. git reset --mixed HEAD~1
  2. git clean -fd (remove untracked files)
  3. git commit -m "new commit message"

Change a commit earlier in history

To edit a commit which is older in history you can use git rebase (only if your commits are not being consumed anyone else yet)

git rebase -i HEAD~2
Enter fullscreen mode Exit fullscreen mode

Here, HEAD~2 is the target till when you want to replay the history. You can also put the commit id just before of the one you need to change.
Since commits are immutable, rebase would re-create the commits.
This command would open a script in a text editor like this:
git rebase -i master
The latest commit is at the bottom.
You can change the script as per need by replacing word "pick" to action needed on the commit. Here add "edit" to the third last commit as this is the commit that i want to change
pick to edit
Now i will save and exit the file. Git will stop at the desired commit and we can perform our changes and use the command "git commit --amend" at the place.
git commit --amend
Once this is done, all we need to do is continue the rebase process using:

git rebase --continue
Enter fullscreen mode Exit fullscreen mode

git rebase --continue
The rebase process would complete and you can see the commit history again. I changed the commit message of commit marked for edit.

git log
Enter fullscreen mode Exit fullscreen mode

chnaged commit message

Thus git rebase allows to replay the whole git commit history, and do separate action needed on each commits. This is also used while deleting, re-ordering or squashing few commits into one similarly.

Splitting the commit

Assume you have the following git history:
Alt Text

As you can see in the second commit from top, "cca3a22 update jquery version and TnC"
Updating TnC and jquery version are two unrelated things that shouldn't exist together in commit. So we should split this commit into two separate commits.

So grab it's commit id and start rebasing from it's parent commit:

git rebase -i 44da2c9
Enter fullscreen mode Exit fullscreen mode

Mark your commit from "pick" to "edit"
Alt Text
Now rebase would stop at this commit and will allow you to split the commit into separate commits.
Alt Text
HEAD currently points to the combined commit, you can use reset command to undo the changes from this commit using either soft or mixed.

git reset --mixed HEAD~1
Enter fullscreen mode Exit fullscreen mode

This would bring contents of the commit to working directory. Now we can commit these two things separately.
After the two commits are done, you can continue and finish the rebase.

git rebase --continue
Enter fullscreen mode Exit fullscreen mode

After this, the new history would look like:
Alt Text
As you can we have succesfully split our commit into two separate commits.

Thanks for making it towards the end of the post.

Alt Text

Please leave feedback if you learnt something new or share it with someone who may benefit from it. Drop me a hello on twitter or checkout my other work in the profile. :)

Top comments (4)

Collapse
 
helderberto profile image
Helder Burato Berto

Very useful tips!
For me the more used feature and solves a lot of problems is the "rebase".

Collapse
 
dpkahuja profile image
Deepak Ahuja πŸ‘¨β€πŸ’»

Thanks for reading. β€œRevert” and β€œreflog” were new for me. Also i often get confused with different reset options i thought about writing it down once and for all. I use rebase daily to squash my commits.

Collapse
 
dpkahuja profile image
Deepak Ahuja πŸ‘¨β€πŸ’»

Thanks for reading.