We all have been familiar with the following kind of git history:
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.
- If you have small related commits, we can "squash" them into one
- If you have a large commit we can split that commit into bunch of smaller commits that represent a logical changeset
- If you have a bad commit message, we can "reword" it
- If commits are not needed, we "drop" them
- 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
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
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
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.
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}
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}
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
To add a change in last commit
- Make your required changes
- Stage your changes
- git commit --amend
To add a file not included in last commit
- Stage the file
- git commit --amend
To delete a file accidently included in last commit
- git reset --mixed HEAD~1
- git clean -fd (remove untracked files)
- 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
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:
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
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.
Once this is done, all we need to do is continue the rebase process using:
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
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:
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
Mark your commit from "pick" to "edit"
Now rebase would stop at this commit and will allow you to split the commit into separate commits.
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
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
After this, the new history would look like:
As you can we have succesfully split our commit into two separate commits.
Thanks for making it towards the end of the post.
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)
Very useful tips!
For me the more used feature and solves a lot of problems is the "rebase".
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.
Thanks for reading.