/ git

Less frequently used git commands

From time to time, I find myself or my peers not using git correctly or efficiently. This is largely due to the fact that some git commands are less well known and used. Below are a few commands that could be helpful but less frequently used.

git bisect

Suppose you find that commit c100 is failing your tests. But it was running fine at commit c67. You want to know which commit in the range (c67, c100] introduced the bug.

Of course you can manually checkout each commit starting from c68 and run tests to see if it's broken. But that's O(n) and tedious. You can do it in O(lg(n)) by using git bisect.

git bisect start
git bisect good c67
git bisect bad c100

Now git will bisects and checks out a revision for you. You have to answer either git bisect good or git bisect bad. This process repeats until it finds the first bad commit.

This is good but not optimal. You can automate this.

git bisect start
git bisect good c67
git bisect bad c100
git bisect run rspec spec/your/failing/spec.rb

Git will take care of the whole process and tell you the first bad commit. Note that rspec spec/your/failing/spec.rb should be the command to determine whether the commit is good or bad. It can be any command that "exit with code 0 if the current source code is good, and exit with a code between 1 and 127 (inclusive), except 125, if the current source code is bad". Take a look at this

git cherry-pick

Put simply, this command let you replay any existing commit to wherever you are right now.

Suppose you have this tree

c1 - c2 - c3 - c4 - c5 - c6 [master]
	  \
      c7 - c8 - c9 [feature]

You only want commit c8 to go into master, you do

git checkout master
git cherry-pick c8

This results in

c1 - c2 - c3 - c4 - c5 - c6 - c8' [master]
	  \
      c7 - c8 - c9 [feature]

Note that commit c8' and c8 have exactly the same change. But they have different SHA.

git filter-branch

Suppose someone accidentally checked in a SSH key that can login to your production server. You want to remove the key completely from git

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch id_rsa' \
--prune-empty --tag-name-filter cat -- --all

The above command will remove the file id_rsa from the entire history of every branch and tag. And prune empty commits, if any.
Since this will rewrite history, you have to force push to all branches and tags

git push origin --force --all
git push origin --force --tags

If your repo is large, git filter-branch may take a long time. Consider using BFG Repo-Cleaner. It's much simpler and faster. To do the same thing using bfg

bfg --delete-files id_rsa

git format-patch

When the build is broken, you can't simply push your commits. But your colleague needs a few commits from you urgently. What do you do? Instead of creating a temporary branch, you can use git format-patch.

This command put your commit in an emailable format, i.e. a patch. You can then email/AirDrop/Thumbdrive it to your colleague. For example:

  • git format-patch -2 creates 2 patches from last 2 commits
  • you email/AirDrop/Thumbdrive these 2 patches to your colleague
  • your colleague applies the patches git apply [patch-name].patch

git reflog

This command is especially useful after you do git reset --hard HEAD~3 and realize that it's a mistake.

Fortunately, git doesn't actually discard your commits even if you do a hard reset. Hard reset merely moves HEAD to a different state. You commmits are not lost. All movements of HEAD are recorded in reflog.

git reflog

You will see something like this

a4f7ad2 HEAD@{0}: reset: moving to HEAD~3
dcb037d HEAD@{1}: checkout: moving from 44d871f62adaa5a70a to master
44d871f HEAD@{2}: checkout: moving from test to 44d871f62adaa5a70a4f5
...

It shows the history of how HEAD has changed. This includes actions such as commit, rebase, checkout, reset, etc. You can see that the first line
a4f7ad2 HEAD@{0}: reset: moving to HEAD~3 is the hard reset. To recover from that mistake, simply do:

git reset --hard HEAD@{1}

Note that entries in reflog are not stored forever. By default, they will expire in 90 days.