When encountering a conflict while rebasing foo
onto master
is there a way to programmatically determine which commit(s) on master
cause the conflict?
As Tim Biegeleisen noted in a comment, the short answer is no. This is a bit too short since it's an in-theory answer, and in practice there's often a machine-find-able answer. ("In theory, theory and practice are the same. In practice, they are not.")
The root of the theoretical problem is that every commit in Git is just a full snapshot. As such, each commit is sort of idempotent, if I may abuse the terminology a bit. For instance, suppose we have the following sequence of commits:
...--B--C---------G--H <-- master
\
D--E--F <-- feature
We now decide to rebase feature
onto master
, i.e., after commit H
. We may or may not get a conflict. If we choose to rebase after G
instead of H
, we may or may not get a completely different set of conflicts.
For instance, suppose commit G
consists of reversing the text on every line of every file, and commit H
is a revert of G
. Then rebasing onto G
is virtually guaranteed to have conflicts (no lines will match up), but rebasing onto H
will have no conflicts (the snapshot in H
matches that in C
). The fact—whether happy, sad, or neutral—is that commit G
is only historically related to commits C
and H
; it has entirely different source files.
Of course, we don't normally find a commit that totally messes up every file, followed by one that restores the previous state. We do occasionally find commits that have been reverted, so that we might, with some programmatic test for conflict, find irrelevant commits (such as commit G
in our theory-example), but if so, that's usually not a big problem anyway since we'll see them being reverted later. So, if we're in the middle of some rebase, and some commit X
has failed to apply, all (hah) we need to do is take this:
...--B--C---------[sequence] <-- master
\
D--X--F <-- feature
and try the rebase on each of the commits in the sequence. The tricky part is choosing which commits to try, for efficiency and to handle merge bubbles in the sequence (if any exist). A brute force git rev-list --reverse --topo-order feature..master
would, however, give us a reasonable starting point: we would now try rebasing commits up through and including X on each commit in order, until one of them fails.
joanis' method makes use symmetry (not strictly guaranteed, especially in the presence of reverts) and might well be more efficient. We could examine the number of commits N
that need to be rebased each way and pick whichever one is shorter. In general, though, for efficiency I'd use a bisection approach, as that will usually be able to identify the problem in log2 N steps, where N is the number of potentially-conflicting commits.
The first thing, though, would probably be to get the brute force method working. To avoid disturbing the existing working tree (with the conflicted rebase-so-far in it), I'd suggest that a tool that does this—as far as I know, it has yet to be written—work by creating a detached-HEAD working tree with git worktree add
.