I previously explained how to merge a single line of code in support of supporting cherry picking and local undo. In there I suggested that pulling in ‘orphan’ changes without their dependencies, then letting them kick in when the dependencies get merged may be useful. On reflection I think this is a bad idea, or at least have more confidence in getting the same effect in other ways.
For cherry-picking my confusion has a lot to do with UX. For some reason I’ve always envisioned cherry-picking as saying ‘Make a patch of the changes from commit X to commit Y and apply them to this branch’, possibly because that’s sort of the way patch files work. Allowing orphan history would support that done verbatim, but it seems to have a problem with what happens if the dependencies get merged piecemeal, resulting in bizarre and likely erroneous behavior. Much better than specifying the changes to be pulled over as being between two commits is to specify them as line ranges from a single commit, with the complete history of those lines pulled in with them. This makes the behavior of merging in dependent commits clear: They’re ignored because they’re already included. There’s an interesting nuance around whether to include context lines and ghost lines which have been deleted immediately adjacent to the line range specified, but that’s a minor UX problem.
This is aggressively explicit rather than implicit cherry-picking. As with the other cases it seems like doing things implicitly is just plain a bad idea. Lines of code identities might not line up of a bunch of changes have been squashed into a single commit. Even if the lines of code do line up if someone makes a change then locally undoes it that shouldn’t cause merges with outside things to be any different than if the changes had never happened and assuming that similar looking changes are actually the same violates that principle. When you don’t have support for explicit cherry picking trying to make support for it implicitly may be the least bad option but when doing it the right way is an option there’s no need to try to support the fundamentally problematic bad way.
Next up is the more difficult case of local undo. I recently realized the scope of the undo needs to be specified by merging an undo of the undo into main or the feature branch so that the feature doesn’t get deleted when everything is merged together. I previously suggested this could be done by taking merging a literal undo of the undo into main with orphan history. This is a reasonable thought but has some problems. It requires some very explicit references to commit ids in the UX, and if the undo itself is done in a series of commits then the behavior will be erratic if they’re merged in one at a time. This is probably a case of ‘if you have a hammer every problem looks like a nail’: I’ve gotten far enough along with supporting stuff with the merge algorithm that there’s a temptation to add more features to it to support more, but orphan history is going a bit too far and is best avoided.
The improved approach is this one: After a local undo is done, cherry-pick the undo into main, but instead of merging it force the resulting value to leave main unchanged then commit that result. This has to be explicit cherry-picking as mentioned above, where the scope is limited by line ranges. This has all the features desired: Merge behavior with branches off main which are pulled in later will be unaffected, when the changes to the backed out branch are pulled in piecemeal they’ll simply be ignored, and there’s no need for orphan history. This is in some sense still the cherry-pick-but-ignore approach I suggested before, but it’s important to point out that the principle still applies with the improved approach to cherry picking. One potential problem is that if there are changes which weren’t in main and are supposed to stay but were backed out during the undo then they’ll be deleted in a merge, but in that case the feature branch was ahead of main and the undo of the undo should have been put into the feature branch, not main.
Eventually I’ll spend an off day on merging and actually start writing some code instead of finding more theory to work through. I’m still fretting about some of the issues around detecting and presenting conflicts. But that may be the last of the theory issues. I probably won’t write full code for the above features because implementing them as stated is trivial on top of the library I’ll write with the real problems being a UX one of specifying the line ranges. This is of course on top of the several other hobby projects I have which actually seem important.