← Back to articles

Absorb Your Git Fixups

If you often find yourself struggling with Git to identify the correct commit to attach your fixup commits, here’s a tool that might save you time and headaches.

Preamble

Before diving in, let’s ensure you’re familiar with git-rebase, its --interactive mode, and the --autosquash feature. If these terms sound unfamiliar, I strongly recommend starting with the Git documentation on rebase. These features let you efficiently modify your commit history, giving you the ability to:

When you use git commit --fixup <commit> or git commit --squash <commit>, Git tags the new commit as a fixup or squash for the specified <commit>. This information is stored in the commit message.

Later, running git rebase --interactive --autosquash HEAD~n (editing the last ncommits) will automatically reorder the commits. The fixup and squash commits will be correctly positioned relative to their base commits, with the appropriate flags pre-applied in the rebase list.

Example

Let’s illustrate with an example. Imagine you’re a kernel developer working on two commits with messages: Edit fork.c and Edit cpu.c.

You make additional changes to fork.c and want to create a fixup commit. Here’s how you’d proceed:

git add fork.c
git commit --fixup 3ee9984  # id of the original commit

Similarly, for a squash commit on cpu.c, you’d follow the same process.

To review and edit the history of these two commits along with their respective fixup and squash commits, you’d use:

git rebase --interactive --autosquash HEAD~4

This organizes the commit list, ensuring your original commits and their fixups/squashes are in the correct order:

rebase

Introducing git-absorb

While git-rebase and --autosquash are powerful, the process of identifying the right commit to attach your fixup or squash commits can be tedious and error-prone. Mistakes often lead to frustrating conflicts during the autosquash process, especially when stacking multiple fixup commits manually.

This is where git-absorb comes in. Written in Rust, this tool simplifies the process by automatically determining which commits your changes should attach to.

How it Works

Returning to our example: after creating the initial commits for fork.c and cpu.c, you make additional changes and stage them with:

git add -A

Instead of manually creating fixup commits, run:

git absorb --one-fixup-per-commit

The --one-fixup-per-commit (or -F) flag minimizes the number of commits. Without it, you’d end up with one commit per set of changed files, which could quickly become unwieldy.

Git-absorb analyzes the staged changes and generates fixup commits, correctly associating them with the appropriate base commits. Your commit history is then neatly organized without manual intervention:

history

Additional Features

If you’d like to immediately open the interactive rebase editor after running git-absorb, use the --and-rebase option. This lets you squash commits right away if needed.

By default, git-absorb inspects the last 10 commits in your history to determine the correct base commits. If you need a more specific range, you can use the --base flag. For instance, if your branch is derived from main, you might run:

git absorb --one-fixup-per-commit --base $(git merge-base main HEAD)

This explicitly defines the starting point for Git-absorb’s analysis, ensuring it aligns with your branch’s history.

Conclusion

git-absorb is a handy tool that takes the guesswork out of managing fixup and squash commits. By automating the process, it saves you time and reduces the risk of conflicts, allowing you to focus on coding instead of wrangling Git.

Give it a try, and let me know how it transforms your workflow!