Abhinav Gupta | About

Fix-up Git commits with Tig

While working on a branch locally, it can become necessary to make minor changes to older commits. For example, consider the need to fix a typo in commit B in the following git history.

$ git checkout -b feature
# ...
$ git commit -m "Add functionality"  # A
# ...
$ git commit -m "Bind configuration" # B
# ...
$ git commit -m "Create handlers"    # C

    o (master)
     \
      A---B---C (HEAD, feature)

One way to do this is use of an Interactive rebase to edit commit B.

$ git checkout feature
$ git rebase -i master
 pick aaaaaa Add functionality
-pick bbbbbb Bind configuration
+edit bbbbbb Bind configuration
 pick cccccc Create handlers
# On commit bbbbbb
$ vim config.py
$ git add config.py
$ git commit --amend
$ git rebase --continue

    o (master)
     \
      A---B'---C' (HEAD, feature)

Auto-squashing

An improvement upon this is use of the --autosquash flag for git rebase. Combined with git commit --fixup, it enables queuing up fixes to older commits.

# On cccccc (HEAD, feature)
$ vim config.py
$ git add config.py
$ git commit --fixup ":/Bind config"

Tidbit: ":/Bind config" is short for “most recent commit with a commit message matching Bind config.” See man gitrevisions for more.

This creates a new commit with the message fixup! Bind configuration.

$ git log
ffffff fixup! Bind configuration
cccccc Create handlers
bbbbbb Bind configuration
aaaaaa Add functionality

    o (master)
     \
      A---B---C---F (HEAD, feature)

Queue up any number of such fixup commits and run an interactive rebase with the --autosquash flag. The instruction list will place these commits in their right places, ready to combine with their targets.

$ git rebase -i --autosquash
pick aaaaaa Add functionality
pick bbbbbb Bind configuration
fixup ffffff fixup! Bind configuration
pick cccccc Create handlers

Save and quit, and the old commit is all fixed up.

o (master)
 \
  A---B'---C' (HEAD, feature)

This improves the process but it necessitates recalling the log message of the target commit. This can be more streamlined.

Using Tig

Tig is a command-line UI for Git. It augments the regular usage of git, and has become my primary method of committing code.

Tig’s main view displays a list of commits for the current branch. Try it out:

Run tig, navigate to a commit with j/k, and open it with Enter.

Inspect the commit. Move around with j/k, or Ctrl-D/Ctrl-U for page down and up. Close the commit with q and similarly inspect the other commits.

Open the status view by pressing s. Look around using the same keys as before. Quit with Q.

Partially staging changes with Tig

A productivity win of Tig right out the box is an easier to use method of partially staging changes (versus git add --patch).

To try it out, make changes in a repository and open the Tig status view by running tig status or by running tig and pressing s. In the status view, navigate to a file (j/k) in the Changes not staged for commit section and open it (Enter).

Inside the file, navigate to a chunk (j/k) and perform one of the following:

Repeat for other files with uncommitted changes. Similarly unstage changes from files in the Changes to be committed section if necessary.

When ready, commit the staged changes from within Tig (C in status view) or quit (Q) and run git commit.

Auto-squashing with Tig

Tig is configurable via a ~/.config/tig/config file. Among other things, it supports introducing new key bindings that execute arbitrary git commands.

Create a ~/.config/tig/config file if it doesn’t already exist and add the following to it.

bind main = !git commit --fixup=%(commit)
bind main <Ctrl-R> !git rebase --autosquash -i %(commit)

Alternatively, add the following to ~/.gitconfig instead. This will have the same effect as the changes suggested above.

[tig "bind"]
	main = = !git commit --fixup=%(commit)
	main = <ctrl-r> !git rebase --autosquash -i %(commit)

This introduces the following key bindings to the main view:

To try it out, make changes in a repository and open tig. Stage changes in the status view (partially if necessary) and switch back to the main view (q).

In the main view, navigate to the commit to be fixed (j/k) and create a new fixup commit by pressing =.

Queue up as many such fixes as needed.

When it’s time to apply the fixup commits, navigate to a commit before the oldest commit being fixed, and press Ctrl-R to start the interactive rebase. Adjust the rebase instruction list if necessary, save, and quit.

Conclusion

By providing convenient methods of partial staging with auto squashing, Tig streamlines the process of fixing up older commits. It’s a valuable addition to the toolbox for anyone who uses Git.

Edit: 2020-04-30: Add instructions for configuring via ~/.gitconfig.

Written on 2020-02-26. Last modified on 2020-04-30.