{"thread":{"id":"64156","subject":"[RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","startedAt":"2025-09-16T21:40:24Z","lastAt":"2025-09-23T23:34:25Z","messageCount":8,"participants":["Antonio Mennillo","brian m. carlson","Elijah Newren"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"526504","messageId":"CACEPZDXGGn0S_8PpEc=BVHhvyuZhWfiDmbxNOK7iPWJOj1jrXg@mail.gmail.com","threadId":"64156","inReplyTo":null,"subject":"[RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"Antonio Mennillo","fromEmail":"antoniomennillo87@gmail.com","sentAt":"2025-09-16T21:39:46Z","receivedAt":"2025-09-16T21:40:24Z","isPatch":false,"sender":{"key":"antoniomennillo87@gmail.com","avatar":null},"body":"Hi Git community,\n\nI’m Antonio Mennillo, a self-taught developer (6 years). I’d like to ask for\ntechnical feedback on a workflow issue I’ve observed during rebase, and share a\nsmall userland tool I wrote to mitigate it.\n\nProblem (observation, possibly a known limitation rather than a bug):\nWhen rebasing feature branches whose commits are semantically interdependent,\nGit replays commits one by one. In practice this can trigger a\ncascading conflict, similar to a loop. Example:\n\n - Commit 1: add interface IUserService\n - Commit 6: add UserServiceImpl (depends on 1)\n - Commit 11: change IUserService signature\n - Commits 12–15: update implementation/tests to match\n\nDuring rebase, conflicts may appear at 1 and again at 6/11, forcing the user to\nremember prior resolutions and reconstruct intent across commits. If I’m\nmischaracterizing the model, I’d appreciate a correction. I’m sharing this\nhumbly to verify whether this is expected behavior or if there is prior art I\nshould be aware of.\n\nMitigation (userland workflow): I built `git-rebase-clean`, which\nsquashes the feature branch first and then rebases. This concentrates\nconflict resolution into a single atomic step with the full final\ncontext visible. The obvious trade-off is commit history granularity:\nyou lose individual commits but gain atomic conflict resolution. In my\nexperience this reduces repeated/conflicting resolutions across\ndependent commits.\n\nUsage (data points):\n- 5+ months in develop/UAT, used in production environment today the first time\n- 30-40 feature branches processed\n- High reduction in conflict-resolution time\n- Only incidents observed: some IDEs (e.g., IntelliJ) occasionally\nfail to load all conflict markers when many conflicts concentrate in\nthe single squashed commit, requiring fallback to command-line\nresolution\n\nRepository: https://github.com/anthem87/clean-rebase\nLicense: GNU AGPL-3.0-or-later for v2.x (earlier tags remain MIT for\nhistorical reasons)\n\nQuestions:\n1) Is this \"semantic dependency\" pain point considered an expected\nlimitation of git rebase, or perhaps an inherent trade-off in the\ndesign?\n2) Would you see value in documenting this squash-then-rebase pattern\nin Git's guidance (or `contrib/`), as one possible workaround?\n3) Are there existing internals, extensions, or directions you\nrecommend for handling dependent commits more gracefully?\n\nContext note (authorship & licensing):\nThere has been growing interest around this tool after real-world use\nwithin my company. To avoid misunderstandings about authorship and\nusage rights, I transitioned new releases to GNU AGPL-3.0 while older\ntags remain MIT; my goal is to preserve clear attribution and set\nappropriate terms. If this licensing discussion is out of scope for\nthe list, please disregard this note.\n\nI’m happy to adapt the tool to community standards if there is interest.\nThank you for Git and for your time.\n\nBest regards,\nAntonio Mennillo\nNaples, Italy\n"},{"id":"526511","messageId":"aMnhSm5QSdRwiJds@fruit.crustytoothpaste.net","threadId":"64156","inReplyTo":"CACEPZDXGGn0S_8PpEc=BVHhvyuZhWfiDmbxNOK7iPWJOj1jrXg@mail.gmail.com","subject":"Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2025-09-16T22:14:34Z","receivedAt":"2025-09-16T22:14:42Z","isPatch":false,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2025-09-16 at 21:39:46, Antonio Mennillo wrote:\n> Hi Git community,\n\nHi,\n\n> Problem (observation, possibly a known limitation rather than a bug):\n> When rebasing feature branches whose commits are semantically interdependent,\n> Git replays commits one by one. In practice this can trigger a\n> cascading conflict, similar to a loop. Example:\n> \n>  - Commit 1: add interface IUserService\n>  - Commit 6: add UserServiceImpl (depends on 1)\n>  - Commit 11: change IUserService signature\n>  - Commits 12–15: update implementation/tests to match\n\nUsually we would recommend that each commit be atomic.  That is, each\ncommit should compile and pass all of the tests, so commits 12–15 would\nbe part of commit 11.\n\n> During rebase, conflicts may appear at 1 and again at 6/11, forcing the user to\n> remember prior resolutions and reconstruct intent across commits. If I’m\n> mischaracterizing the model, I’d appreciate a correction. I’m sharing this\n> humbly to verify whether this is expected behavior or if there is prior art I\n> should be aware of.\n\nAre you familiar with `git rerere`?  I have just done a complicated\nrebase of this sort and it remembers resolutions for you so you don't\nhave to.\n\n> Mitigation (userland workflow): I built `git-rebase-clean`, which\n> squashes the feature branch first and then rebases. This concentrates\n> conflict resolution into a single atomic step with the full final\n> context visible. The obvious trade-off is commit history granularity:\n> you lose individual commits but gain atomic conflict resolution. In my\n> experience this reduces repeated/conflicting resolutions across\n> dependent commits.\n\nI think most people on this list will consider losing the history\nunacceptable, so I don't think this is a thing we'll want to encourage.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"526711","messageId":"CACEPZDU2Bu2c9RFBwD499-UUNyQGuRRXNbMx0aBrqwGoc=4EhQ@mail.gmail.com","threadId":"64156","inReplyTo":"aMnhSm5QSdRwiJds@fruit.crustytoothpaste.net","subject":"Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"Antonio Mennillo","fromEmail":"antoniomennillo87@gmail.com","sentAt":"2025-09-18T17:02:59Z","receivedAt":"2025-09-18T17:03:38Z","isPatch":false,"sender":{"key":"antoniomennillo87@gmail.com","avatar":null},"body":"Hi Brian,\n\nThank you for the thoughtful response, and please excuse me if I am a\nbit clumsy —\nthis is my first interaction with the Git community.\n\n> Usually we would recommend that each commit be atomic...\n\nI perfectly understand this best practice. In my context, working with\nmany junior developers,\nit is not always possible to enforce atomic commits. In some\n“emergency” situations we\nsquashed just to make progress, even if that meant losing granularity.\n\nYour explanation really helped me see things more clearly, and I appreciated\nthe constructive (non-toxic) way you put it.\n\n> Are you familiar with `git rerere`?...\n\nYes, rerere helps with repeated textual conflicts, but the scenario I\nworry about\nis semantic: if commit M changes an interface and commit N implements it, then\nchanging M during rebase can make N semantically invalid. rerere\ncannot solve that\n— it remembers markers, but cannot understand that “this\nimplementation no longer\nmatches the interface.”\n\n> I think most people on this list will consider losing the history unacceptable...\n\nThat feedback inspired me to improve the tool. Version 2.0.0\n(literally born today,\nthanks to your comment) now works differently:\n\n1. Temporarily squash the feature branch (so the rebase is effectively `1/1`)\n2. Resolve conflicts once, with the full final context\n3. Automatically re-expand the squash by replaying saved diffs, restoring\n   the original commits (with new hashes, as expected in any rebase)\n\nIn integration tests this preserves detailed history while avoiding cascading\nconflicts. All 23 tests currently pass (including empty commits, binary files,\nspecial characters, conflict abort/continue). Of course, real-world\nusage is still pending.\n\nThis workflow essentially resolves conflicts with full context, then\nreconstructs\ngranular history — a kind of “best of both worlds.”\n\nI would be very interested in whether the community sees this worth exploring\nas an **experimental rebase strategy** (something like `git rebase\n--squash-restore`),\nor if it overlaps with existing mechanisms I am not aware of.\n\nCode: https://github.com/anthem87/clean-rebase/tree/v2.0.0\n\nBest regards,\nAntonio\nAntonio\n\nIl giorno mer 17 set 2025 alle ore 00:14 brian m. carlson\n<sandals@crustytoothpaste.net> ha scritto:\n>\n> On 2025-09-16 at 21:39:46, Antonio Mennillo wrote:\n> > Hi Git community,\n>\n> Hi,\n>\n> > Problem (observation, possibly a known limitation rather than a bug):\n> > When rebasing feature branches whose commits are semantically interdependent,\n> > Git replays commits one by one. In practice this can trigger a\n> > cascading conflict, similar to a loop. Example:\n> >\n> >  - Commit 1: add interface IUserService\n> >  - Commit 6: add UserServiceImpl (depends on 1)\n> >  - Commit 11: change IUserService signature\n> >  - Commits 12–15: update implementation/tests to match\n>\n> Usually we would recommend that each commit be atomic.  That is, each\n> commit should compile and pass all of the tests, so commits 12–15 would\n> be part of commit 11.\n>\n> > During rebase, conflicts may appear at 1 and again at 6/11, forcing the user to\n> > remember prior resolutions and reconstruct intent across commits. If I’m\n> > mischaracterizing the model, I’d appreciate a correction. I’m sharing this\n> > humbly to verify whether this is expected behavior or if there is prior art I\n> > should be aware of.\n>\n> Are you familiar with `git rerere`?  I have just done a complicated\n> rebase of this sort and it remembers resolutions for you so you don't\n> have to.\n>\n> > Mitigation (userland workflow): I built `git-rebase-clean`, which\n> > squashes the feature branch first and then rebases. This concentrates\n> > conflict resolution into a single atomic step with the full final\n> > context visible. The obvious trade-off is commit history granularity:\n> > you lose individual commits but gain atomic conflict resolution. In my\n> > experience this reduces repeated/conflicting resolutions across\n> > dependent commits.\n>\n> I think most people on this list will consider losing the history\n> unacceptable, so I don't think this is a thing we'll want to encourage.\n> --\n> brian m. carlson (they/them)\n> Toronto, Ontario, CA\n"},{"id":"526732","messageId":"aMyGHriNpmekoqD5@fruit.crustytoothpaste.net","threadId":"64156","inReplyTo":"CACEPZDU2Bu2c9RFBwD499-UUNyQGuRRXNbMx0aBrqwGoc=4EhQ@mail.gmail.com","subject":"Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2025-09-18T22:22:22Z","receivedAt":"2025-09-18T22:22:25Z","isPatch":false,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2025-09-18 at 17:02:59, Antonio Mennillo wrote:\n> Hi Brian,\n> \n> Thank you for the thoughtful response, and please excuse me if I am a\n> bit clumsy —\n> this is my first interaction with the Git community.\n> \n> > Usually we would recommend that each commit be atomic...\n> \n> I perfectly understand this best practice. In my context, working with\n> many junior developers,\n> it is not always possible to enforce atomic commits. In some\n> “emergency” situations we\n> squashed just to make progress, even if that meant losing granularity.\n\nYeah, I've seen that at previous employers.  Even where I work now, I\ndefinitely see differing levels of interest in atomic commits with nice\ncommit messages.\n\n> Your explanation really helped me see things more clearly, and I appreciated\n> the constructive (non-toxic) way you put it.\n\nWell, thank you.\n\n> Yes, rerere helps with repeated textual conflicts, but the scenario I\n> worry about\n> is semantic: if commit M changes an interface and commit N implements it, then\n> changing M during rebase can make N semantically invalid. rerere\n> cannot solve that\n> — it remembers markers, but cannot understand that “this\n> implementation no longer\n> matches the interface.”\n\nYes, this is effectively unavoidable because Git doesn't know the\nstructure of your code or data.  For instance, in Rust, I could add a\nnew mandatory-to-implement method to a trait and then all\nimplementations would need an update.\n\n> That feedback inspired me to improve the tool. Version 2.0.0\n> (literally born today,\n> thanks to your comment) now works differently:\n> \n> 1. Temporarily squash the feature branch (so the rebase is effectively `1/1`)\n> 2. Resolve conflicts once, with the full final context\n> 3. Automatically re-expand the squash by replaying saved diffs, restoring\n>    the original commits (with new hashes, as expected in any rebase)\n\nYou haven't described exactly how this works, but it looks like it\nperforms a fixed set of transforms (e.g., symbol renames).  That can be\nuseful, but it isn't sufficient in a lot of cases.\n\nFor instance, in some work on Git I'm doing, I need to include a header\nfile for a function.  It was previously included in another header, but\nmy rebase onto the main branch had it removed, so I need to add it into\nthe C file I'm working on.  The only way I can 100% know which commit\nneeds that change programmatically is to try to compile on each commit\nuntil it fails, and insert it there.  This, of course, requires atomic\ncommits.\n\nIn a lot of cases, I use git-autofixup[0], which looks at changed lines\nin the diff to determine which commit to squash the changes into. It's a\ngreat idea, but it has limitations, since sometimes a change logically\nattaches to a commit but isn't close to it in the context of the diff\n(such as my header inclusion change above), and sometimes I've changed\nsimilar lines in an earlier commit A (where my change should be\nsquashed) as well as in later commit B, yet it gets attributed to B\naccidentally.  It's convenient and generally works well, but I have to\nbe careful not to trust it entirely.\n\n> I would be very interested in whether the community sees this worth exploring\n> as an **experimental rebase strategy** (something like `git rebase\n> --squash-restore`),\n> or if it overlaps with existing mechanisms I am not aware of.\n\nI think the fact that it adds a dependency on tree-sitter is kind of a\nnon-starter for us.  We're currently having a mildly contentious\ndiscussion over the use of Rust in Git and it won't be mandatory until\nGit 3.0.\n\nIt certainly covers some use cases, but it looks like it only handles\ndeletions, file renames, and symbol renames, and the latter only for a\nsmall set of file types.  There's a lot of cases that it doesn't handle,\nand I'm afraid that people will end up disappointed by expecting it to\nmagically handle a lot more than it does.  That doesn't mean it isn't a\nuseful and valuable tool for many use cases, but users may have much\nhigher expectations that it will magically solve their problems when\nthat isn't the case.  I have some tools that I've written myself that\nare like that, for instance.\n\nI'll also point out that we try to avoid hard-coding known languages.\nPeople use lots of languages with Git, some of which are not even known\nto Vim, which has an exceptionally complete list of file types and\nsyntax highlighting.  People also do non-code things with Git; for\ninstance, I store my creative writing in a repository, and other people\nuse Git for working on documentation or technical books.  As much as\npossible, we want tooling that works for at least generic text files and\nideally, all files.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"526820","messageId":"CACEPZDWgoa18LAhzOzu__jy8ssgR8-PmZb6gmDzCS=q6f25=wA@mail.gmail.com","threadId":"64156","inReplyTo":"aMyGHriNpmekoqD5@fruit.crustytoothpaste.net","subject":"Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"Antonio Mennillo","fromEmail":"antoniomennillo87@gmail.com","sentAt":"2025-09-19T21:14:51Z","receivedAt":"2025-09-19T21:15:30Z","isPatch":false,"sender":{"key":"antoniomennillo87@gmail.com","avatar":null},"body":"Hi Brian,\n\nThank you again for the feedback, it gave me a fundamental piece of\npuzzle about Git's design constraints that I hadn't fully considered.\n\nYou're right about tree-sitter being inappropriate for Git core. That\napproach is too narrow, covering only specific languages. My v2.0.0\nalready prioritizes Git's existing plumbing to track content changes\nuniversally, with tree-sitter as an optional enhancement. This stays\ncontent-agnostic and removes that which I can define as a source of\nnondeterminism and heuristic complexity that it's elegant to avoid.\n\nTo summarize the mechanism:\n\n1) Squash the feature branch into a single commit: temporarily\ncompressing the branch makes all internal semantic dependencies\nvisible at once, avoiding cascade conflicts during step-by-step rebase\nwith non-atomic commits.\n\n2) Resolve conflicts once with full context: the semantic issues are\n\"suspended\" in this unified state where everything is coherent.\n\n3) Restore original commits by replaying saved diffs: using a\nhash-based approach with git's native diff/apply machinery, the tool\nremains file-type agnostic. The restored commits may still break\nindividually (e.g., won't compile in isolation), but the branch is now\nrebased with granular history preserved.\n\nTo address your header file example specifically: when common.h no\nlonger includes stdlib.h after rebasing onto main, you would indeed\nneed to add #include <stdlib.h> during conflict resolution. With my\ntool, you'd add this include once while resolving the squashed\ncommit's conflicts. The fix then persists through all restored commits\n- so instead of potentially fixing the same missing header multiple\ntimes across commits during a normal rebase, you handle it once.\nYou're correct that the tool cannot determine which specific commit\nshould ideally contain this fix (that would require compilation\ntesting), but it prevents the cascading resolution burden while\npreserving the granular history.\n\n\nThe key point: this doesn't fix non-atomic commits. The semantic\nproblems remain if you examine individual commits. But it prevents\nthese issues from causing redundant conflict resolution during rebase\nby compressing the timeline of the branch in a single instant \"t\"\nwhere the semantic non-atomic misalignment appears simultaneously.\n\nThe tool trades ideal per-commit correctness for efficiency in\nreal-world scenarios where atomic commits aren't always achievable\n(junior developers, emergency fixes, legacy codebases).\n\nRegarding expectations: documentation will be clear that this only\nreduces redundant conflict resolution. It doesn't make commits atomic\nor understand semantics. It manages semantic inconsistency by\ncompression, intended as reduced dimensionality. The defects remain in\nhistory but no longer interfere with rebasing.\n\nI apologize if the code evolution seems rushed - v2.0.0 was developed\nquickly to establish a working implementation of the core concept. For\nv3.0.0, I plan to remove tree-sitter entirely to achieve complete\nlanguage-agnosticism. I'll also need to properly evaluate edge cases\nlike submodules and merge commits through integration testing, though\nthe squash operation should hopefully be quite independent from these\nstructures. My testing focus will be on ensuring determinism, proper\nrollback capabilities, and full idempotency across all supported\nscenarios.\n\nThe current implementation is available at\nhttps://github.com/anthem87/clean-rebase/tree/v2.0.0 if you'd like to\nexplore the approach.\n\nThanks again for the patience and the attention.\n\nBest regards,\nAntonio\n\n\nIl giorno ven 19 set 2025 alle ore 00:22 brian m. carlson\n<sandals@crustytoothpaste.net> ha scritto:\n>\n> On 2025-09-18 at 17:02:59, Antonio Mennillo wrote:\n> > Hi Brian,\n> >\n> > Thank you for the thoughtful response, and please excuse me if I am a\n> > bit clumsy —\n> > this is my first interaction with the Git community.\n> >\n> > > Usually we would recommend that each commit be atomic...\n> >\n> > I perfectly understand this best practice. In my context, working with\n> > many junior developers,\n> > it is not always possible to enforce atomic commits. In some\n> > “emergency” situations we\n> > squashed just to make progress, even if that meant losing granularity.\n>\n> Yeah, I've seen that at previous employers.  Even where I work now, I\n> definitely see differing levels of interest in atomic commits with nice\n> commit messages.\n>\n> > Your explanation really helped me see things more clearly, and I appreciated\n> > the constructive (non-toxic) way you put it.\n>\n> Well, thank you.\n>\n> > Yes, rerere helps with repeated textual conflicts, but the scenario I\n> > worry about\n> > is semantic: if commit M changes an interface and commit N implements it, then\n> > changing M during rebase can make N semantically invalid. rerere\n> > cannot solve that\n> > — it remembers markers, but cannot understand that “this\n> > implementation no longer\n> > matches the interface.”\n>\n> Yes, this is effectively unavoidable because Git doesn't know the\n> structure of your code or data.  For instance, in Rust, I could add a\n> new mandatory-to-implement method to a trait and then all\n> implementations would need an update.\n>\n> > That feedback inspired me to improve the tool. Version 2.0.0\n> > (literally born today,\n> > thanks to your comment) now works differently:\n> >\n> > 1. Temporarily squash the feature branch (so the rebase is effectively `1/1`)\n> > 2. Resolve conflicts once, with the full final context\n> > 3. Automatically re-expand the squash by replaying saved diffs, restoring\n> >    the original commits (with new hashes, as expected in any rebase)\n>\n> You haven't described exactly how this works, but it looks like it\n> performs a fixed set of transforms (e.g., symbol renames).  That can be\n> useful, but it isn't sufficient in a lot of cases.\n>\n> For instance, in some work on Git I'm doing, I need to include a header\n> file for a function.  It was previously included in another header, but\n> my rebase onto the main branch had it removed, so I need to add it into\n> the C file I'm working on.  The only way I can 100% know which commit\n> needs that change programmatically is to try to compile on each commit\n> until it fails, and insert it there.  This, of course, requires atomic\n> commits.\n>\n> In a lot of cases, I use git-autofixup[0], which looks at changed lines\n> in the diff to determine which commit to squash the changes into. It's a\n> great idea, but it has limitations, since sometimes a change logically\n> attaches to a commit but isn't close to it in the context of the diff\n> (such as my header inclusion change above), and sometimes I've changed\n> similar lines in an earlier commit A (where my change should be\n> squashed) as well as in later commit B, yet it gets attributed to B\n> accidentally.  It's convenient and generally works well, but I have to\n> be careful not to trust it entirely.\n>\n> > I would be very interested in whether the community sees this worth exploring\n> > as an **experimental rebase strategy** (something like `git rebase\n> > --squash-restore`),\n> > or if it overlaps with existing mechanisms I am not aware of.\n>\n> I think the fact that it adds a dependency on tree-sitter is kind of a\n> non-starter for us.  We're currently having a mildly contentious\n> discussion over the use of Rust in Git and it won't be mandatory until\n> Git 3.0.\n>\n> It certainly covers some use cases, but it looks like it only handles\n> deletions, file renames, and symbol renames, and the latter only for a\n> small set of file types.  There's a lot of cases that it doesn't handle,\n> and I'm afraid that people will end up disappointed by expecting it to\n> magically handle a lot more than it does.  That doesn't mean it isn't a\n> useful and valuable tool for many use cases, but users may have much\n> higher expectations that it will magically solve their problems when\n> that isn't the case.  I have some tools that I've written myself that\n> are like that, for instance.\n>\n> I'll also point out that we try to avoid hard-coding known languages.\n> People use lots of languages with Git, some of which are not even known\n> to Vim, which has an exceptionally complete list of file types and\n> syntax highlighting.  People also do non-code things with Git; for\n> instance, I store my creative writing in a repository, and other people\n> use Git for working on documentation or technical books.  As much as\n> possible, we want tooling that works for at least generic text files and\n> ideally, all files.\n> --\n> brian m. carlson (they/them)\n> Toronto, Ontario, CA\n"},{"id":"526869","messageId":"CABPp-BEU-DzNtyeC0j9OKJhqnmOw18TXvCwmmPjZL33Hp=gSUQ@mail.gmail.com","threadId":"64156","inReplyTo":"CACEPZDWgoa18LAhzOzu__jy8ssgR8-PmZb6gmDzCS=q6f25=wA@mail.gmail.com","subject":"Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2025-09-20T19:13:57Z","receivedAt":"2025-09-20T19:14:09Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Fri, Sep 19, 2025 at 2:17 PM Antonio Mennillo\n<antoniomennillo87@gmail.com> wrote:\n>\n> Hi Brian,\n>\n> Thank you again for the feedback, it gave me a fundamental piece of\n> puzzle about Git's design constraints that I hadn't fully considered.\n>\n> You're right about tree-sitter being inappropriate for Git core. That\n> approach is too narrow, covering only specific languages. My v2.0.0\n> already prioritizes Git's existing plumbing to track content changes\n> universally, with tree-sitter as an optional enhancement. This stays\n> content-agnostic and removes that which I can define as a source of\n> nondeterminism and heuristic complexity that it's elegant to avoid.\n>\n> To summarize the mechanism:\n>\n> 1) Squash the feature branch into a single commit: temporarily\n> compressing the branch makes all internal semantic dependencies\n> visible at once, avoiding cascade conflicts during step-by-step rebase\n> with non-atomic commits.\n>\n> 2) Resolve conflicts once with full context: the semantic issues are\n> \"suspended\" in this unified state where everything is coherent.\n>\n> 3) Restore original commits by replaying saved diffs: using a\n> hash-based approach with git's native diff/apply machinery, the tool\n> remains file-type agnostic. The restored commits may still break\n> individually (e.g., won't compile in isolation), but the branch is now\n> rebased with granular history preserved.\n\nI don't understand how this is possible, other than in trivial cases.\nLots of questions:\n\nHow exactly is replaying the saved diffs supposed to work?  Replaying\nthose diffs would run into conflicts, otherwise you wouldn't have been\ntrying to search for a modified strategy in the first place.  How do\nyou resolve those?  Are you assuming all conflicts are orthogonal,\ni.e. that the conflicts from patches A and B are never on overlapping\nor even nearby lines to each other?  In such a case, there wouldn't be\nany \"cascade of conflicts\" to resolve anyway, so the approach would be\nunnecessary.  And if this isn't true, then I don't see how you can\nresolve all the conflicts at once and magically split them out into\nseparate commits and replay things in a way that doesn't have\nconflicts -- at least not without further human intervention.\n\nAnd that was focusing on content conflicts.  What do you do with\nnon-content conflicts, such as file mode differences, file/directory\nconflicts, symlink/submodule conflicts, rename conflict, directory\nrename conflict, etc.?\n\nBut going back to content conflicts, are you somehow sidestepping the\nneed to assume orthogonality?  Perhaps when you attempt to replay and\nhit a conflict, you just take the end-result for that hunk range for\nthe first patch that touches it, and for any other patch that touches\nthat hunk range you simply discard the changes for those since they've\nbeen squashed into the first patch?  Or something similar?\n\nAs another way to look at this problem, what about cases where\nindividual commits being replayed will conflict, but merging the end\nresult has no conflicts?  (In other words, only the intermediate\nstates had a conflict of some type -- a simple example of this is\nsomeone making a change and then shortly later reverting it.)  In such\na case, by resolving conflicts only at the end, there was no\nresolution of the conflicts experienced by those intermediate states.\nWhat do you do there?\n\nWhat about cases like\nhttps://git-scm.com/docs/gitfaq#Documentation/gitfaq.txt-IfImakeachangeontwobranchesbutrevertitononewhydoesthemergeofthosebranchesincludethechange\n(both sides of history make a change, and one side also reverts that\nchange) -- by merging the squashed result, you'd end up with the\nchange being applied, but when you try to split it out into separate\ncommits, you have \"apply the change\" followed by \"revert the change\"\n-- how does that result in the change being applied in the end result?\n\nAre there cases where your algorithm feels indistinguishable from\nsquashing all the commits, having the user resolve conflicts, and then\nrandomly assigning the hunks to a completely new set of commits\n(beyond the obvious differences in commit messages and perhaps number\nof commits)?  Every algorithm I can think of for automatically playing\nexisting commits while only resolving conflicts at the end would seem\nto sometimes suffer from this kind of downside, but it's entirely\npossible I'm only thinking of a few brute force algorithms and I'm\nmissing something clever that you might be doing.\n\n> To address your header file example specifically: when common.h no\n> longer includes stdlib.h after rebasing onto main, you would indeed\n> need to add #include <stdlib.h> during conflict resolution. With my\n> tool, you'd add this include once while resolving the squashed\n> commit's conflicts. The fix then persists through all restored commits\n> - so instead of potentially fixing the same missing header multiple\n> times across commits during a normal rebase, you handle it once.\n> You're correct that the tool cannot determine which specific commit\n> should ideally contain this fix (that would require compilation\n> testing), but it prevents the cascading resolution burden while\n> preserving the granular history.\n\nI think there may have been a misunderstanding here.  The purpose\nwasn't granular history, it was meaningful logical commits; those tend\nto come in granular sizes, but granularity was more of a side-effect\nrather than the purpose. Having a disfigured granular history is quite\ndifferent than meaningful logical commits.\n\n> The key point: this doesn't fix non-atomic commits. The semantic\n> problems remain if you examine individual commits. But it prevents\n> these issues from causing redundant conflict resolution during rebase\n> by compressing the timeline of the branch in a single instant \"t\"\n> where the semantic non-atomic misalignment appears simultaneously.\n>\n> The tool trades ideal per-commit correctness for efficiency in\n> real-world scenarios where atomic commits aren't always achievable\n> (junior developers, emergency fixes, legacy codebases).\n\nI'm pretty biased here, but let me take exception with your wording\nthat these are not achievable cases.  I'm going to push back on that,\nrather strongly.  In fact, I'd go further and say that legacy\ncodebases are where logical small commits make the *most* sense\n(sometimes with small new projects the \"why\" and the \"what\" you're\ndoing is obvious and so there's not much to write in a commit message,\nand anyone can follow along, whereas in a legacy codebase you need to\ncarefully document why you are making changes and ensure each change\nin isolation makes sense so others can figure out why each line of\ncode does what it does).  In regards to junio developers, letting\njunior developers develop bad habits (and even facilitating such) just\nleads to them later being senior developers with bad habits; I think\nit'd be better to take the time to coach them on proper practices\ninstead.  And in the remaining case, what you describe sounds to me\nlike a rushed emergency fix, and I feel like rushed emergency fixes\nresult in more emergencies later.\n\nI understand that many are reluctant to put in the effort to make\nclear, logical commits, but I feel that each of your cases are\nexamples of unwillingness or lack of training, not an actual\ninability.\n\nAs noted above, I'm quite biased here, and would rather encourage\npeople to make clear, logical, easy-to-follow-and-review-years-later\nsteps that are documented as commits.  I think tools which encourage\nor facilitate doing the opposite are things that should be shunned.\n(I hate squash-merging in GitHub for this reason, and GitHub's lack of\nfocus on treating commit messages as important first-class material\nfor review has long bothered me deeply, as I've commented elsewhere.)\nOthers are free to disagree with me and pursue their own path, but\nthat's my position.\n\n> Regarding expectations: documentation will be clear that this only\n> reduces redundant conflict resolution. It doesn't make commits atomic\n> or understand semantics. It manages semantic inconsistency by\n> compression, intended as reduced dimensionality. The defects remain in\n> history but no longer interfere with rebasing.\n>\n> I apologize if the code evolution seems rushed - v2.0.0 was developed\n> quickly to establish a working implementation of the core concept. For\n> v3.0.0, I plan to remove tree-sitter entirely to achieve complete\n> language-agnosticism. I'll also need to properly evaluate edge cases\n> like submodules and merge commits through integration testing, though\n> the squash operation should hopefully be quite independent from these\n> structures. My testing focus will be on ensuring determinism, proper\n> rollback capabilities, and full idempotency across all supported\n> scenarios.\n>\n> The current implementation is available at\n> https://github.com/anthem87/clean-rebase/tree/v2.0.0 if you'd like to\n> explore the approach.\n>\n> Thanks again for the patience and the attention.\n\nI hope my email doesn't come across as too negative.  I'm a big fan of\nexploring and attempting new things.  While I am biased against tools\nthat work against logical & bisectable changes, and I'm worried your\ntool is going to face an awful lot of problematic cases if you\ncontinue with it, hopefully my email provides more context for you and\nalerts you to some cases you might need to investigate if you want to\npush the tool further.\n\nEven if I am not in the camp that'd be interested in using your tool,\nI am very curious if you have clever ideas for any of the special\ncases I listed above and how to handle them; sometimes any such ideas\nmight have ways of being wielded elsewhere.\n"},{"id":"526895","messageId":"CACEPZDUYKhRBB9Te+dn8fdgDtuo_gHdmxqGBMvp+xG+GEiYc3g@mail.gmail.com","threadId":"64156","inReplyTo":"CABPp-BEU-DzNtyeC0j9OKJhqnmOw18TXvCwmmPjZL33Hp=gSUQ@mail.gmail.com","subject":"Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"Antonio Mennillo","fromEmail":"antoniomennillo87@gmail.com","sentAt":"2025-09-21T14:06:12Z","receivedAt":"2025-09-21T14:06:51Z","isPatch":false,"sender":{"key":"antoniomennillo87@gmail.com","avatar":null},"body":"Hi,\n\nSorry for the duplicate earlier, I realized my email was rejected by\nthe mailing list for being HTML, I have no pc available now. Resending\nin plain text\n\nThank you again for your detailed and thoughtful response. I apologize\nfor my delayed reply. I'm currently traveling and away from my\ndevelopment environment, which is making it difficult to properly\naddress all the important technical points you've raised.\n\nI don't find your emails as negative, quite the opposite, I repeat:\nit's precious insight and experience sharing.\n\nPlease expect my detailed response by Wednesday at the latest. I\nappreciate your patience and the time you've taken to engage so deeply\nwith this proposal.\n\nBest regards,\nAntonio\n\n\nIl giorno sab 20 set 2025 alle ore 21:14 Elijah Newren\n<newren@gmail.com> ha scritto:\n>\n> Hi,\n>\n> On Fri, Sep 19, 2025 at 2:17 PM Antonio Mennillo\n> <antoniomennillo87@gmail.com> wrote:\n> >\n> > Hi Brian,\n> >\n> > Thank you again for the feedback, it gave me a fundamental piece of\n> > puzzle about Git's design constraints that I hadn't fully considered.\n> >\n> > You're right about tree-sitter being inappropriate for Git core. That\n> > approach is too narrow, covering only specific languages. My v2.0.0\n> > already prioritizes Git's existing plumbing to track content changes\n> > universally, with tree-sitter as an optional enhancement. This stays\n> > content-agnostic and removes that which I can define as a source of\n> > nondeterminism and heuristic complexity that it's elegant to avoid.\n> >\n> > To summarize the mechanism:\n> >\n> > 1) Squash the feature branch into a single commit: temporarily\n> > compressing the branch makes all internal semantic dependencies\n> > visible at once, avoiding cascade conflicts during step-by-step rebase\n> > with non-atomic commits.\n> >\n> > 2) Resolve conflicts once with full context: the semantic issues are\n> > \"suspended\" in this unified state where everything is coherent.\n> >\n> > 3) Restore original commits by replaying saved diffs: using a\n> > hash-based approach with git's native diff/apply machinery, the tool\n> > remains file-type agnostic. The restored commits may still break\n> > individually (e.g., won't compile in isolation), but the branch is now\n> > rebased with granular history preserved.\n>\n> I don't understand how this is possible, other than in trivial cases.\n> Lots of questions:\n>\n> How exactly is replaying the saved diffs supposed to work?  Replaying\n> those diffs would run into conflicts, otherwise you wouldn't have been\n> trying to search for a modified strategy in the first place.  How do\n> you resolve those?  Are you assuming all conflicts are orthogonal,\n> i.e. that the conflicts from patches A and B are never on overlapping\n> or even nearby lines to each other?  In such a case, there wouldn't be\n> any \"cascade of conflicts\" to resolve anyway, so the approach would be\n> unnecessary.  And if this isn't true, then I don't see how you can\n> resolve all the conflicts at once and magically split them out into\n> separate commits and replay things in a way that doesn't have\n> conflicts -- at least not without further human intervention.\n>\n> And that was focusing on content conflicts.  What do you do with\n> non-content conflicts, such as file mode differences, file/directory\n> conflicts, symlink/submodule conflicts, rename conflict, directory\n> rename conflict, etc.?\n>\n> But going back to content conflicts, are you somehow sidestepping the\n> need to assume orthogonality?  Perhaps when you attempt to replay and\n> hit a conflict, you just take the end-result for that hunk range for\n> the first patch that touches it, and for any other patch that touches\n> that hunk range you simply discard the changes for those since they've\n> been squashed into the first patch?  Or something similar?\n>\n> As another way to look at this problem, what about cases where\n> individual commits being replayed will conflict, but merging the end\n> result has no conflicts?  (In other words, only the intermediate\n> states had a conflict of some type -- a simple example of this is\n> someone making a change and then shortly later reverting it.)  In such\n> a case, by resolving conflicts only at the end, there was no\n> resolution of the conflicts experienced by those intermediate states.\n> What do you do there?\n>\n> What about cases like\n> https://git-scm.com/docs/gitfaq#Documentation/gitfaq.txt-IfImakeachangeontwobranchesbutrevertitononewhydoesthemergeofthosebranchesincludethechange\n> (both sides of history make a change, and one side also reverts that\n> change) -- by merging the squashed result, you'd end up with the\n> change being applied, but when you try to split it out into separate\n> commits, you have \"apply the change\" followed by \"revert the change\"\n> -- how does that result in the change being applied in the end result?\n>\n> Are there cases where your algorithm feels indistinguishable from\n> squashing all the commits, having the user resolve conflicts, and then\n> randomly assigning the hunks to a completely new set of commits\n> (beyond the obvious differences in commit messages and perhaps number\n> of commits)?  Every algorithm I can think of for automatically playing\n> existing commits while only resolving conflicts at the end would seem\n> to sometimes suffer from this kind of downside, but it's entirely\n> possible I'm only thinking of a few brute force algorithms and I'm\n> missing something clever that you might be doing.\n>\n> > To address your header file example specifically: when common.h no\n> > longer includes stdlib.h after rebasing onto main, you would indeed\n> > need to add #include <stdlib.h> during conflict resolution. With my\n> > tool, you'd add this include once while resolving the squashed\n> > commit's conflicts. The fix then persists through all restored commits\n> > - so instead of potentially fixing the same missing header multiple\n> > times across commits during a normal rebase, you handle it once.\n> > You're correct that the tool cannot determine which specific commit\n> > should ideally contain this fix (that would require compilation\n> > testing), but it prevents the cascading resolution burden while\n> > preserving the granular history.\n>\n> I think there may have been a misunderstanding here.  The purpose\n> wasn't granular history, it was meaningful logical commits; those tend\n> to come in granular sizes, but granularity was more of a side-effect\n> rather than the purpose. Having a disfigured granular history is quite\n> different than meaningful logical commits.\n>\n> > The key point: this doesn't fix non-atomic commits. The semantic\n> > problems remain if you examine individual commits. But it prevents\n> > these issues from causing redundant conflict resolution during rebase\n> > by compressing the timeline of the branch in a single instant \"t\"\n> > where the semantic non-atomic misalignment appears simultaneously.\n> >\n> > The tool trades ideal per-commit correctness for efficiency in\n> > real-world scenarios where atomic commits aren't always achievable\n> > (junior developers, emergency fixes, legacy codebases).\n>\n> I'm pretty biased here, but let me take exception with your wording\n> that these are not achievable cases.  I'm going to push back on that,\n> rather strongly.  In fact, I'd go further and say that legacy\n> codebases are where logical small commits make the *most* sense\n> (sometimes with small new projects the \"why\" and the \"what\" you're\n> doing is obvious and so there's not much to write in a commit message,\n> and anyone can follow along, whereas in a legacy codebase you need to\n> carefully document why you are making changes and ensure each change\n> in isolation makes sense so others can figure out why each line of\n> code does what it does).  In regards to junio developers, letting\n> junior developers develop bad habits (and even facilitating such) just\n> leads to them later being senior developers with bad habits; I think\n> it'd be better to take the time to coach them on proper practices\n> instead.  And in the remaining case, what you describe sounds to me\n> like a rushed emergency fix, and I feel like rushed emergency fixes\n> result in more emergencies later.\n>\n> I understand that many are reluctant to put in the effort to make\n> clear, logical commits, but I feel that each of your cases are\n> examples of unwillingness or lack of training, not an actual\n> inability.\n>\n> As noted above, I'm quite biased here, and would rather encourage\n> people to make clear, logical, easy-to-follow-and-review-years-later\n> steps that are documented as commits.  I think tools which encourage\n> or facilitate doing the opposite are things that should be shunned.\n> (I hate squash-merging in GitHub for this reason, and GitHub's lack of\n> focus on treating commit messages as important first-class material\n> for review has long bothered me deeply, as I've commented elsewhere.)\n> Others are free to disagree with me and pursue their own path, but\n> that's my position.\n>\n> > Regarding expectations: documentation will be clear that this only\n> > reduces redundant conflict resolution. It doesn't make commits atomic\n> > or understand semantics. It manages semantic inconsistency by\n> > compression, intended as reduced dimensionality. The defects remain in\n> > history but no longer interfere with rebasing.\n> >\n> > I apologize if the code evolution seems rushed - v2.0.0 was developed\n> > quickly to establish a working implementation of the core concept. For\n> > v3.0.0, I plan to remove tree-sitter entirely to achieve complete\n> > language-agnosticism. I'll also need to properly evaluate edge cases\n> > like submodules and merge commits through integration testing, though\n> > the squash operation should hopefully be quite independent from these\n> > structures. My testing focus will be on ensuring determinism, proper\n> > rollback capabilities, and full idempotency across all supported\n> > scenarios.\n> >\n> > The current implementation is available at\n> > https://github.com/anthem87/clean-rebase/tree/v2.0.0 if you'd like to\n> > explore the approach.\n> >\n> > Thanks again for the patience and the attention.\n>\n> I hope my email doesn't come across as too negative.  I'm a big fan of\n> exploring and attempting new things.  While I am biased against tools\n> that work against logical & bisectable changes, and I'm worried your\n> tool is going to face an awful lot of problematic cases if you\n> continue with it, hopefully my email provides more context for you and\n> alerts you to some cases you might need to investigate if you want to\n> push the tool further.\n>\n> Even if I am not in the camp that'd be interested in using your tool,\n> I am very curious if you have clever ideas for any of the special\n> cases I listed above and how to handle them; sometimes any such ideas\n> might have ways of being wielded elsewhere.\n"},{"id":"527163","messageId":"CACEPZDUah8CqnMpXRNB_djp7_fWZe56XRXVF-swtk-R=XpMJOQ@mail.gmail.com","threadId":"64156","inReplyTo":"CACEPZDUYKhRBB9Te+dn8fdgDtuo_gHdmxqGBMvp+xG+GEiYc3g@mail.gmail.com","subject":"Re: [RFC] git-rebase-clean: mitigating a “semantic conflict cascade” during rebase","fromName":"Antonio Mennillo","fromEmail":"antoniomennillo87@gmail.com","sentAt":"2025-09-23T23:33:46Z","receivedAt":"2025-09-23T23:34:25Z","isPatch":false,"sender":{"key":"antoniomennillo87@gmail.com","avatar":null},"body":"Hi Brian, Elijah,\n\nI need to acknowledge that my initial reasoning around this tool\ndeveloped quite rapidly, driven both by enthusiasm for sharing results\nand by pressure from growing interest in my work network. Now, with\nmore time to reflect, I can analyze your comments more rationally\nseeing all the implications.\n\nThe \"semantic conflict\" example should be considered only as a\nlimiting use case I've seen in emergency situations. It was not\nintended as the definition of the tool, but my fault I set it up in\nthe object of the mailing list. The actual purpose is to consolidate\nrebase conflicts into a single step (one-pass conflict resolution) via\nsquash, ideally reducing the cardinality from N to 1.\nFor instance, rebasing a 20-commit feature branch with 5 conflict\npoints currently requires 5 separate git rebase --continue cycles.\nThis tool consolidates those into a single resolution step, then aims\nto reconstruct the commits. On large codebases, such as multi-modular\nJava projects, managing a rebase in an IDE becomes significantly more\nmanageable with one consolidated conflict rather than dozens, reducing\nboth cognitive load and the chance of errors.\n\nI fully understand and respect the importance of atomic commits. To be\nexplicit, this tool:\n\n- does not make non-atomic commits atomic\n- does not guarantee bisectability\n- does not replace developer discipline\n\nIt only reduces the number of conflict resolution iterations. Poor\ncommit atomicity will still widely affect the number of conflicting\nfiles. The \"temporal flattening\" I mentioned earlier refers simply to\nthis consolidation.\n\nFrom a technical standpoint, I am developing a different approach that\nproperly addresses the concerns you raised. This new implementation\nworks directly with Git's internal structures to ensure deterministic\nbehavior, specifically handling revert detection, intermediate-only\nconflicts, non-content conflicts, and attribution at appropriate\ngranularity levels.\n\nI'm targeting this weekend for a working implementation with\nintegration test coverage.\n\nThe goal remains pragmatic: helping developers handle complex rebases\nmore efficiently when dealing with real-world constraints. The\nconsequence of a bad commit atomicity will still be there to be solved\nby a strict commit discipline.\n\nWhat becomes easier is avoiding redundant conflict resolution cycles,\nwhile the consequences of poor commit atomicity remain unchanged as\nthe Git philosophy rightly points out.\n\nBest regards,\nAntonio\n"}]}