惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
博客园_首页
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
The Cloudflare Blog
V
Visual Studio Blog
罗磊的独立博客
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
D
Docker
Last Week in AI
Last Week in AI
B
Blog RSS Feed
C
Check Point Blog
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
博客园 - 聂微东
MongoDB | Blog
MongoDB | Blog
雷峰网
雷峰网

Butler's Log

Figma agent vs. one button: shipping design tokens to npm Local Model Performance on an M5 Max GitButler 0.22 - "Catch 22" Agentic Version Control Benchmarks Grit: rewriting Git in Rust with agents Git Merge 2026 Agent-safe Git with GitButler We’ve raised $17M to build what comes after Git Announcing the GitButler CLI for Linux The Great CSS Expansion A couple of git nits Simplifying Git by Using GitButler Introducing the GitButler CLI GitButler 0.19 - "Commander Keen" But Head: Crafting a Custom Font MCP vs RAG: Two Very Different Ways to Gain Context Getting Started With GitButler Agents Using the GitButler MCP Server to Build Better AI-Driven Git Workflows Using GitButler With Multiple GitHub Accounts Advent of Code! Upcoming GitButler Events Use GitButler for your Gerrit workflow Integrating GitButler and GitHub Enterprise Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 3 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 2 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 1 Grid Happens: Because Flexbox Wasn’t Enough Using Cursor Hooks for automatic version control Deep Dive into the new Cursor Hooks A Responsive Item Counter with CSS only
Fixing up Git with Autosquash
Scott Chacon · 2024-03-11 · via Butler's Log

This year I've been giving talks all over the place, pointing out some cool features of Git that I've found aren't super well known and writing up some of these features here. Previously I've written about worktrees, large repository features and a few other things.

Today I want to show you a really handy way to incorporate fixups into a series of commits using git commit --fixup and git rebase --autosquash.

But first, let's take a look at the problem that this is designed to solve.

As you may or may not know, Git actually doesn't store patches or changesets, it stores snapshots. However, lots of Git commands can convert a list of them into the differences between each one, which makes it seem like each commit is a patch.

What makes things a little more confusing is that the commit message generally describes this difference, rather than the actual data that it points to.

This is more or less what Git was originally written to do, keep a series of snapshots of the Linux kernel and easily convert them into a series of patches that can easily be e-mailed to a mailing list and re-applied locally.

So this is the generally intended original workflow of a Git user. You make a change and document it. You make another change and document it. Then you use git send-email to mail those changes to the mailing list for feedback.

Today this is often done as a branch with a Pull Request on GitHub, but the idea is the same: you have commits that describe your changes that people can inspect in isolation.

If you're using GitHub and PRs to collaborate on changes, it's possible that you think of the atomic unit of work as the branch rather than the commit for this reason. That the PR description is actually what's most important, not individual commit messages, largely because GitHub makes the PR reviewable and not individual commits. Whereas in mailing lists, the individual commits are quite important for review purposes.

In a mailing list, one normally sends a patch series, gets feedback on each patch and then you need to re-roll the series and re-send it. This article is about trying to do that in an easier way. Maintain a nice series of commits as "patches" and incorporate feedback into that series, rather than just pushing new commits on top of the branch.

Ok, so let's imagine a scenario. Let's say that you have two commits that implement some feature and document it.

❯ git log --oneline sc-feature-A ^origin/master
18e9c6b997a (sc-feature-A) add documentation for new feature
e57c40da089 add new feature

We can also see that each change has modified some files (with --stat):

❯ git log --oneline --stat sc-feature-A ^origin/master
18e9c6b997a (sc-feature-A) add documentation for new feature
 README.md | 1 +
 1 file changed, 1 insertion(+)
e57c40da089 add new feature
 gitbutler-app/src/virtual_branches/errors.rs              | 1 +
 gitbutler-app/src/virtual_branches/files.rs               | 1 +
 gitbutler-ui/src/lib/components/Differ/CodeHighlighter.ts | 2 ++
 3 files changed, 4 insertions(+)

You send your patch series to a mailing list (or push them to a Pull Request).

Now you have two commits that are viewed as patches and commented on. The feedback is valuable and you want to incorporate changes to address the feedback into your commits.

