以柔克刚:化解 Git 操作的疲劳感
Defeating Git Rigour Fatigue with Jujutsu

原始链接: https://ikesau.co/blog/defeating-git-rigour-fatigue-with-jujutsu/

为大型功能特性维护干净的提交历史(即每个提交都是逻辑清晰、便于审阅的步骤)往往令人精疲力竭。像 `jj absorb` 或 `jj squash -i` 这样的传统工具容易出错,或者在边界复杂时导致合并冲突。 作者提出了一种替代工作流:“衣物分类法”(Laundry Sorting)。在开发过程中,与其费力维护完美的提交,不如先让初始提交变成一堆混乱的混合更改。待功能完成后,你先创建一个由空提交组成的链条,按逻辑顺序排列,代表你理想中的历史记录。接着,将所有“混乱”的提交合并为一个“全量”提交,再以交互方式将特定的代码块迁移到对应的逻辑提交中。 这种方法避免了“拆分与合并”的循环,简化了整理过程,并通过一次性分发更改避免了冲突地狱。虽然这有时会产生无法编译的中间提交(牺牲了完美的 `git bisect` 历史),但它显著减轻了版本控制带来的认知负担,将审阅者的可读性和开发者的顺畅度置于严格的增量提交规范之上。

Hacker News 最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 用 Jujutsu 克服 Git 操作疲劳 (ikesau.co) 9 分 | ikesau | 2 小时前 | 隐藏 | 过往 | 收藏 | 讨论 | 帮助 社区准则 | 常见问题 | 列表 | API | 安全 | 法律 | 加入 YC | 联系 搜索:
相关文章

原文
This post assumes a basic level of familiarity with the jujutsu version control system. If you haven't used jujutsu, you'll still get the gist of the idea, but I recommend reading Steve's Jujutsu tutorial after.

When developing a large feature, writing Good Commits is hard.

And by Good Commits, I mean something like:

define types add DB functions server CRUD client API client UI

This allows reviewers to step through your pull request in small bites, with each set of changes scoped to a single aspect of the feature.

So, naturally, here's what I do instead:

define types add DB functions WIP test code server CRUD client API and UI fix DB function fix UI bug refactor CRUD fix another UI bug

Latter commits overwrite work that was done in earlier commits and the story breaks.⚖️

Jujutsu makes it easier to hop around commits and iterate quickly on compartmentalized changesets, but it's still effortful and I get averse.🤖

jj absorb helps somewhat, as does jj squash -i, but they both have their downsides:

  1. absorb assigns the changes based on whichever previous commit most recently touched those files, which sometimes doesn't actually correspond to which commit should own these particular changes.
  2. squash can get you stuck in merge conflict hell if your boundaries aren't extremely clean.

So here's a solution to this problem of "git rigour fatigue" that I've come up with.

For this example, let's represent commits visually. Imagine red represents changes to the type definitions, blue to the UI and so on:

Mayhem. Our first commit is a mix of red and blue. We touch red in multiple places!

To fix this, let's create our ideal commit history first, using jj new -B messy-first -m 'red'

Then we can do the rest. (I switch to jj new -A red -m 'blue' at this point)

Then we squash all the commits with actual changes in them into one with jj squash --from messy-first..messy-last --into messy-first

Then we use jj squash -i --from --into red and pick out the red changes, putting them into the red box:

And so on:

Eventually everything's in the right place and the "everything commit" is empty.

For large features, I find this workflow far easier than having to maintain strict git rigour for the lifecycle of the feature's development. It's easier to make improvised commits with temp debugging state in them and tidy it all up in one sweep at the end.

preemptions:

  1. I don't have a good name for this technique. "Doing Commits Like A Big Pile Of Laundry", perhaps?
  2. This is different from (and, imo, superior to) jj split:
    1. With split, if I miss a hunk that should have been in red, I have to split again and squash.
    2. This technique more easily allows sorting the easiest hunks at the beginning without worrying about how it will effect the commit sequencing.
  3. This reason why doing it all at the end is (often) better than using jj squash -i as you go is because the final state of the everything commit is guaranteed to not have any conflicts. Creating a new "fix red and green" commit and interactively squashing that into your red and green commits might break your blue commit if it happens to touch one of the affected files.
  4. A downside to this technique is that there's no guarantee that every commit will compile, which might be a dealbreaker.

⚖️ Some people prefer this, as it helps git bisect work better. Debuggability versus reviewer convenience is the tradeoff, I guess.

🤖 Especially in a world of LLM agents, that will gladly fix bugs for you that span multiple boundaries in 30 seconds.

联系我们 contact @ memedata.com