-2

I have a branch mybranch with all sorts of changes and need to rebase it on master. The difference between the current and the target base of mybranch is one commit replace that consists entirely of replacing (most but not all) occurrences of foo with bar. This operation can be repeated with reasonable effort at any revision.

* 46de000 (mybranch) my latest change
* ...
* 452e6f5 my second change
* 452e6f4 my first change
| * dc5024d (origin/master, master, tag: replace) replace /foo/bar/
|/  
* 5fab176 common base
* 7bbe99e initial commit

The commits in mybranch do not change any occurrence of foo itself but they do change several lines that contain foo. So when I rebase mybranch on replace I need to resolve many merge conflicts, more than are feasible to resolve by hand. Those conflicts cannot be automatically resolved because replace changes a line one way and the commits of mybranch change it another way. But since the word bar is only introduced in replace and other commit contains it it is easy to formulate instructions for how to resolve a conflict when given snippets for REMOTE, LOCAL and BASE:

if base.contains('foo') and local.contains('bar'):
   return remote.replace('foo', 'bar')
else:
   exit('unexpected conflict: $remote $local $base')

Is there some tool that I can pass such instructions to?

Attempts at a solution

programmatically

git-filter-repo can batch rewrite history

I don't think there is a way to modify mybranch in a way that avoids merge conflicts.

gitPython

Looks promising but merge conflict resolution seems not yet implemented and the project is in maintenance mode.

manually after all

I'm really looking for a way to solve this programmatically, but as there have been many suggestions on how to do it manually I might as well collect them here:

Apply the replacement on top of mybranch

Would work if i was the owner, but I am not. replace is on origin/master and won't come off.

Apply the replacement on top of mybranch, squash it, rebase on master.

After applying the replace operation on top of mybranch as a new commit replace_on_top and squashing everything into one commit this single commit can be rebased on master with git rebase -Xtheirs master. Downside is that I lose the separation of mybranch into several commits. Maybe this replace_on_top can somehow be automatically distributed to the first commits in mybranch that change a file? Then I could keep the separate commits in mybranch.

With GUI or IDE

There are too many merge conflicts to resolve them manually, even with the help of an IDE.

Just merge mybranch into master

Easier than a rebase because all conflicts come at once, but I really want to rebase.

