Abhinav Gupta | About

Fix-up Git commits with Tig

Table of Contents

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"  # aaaaaa
# ...
$ git commit -m "Bind configuration" # bbbbbb
# ...
$ git commit -m "Create handlers"    # cccccc
 ABCmainfeature

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

Start an interactive rebase
$ git checkout feature
$ git rebase -i main
Mark commit B for editing
 pick aaaaaa Add functionality
-pick bbbbbb Bind configuration
+edit bbbbbb Bind configuration
 pick cccccc Create handlers
Edit the commit
# On commit bbbbbb
$ vim config.py
$ git add config.py
$ git commit --amend
$ git rebase --continue
 ABCmainfeature

1. 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"
💡 Tip

":/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
 ABCFmainfeature

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
 ABFCmainfeature

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

 ABCmainfeature

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

2. 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.

image

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.

image

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

image

2.2. 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).

image

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

  • Stage this entire chunk (u)

  • Stage a single line in the chunk (1)

  • Increase (]) or decrease ([) diff context

  • Split the chunk (\) and go to step 3

  • Close the file (q)

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

image

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

image

3. 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:

  • = will create a new fixup commit for the focused commit

  • Ctrl-R will being an interactive rebase with auto-squashing enabled on top of the focused commit

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).

image

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

image

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.

image

4. 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 . Last modified on 2020-04-30.