Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase
- From
- Antonio Mennillo <antoniomennillo87@gmail.com>
- Date
- Sep 19, 2025, 21:14 UTC
- Message-ID
- <CACEPZDWgoa18LAhzOzu__jy8ssgR8-PmZb6gmDzCS=q6f25=wA@mail.gmail.com>
- In-Reply-To
- <aMyGHriNpmekoqD5@fruit.crustytoothpaste.net>
Hi Brian,
Thank you again for the feedback, it gave me a fundamental piece of puzzle about Git's design constraints that I hadn't fully considered.
You're right about tree-sitter being inappropriate for Git core. That approach is too narrow, covering only specific languages. My v2.0.0 already prioritizes Git's existing plumbing to track content changes universally, with tree-sitter as an optional enhancement. This stays content-agnostic and removes that which I can define as a source of nondeterminism and heuristic complexity that it's elegant to avoid.
To summarize the mechanism:
1) Squash the feature branch into a single commit: temporarily compressing the branch makes all internal semantic dependencies visible at once, avoiding cascade conflicts during step-by-step rebase with non-atomic commits.
2) Resolve conflicts once with full context: the semantic issues are "suspended" in this unified state where everything is coherent.
3) Restore original commits by replaying saved diffs: using a hash-based approach with git's native diff/apply machinery, the tool remains file-type agnostic. The restored commits may still break individually (e.g., won't compile in isolation), but the branch is now rebased with granular history preserved.
To address your header file example specifically: when common.h no longer includes stdlib.h after rebasing onto main, you would indeed need to add #include <stdlib.h> during conflict resolution. With my tool, you'd add this include once while resolving the squashed commit's conflicts. The fix then persists through all restored commits - so instead of potentially fixing the same missing header multiple times across commits during a normal rebase, you handle it once. You're correct that the tool cannot determine which specific commit should ideally contain this fix (that would require compilation testing), but it prevents the cascading resolution burden while preserving the granular history.
The key point: this doesn't fix non-atomic commits. The semantic problems remain if you examine individual commits. But it prevents these issues from causing redundant conflict resolution during rebase by compressing the timeline of the branch in a single instant "t" where the semantic non-atomic misalignment appears simultaneously.
The tool trades ideal per-commit correctness for efficiency in real-world scenarios where atomic commits aren't always achievable (junior developers, emergency fixes, legacy codebases).
Regarding expectations: documentation will be clear that this only reduces redundant conflict resolution. It doesn't make commits atomic or understand semantics. It manages semantic inconsistency by compression, intended as reduced dimensionality. The defects remain in history but no longer interfere with rebasing.
I apologize if the code evolution seems rushed - v2.0.0 was developed quickly to establish a working implementation of the core concept. For v3.0.0, I plan to remove tree-sitter entirely to achieve complete language-agnosticism. I'll also need to properly evaluate edge cases like submodules and merge commits through integration testing, though the squash operation should hopefully be quite independent from these structures. My testing focus will be on ensuring determinism, proper rollback capabilities, and full idempotency across all supported scenarios.
The current implementation is available at https://github.com/anthem87/clean-rebase/tree/v2.0.0 if you'd like to explore the approach.
Thanks again for the patience and the attention.
Best regards, Antonio
Il giorno ven 19 set 2025 alle ore 00:22 brian m. carlson <sandals@crustytoothpaste.net> ha scritto:
Show 101 quoted lines
> > On 2025-09-18 at 17:02:59, Antonio Mennillo wrote: > > Hi Brian, > > > > Thank you for the thoughtful response, and please excuse me if I am a > > bit clumsy — > > this is my first interaction with the Git community. > > > > > Usually we would recommend that each commit be atomic... > > > > I perfectly understand this best practice. In my context, working with > > many junior developers, > > it is not always possible to enforce atomic commits. In some > > “emergency” situations we > > squashed just to make progress, even if that meant losing granularity. > > Yeah, I've seen that at previous employers. Even where I work now, I > definitely see differing levels of interest in atomic commits with nice > commit messages. > > > Your explanation really helped me see things more clearly, and I appreciated > > the constructive (non-toxic) way you put it. > > Well, thank you. > > > Yes, rerere helps with repeated textual conflicts, but the scenario I > > worry about > > is semantic: if commit M changes an interface and commit N implements it, then > > changing M during rebase can make N semantically invalid. rerere > > cannot solve that > > — it remembers markers, but cannot understand that “this > > implementation no longer > > matches the interface.” > > Yes, this is effectively unavoidable because Git doesn't know the > structure of your code or data. For instance, in Rust, I could add a > new mandatory-to-implement method to a trait and then all > implementations would need an update. > > > That feedback inspired me to improve the tool. Version 2.0.0 > > (literally born today, > > thanks to your comment) now works differently: > > > > 1. Temporarily squash the feature branch (so the rebase is effectively `1/1`) > > 2. Resolve conflicts once, with the full final context > > 3. Automatically re-expand the squash by replaying saved diffs, restoring > > the original commits (with new hashes, as expected in any rebase) > > You haven't described exactly how this works, but it looks like it > performs a fixed set of transforms (e.g., symbol renames). That can be > useful, but it isn't sufficient in a lot of cases. > > For instance, in some work on Git I'm doing, I need to include a header > file for a function. It was previously included in another header, but > my rebase onto the main branch had it removed, so I need to add it into > the C file I'm working on. The only way I can 100% know which commit > needs that change programmatically is to try to compile on each commit > until it fails, and insert it there. This, of course, requires atomic > commits. > > In a lot of cases, I use git-autofixup[0], which looks at changed lines > in the diff to determine which commit to squash the changes into. It's a > great idea, but it has limitations, since sometimes a change logically > attaches to a commit but isn't close to it in the context of the diff > (such as my header inclusion change above), and sometimes I've changed > similar lines in an earlier commit A (where my change should be > squashed) as well as in later commit B, yet it gets attributed to B > accidentally. It's convenient and generally works well, but I have to > be careful not to trust it entirely. > > > I would be very interested in whether the community sees this worth exploring > > as an **experimental rebase strategy** (something like `git rebase > > --squash-restore`), > > or if it overlaps with existing mechanisms I am not aware of. > > I think the fact that it adds a dependency on tree-sitter is kind of a > non-starter for us. We're currently having a mildly contentious > discussion over the use of Rust in Git and it won't be mandatory until > Git 3.0. > > It certainly covers some use cases, but it looks like it only handles > deletions, file renames, and symbol renames, and the latter only for a > small set of file types. There's a lot of cases that it doesn't handle, > and I'm afraid that people will end up disappointed by expecting it to > magically handle a lot more than it does. That doesn't mean it isn't a > useful and valuable tool for many use cases, but users may have much > higher expectations that it will magically solve their problems when > that isn't the case. I have some tools that I've written myself that > are like that, for instance. > > I'll also point out that we try to avoid hard-coding known languages. > People use lots of languages with Git, some of which are not even known > to Vim, which has an exceptionally complete list of file types and > syntax highlighting. People also do non-code things with Git; for > instance, I store my creative writing in a repository, and other people > use Git for working on documentation or technical books. As much as > possible, we want tooling that works for at least generic text files and > ideally, all files. > -- > brian m. carlson (they/them) > Toronto, Ontario, CA