Article URL: https://lalitm.com/post/git-history/ Comments URL: https://news.ycombinator.com/item?id=48901010 Points: 103 # Comments: 65

Working with lots of changes in parallel on git can be painful. You end up juggling branches and commits, and running scary rebase -i commands that can leave your tree in a half-broken state if you so much as sneeze. jj, an alternative to git, gets discussed a lot these days (1, 2, 3, 4) and is often pitched as a solution. While I’m very sold on the problems jj is trying to solve, the way it solves them hasn’t quite hit home with me. Every 3 months, for the last 1.5 years, I try it out for a few days, really trying to make it part of my workflow but eventually I give up and go back to git.1 That’s where git history comes in. It’s an experimental command that arrived across two releases, 2.54 (April, reword and split subcommands) and 2.55 (June, fixup subcommand). It got a flurry of attention on each release day, and then, as far as I can tell, not much community discussion since. Which is a shame, because IMO it already delivers several of the benefits people tout for jj without needing to switch your whole workflow. And the cool thing is that it’s part of the core git distribution, so you can try it without installing anything. git history fixup fixes an old commit that has something wrong in it, then autorebases all your branches to match. You stage the fix as usual with git add, then run git history fixup <commit> to fold those staged changes into the target commit. It’s like a git commit --fixup plus an autosquash rebase but with the extra magic that it also updates any other branch which contained that commit. That last part goes further than git rebase --update-refs, which only moves refs sitting inside the range you’re actively rebasing. git history instead finds and rewrites every local branch descended from the commit (while also having an option to limit it to only the current branch). On the other hand it does not work in the presence of merge commits which, for some usages of git, is going to be a dealbreaker. B* is B with the fix folded in. Rewriting a commit gives it a new hash, so C and D are automatically re-created on top as C* and D*, and the feat-1 and feat-2 branch tips move with them. The most important property, common to all three commands, is that it’s atomic: it never leaves your tree in a half-broken state. It manages this by refusing any operation that could produce a conflict.