Here is where things become a little difficult. You want to still have two commits with the same good descriptive commit messages, but you need to incorporate changes from your feedback into different files in each commit.

One way to do this is to interactively rebase, stopping at each commit, incorporating your changes destined for that commit, and then continuing. This is a little difficult because you can't really make the changes to make sure they work and then do this very easily.

Or, you could make the changes, commit it into temporary commits, then rebase interactively, rearrange the commits and squash them together.

We make our temporary commits:

❯ # fix the documenation
❯ git commit -am 'feedback on documentation'

❯ # fix the feature
❯ git commit -am 'feedback on feature'

❯ git log --oneline sc-feature-A ^origin/master
d7c50392f51 (sc-feature-A) feedback on feature
c679989f4a7 feedback on documentation
18e9c6b997a add documentation for new feature
e57c40da089 add new feature

Then we interactively rebase and rearrange the pick lines:

❯ git rebase -i origin/master

# REBASE FILE:

---
pick e57c40da08 add new feature
pick 18e9c6b997 add documentation for new feature
pick c679989f4a feedback on documentation
pick d7c50392f5 feedback on feature
---

# BECOMES:

---
pick e57c40da08 add new feature
squash d7c50392f5 feedback on feature
pick 18e9c6b997 add documentation for new feature
squash c679989f4a feedback on documentation
---

This rebase should work without issues (here I also changed the commit message to add w / feedback, just to be clear that it's incorporated the changes)

❯ git rebase -i origin/master
[detached HEAD 3224c58f81] add new feature, w/feedback
Date: Mon Mar 11 11:17:21 2024 +0100
 3 files changed, 5 insertions(+)
[detached HEAD d0a90ff020] add documentation, w/ feedback
 Date: Mon Mar 11 11:17:30 2024 +0100
 1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/sc-feature-A.

And viola, we have two commits again, this time with the feedback incorporated.

❯ git log --oneline sc-feature-A ^origin/master
eb4262e9e46 (sc-feature-A) add documentation, w/ feedback
3224c58f81c add new feature, w/feedback

Now we can re-send the series to the mailing list, or force-push to our branch.

However, this is common enough of an issue, that Git has a clever way to do this a little more automatically. Instead of committing something that lets us know that we fixed up a previous commit, we can let Git know that a commit is a fixup.

With, you may have guessed it, the --fixup flag. So let's say we get more feedback on our series and need to fixup both commits again for a third round of the series. This time, we'll use the --fixup flag:

❯ # do more work on documentation
❯ git commit -a --fixup=eb4262e9e46
[sc-feature-A f77a3f5909] fixup! add documentation, w/ feedback
 1 file changed, 1 insertion(+)

❯ # do more work on the feature

❯ git commit -a --fixup=3224c58f81c
[sc-feature-A 6149ef8966] fixup! add new feature, w/feedback
 1 file changed, 1 insertion(+)

❯ git log --oneline sc-feature-A ^origin/master
6149ef89666 (sc-feature-A) fixup! add new feature, w/feedback
f77a3f5909a fixup! add documentation, w/ feedback
eb4262e9e46 add documentation, w/ feedback
3224c58f81c add new feature, w/feedback

It's not super complicated, but in the case of committing with a --fixup=[sha] flag, Git will write a specially formatted commit message that it can recognize to mean "this commit fixes up this other commit".

Now, instead of manually interactively rebasing and rearranging the pick/squash commands, you can simply run git rebase --autosquash:

❯ git rebase --autosquash origin/master
Successfully rebased and updated refs/heads/sc-feature-A.

❯ git log --oneline sc-feature-A ^origin/master
0273a58c32e (sc-feature-A) add documentation, w/ feedback
7c102015437 add new feature, w/feedback

It basically does exactly what we just did, but automatically - no editor is opened, no instructions manually moved around.

💡

The --fixup option also takes amend and reword options, to handle rewording the commit message as well. For example, git commit --fixup:amend=[sha]. Check out the Git commit docs for more options.

If you're trying to maintain a clean patch series as a continuously updated set of commits, --fixup and --autosquash may make things a little simpler. Enjoy your squashing!