peer
  • 4,171
  • 8
  • 42
  • 73
  • 1
    It might be easier to just drop `dc5024d` from the rebase and then redo the replacement, if the replacement is pretty straightforward. – erik258 Aug 27 '22 at 16:46
  • Most ides provide tools for resolving merge conflicts (i.e., automatically). Probably git GUI clients have tools like this (I usually go with the command line but I'm think of clients like sourcetree, git kraken, etc. : https://git-scm.com/downloads/guis - probably worth looking into that as well as checking the capabilities of your ide (vscode, pycharm, whatever you are using, if any). Also agree with previous comment - you can resolve the conflict by banishing it from existence and starting over with the cleanup. – topsail Aug 27 '22 at 16:54
  • `rebase` and `squash` are separate things. You can rebase your branch on top of master - that means taking master and applying your commits over the top of master. You can omit particular commits from the rebase (`rebase -i` is how I usually invoke a rebase). If you start by creating a new branch off `mybranch` you can experiment with impunity. – erik258 Aug 27 '22 at 17:16
  • @DanielFarrell The replacement is pretty straightforward but unfortunately I may not modify `master`. I've edited the question to make that clearer. (I also cannot squash `mybranch` into one commit, amend it by the replacement and rebase that because I still need the separate commits) – peer Aug 27 '22 at 17:20
  • @DanielFarrell The `squash` thing was just another hack solution that I've considered. If edited the original comment to clarify but if it adds more confusion just ignore it. I am absolutely creating new branches to test the rebase but even in an interactive rebase I surely cannot omit the `replace` commit that I want to rebase on? – peer Aug 27 '22 at 17:25
  • yeah you can omit it. Just delete it from the list and it won't be applied. As the `rebase -i` comment section says, "If you remove a line here THAT COMMIT WILL BE LOST." – erik258 Aug 27 '22 at 17:50
  • @topsail I am not aware of any IDEs that automatically resolve merge conflicts more effectively that git's own 3-way merge. To my knowledge they just provide nice visualization of `LOCAL` and `REMOTE`, while I still have to manually decide how to resolve. This is after all a legitimate merge conflict: `replace` does one thing with a line, and a commit on `mybranch` does something different with it, I don't think this could be automatically merged without user input – peer Aug 27 '22 at 17:50
  • An example of how you would resolve merge conflicts through a gui tool in your IDE is here: https://stackoverflow.com/questions/38216541/visual-studio-code-how-to-resolve-merge-conflicts-with-git I don't mean to say whether this is the IDE doing it, or just the IDE visualizing git merge tools - I am speaking in loose parlance - since you are clicking buttons in the IDE and the IDE provides these nice buttons to click, it just a manner of speaking to say this is done through the IDE. – topsail Aug 27 '22 at 17:59
  • @DanielFarrell I don't understand the point of omitting `replace`. It is not a commit of `mybranch` and thus not part of the list of commits that I want to apply but the very commit I want to rebase on. So if I do `rebase -i` on `mybranch` the line for `replace` won't show up, because it is not a part of that branch. And If I drop `replace` from `master` then the rebase of `mybranch` to `master` will be very easy (because that's where it already is) but it won't solve my problem. – peer Aug 27 '22 at 18:02
  • @topsail Yeah that is still within my definition of having to do it manually :-) I do not want to inspect any merge conflict. And mind you, I cannot just click buttons either as neither the suggestion from `REMOTE` or `LOCAL` is desired, I have to merge both parts to integrate both changes into my solution. – peer Aug 27 '22 at 18:09
  • I understand the desire to rebase here, but sometimes it's just easier to merge instead. Then you only have one set of conflicts to resolve. – TTT Aug 27 '22 at 19:13

1 Answers1

0

There is no existing Git tool for this. (There was a proposal recently on the Git mailing list for one, but I believe the problem itself is a little under-specified to start with and the result is that no one is entirely sure how to proceed.)

Fundamentally, what you'd like to do is:

  • Take each commit to be copied (as in what git rebase does with a copy) and make two temporary commits—one of it, and one of its parent—with the mechanical substitution in place.

    Note that the "replacement" temporary parent commit is just the commit we copied last time, if we've already copied one commit. This can be used for optimization but is not relevant to the overall process. But there is one caveat; see below.

  • Using the copied child that has the copied parent, do the git cherry-pick operation that rebase will do. Since both parent and child have the mechanical substitution already done, only the remaining changes show up in the diff from parent to HEAD, and in the diff from parent to child.

    If there are conflicts, present and resolve them in the usual way.

  • Repeat until all commits are copied, in the usual way, then move the one branch name as git rebase usually does.

The caveat is that in this "make temporary commits" process, we may want that mechanical substitution process depend on the depth within the branch. Consider:

          A--B--C   <-- topic (to be rebased)
         /
...--o--*--o--F--o--...--@   <-- main

We want to copy the effect of A, B, C to commit @. But there was a "flag day" commit: here it's commit F. Here, some sort of mechanical substitution was done.

In this particular rebase sequence, it's clear that the mechanical replacement of foo to bar that happened in F does need to be applied to commits * and then A-B-C. But what if we have a more complex structure:

...--o--*--F---o--G--o--...--@   <-- main
         \      \
          A------M---B--C   <-- topic (to be rebased)

Here we'd like to copy A-B-C as before, but there were two flag days. We want the foo-to-bar substitution done when copying A, but a different substitution—the one from G—done when copying B and C. (And in the most general case, if M dropped the flag-day stuff from F because o was the important part, we want both done.)

If we ignore the complication case, in which some flag days are merged in at some point and others aren't, we get a pretty straightforward set of operations that git rebase could do if it were programmed for it. But it isn't.

torek
  • 448,244
  • 59
  • 642
  • 775