Subject: Re: [RFC] Rebasing merges: a jorney to the ultimate solution (Road Clear)

From: Sergey Organov <[email-masked]>
Date: Wed, 28 Feb 2018 08:44:13 +0300

Hi Johannes,

Johannes Schindelin <[email-masked]> writes:
> Hi Buga,
>
> thank you for making this a lot more understandable to this thick
> developer.
>
> On Tue, 27 Feb 2018, Igor Djordjevic wrote:
>
>> On 27/02/2018 19:55, Igor Djordjevic wrote:
>> > 
>> > It would be more along the lines of "(1) rebase old merge commit parents, 
>> > (2) generate separate diff between old merge commit and each of its 
>> > parents, (3) apply each diff to their corresponding newly rebased 
>> > parent respectively (as a temporary commit, one per rebased parent), 
>> > (4) merge these temporary commits to generate 'rebased' merge commit, 
>> > (5) drop temporary commits, recording their parents as parents of 
>> > 'rebased' merge commit (instead of dropped temporary commits)".
>> > 
>> > Implementation wise, steps (2) and (3) could also be done by simply 
>> > copying old merge commit _snapshot_ on top of each of its parents as 
>> > a temporary, non-merge commit, then rebasing (cherry-picking) these 
>> > temporary commits on top of their rebased parent commits to produce 
>> > rebased temporary commits (to be merged for generating 'rebased' 
>> > merge commit in step (4)).
>> 
>> For those still tagging along (and still confused), here are some 
>> diagrams (following what Sergey originally described). Note that 
>> actual implementation might be even simpler, but I believe it`s a bit 
>> easier to understand like this, using some "temporary" commits approach.
>> 
>> Here`s our starting position:
>> 
>> (0) ---X1---o---o---o---o---o---X2 (master)
>>        |\
>>        | A1---A2---A3
>>        |             \
>>        |              M (topic)
>>        |             /
>>        \-B1---B2---B3
>> 
>> 
>> Now, we want to rebase merge commit M from X1 onto X2. First, rebase
>> merge commit parents as usual:
>> 
>> (1) ---X1---o---o---o---o---o---X2
>>        |\                       |\
>>        | A1---A2---A3           | A1'--A2'--A3'
>>        |             \          |
>>        |              M         |
>>        |             /          |
>>        \-B1---B2---B3           \-B1'--B2'--B3'
>> 
>> 
>> That was commonly understandable part.
>
> Good. Let's assume that I want to do this interactively (because let's
> face it, rebase is boring unless we shake up things a little). And let's
> assume that A1 is my only change to the README, and that I realized that
> it was incorrect and I do not want the world to see it, so I drop A1'.
>
> Let's see how things go from here:
>
>> Now, for "rebasing" the merge commit (keeping possible amendments), we
>> do some extra work. First, we make two temporary commits on top of old
>> merge parents, by using exact tree (snapshot) of commit M:
>> 
>> (2) ---X1---o---o---o---o---o---X2
>>        |\                       |\
>>        | A1---A2---A3---U1      | A1'--A2'--A3'
>>        |             \          |
>>        |              M         |
>>        |             /          |
>>        \-B1---B2---B3---U2      \-B1'--B2'--B3'
>
> Okay, everything would still be the same except that I still have dropped
> A1'.
>
>> So here, in terms of _snapshots_ (trees, not diffs), U1 = U2 = M.
>> 
>> Now, we rebase these temporary commits, too:
>> 
>> (3) ---X1---o---o---o---o---o---X2
>>        |\                       |\
>>        | A1---A2---A3---U1      | A1'--A2'--A3'--U1'
>>        |             \          |
>>        |              M         |
>>        |             /          |
>>        \-B1---B2---B3---U2      \-B1'--B2'--B3'--U2'
>
> I still want to drop A1 in this rebase, so A1' is still missing.
>
> And now it starts to get interesting.
>
> The diff between A3 and U1 does not touch the README, of course, as I said
> that only A1 changed the README.  But the diff between B3 and U2 does
> change the README, thanks to M containing A1 change.
>
> Therefore, the diff between B3' and U2' will also have this change to the
> README. That change that I wanted to drop.
>
>> As a next step, we merge these temporary commits to produce our
>> "rebased" merged commit M:
>> 
>> (4) ---X1---o---o---o---o---o---X2
>>        |\                       |\
>>        | A1---A2---A3---U1      | A1'--A2'--A3'--U1'
>>        |             \          |                  \
>>        |              M         |                   M'
>>        |             /          |                  /
>>        \-B1---B2---B3---U2      \-B1'--B2'--B3'--U2'
>
> And here, thanks to B3'..U2' changing the README, M' will also have that
> change that I wanted to see dropped.
>
> Note that A1' is still dropped in my example.
>
>> Finally, we drop temporary commits, and record rebased commits A3' 
>> and B3' as our "rebased" merge commit parents instead (merge commit 
>> M' keeps its same tree/snapshot state, just gets parents replaced):
>> 
>> (5) ---X1---o---o---o---o---o---X2
>>        |\                       |\
>>        | A1---A2---A3---U1      | A1'--A2'--A3'
>>        |             \          |             \
>>        |              M         |              M'
>>        |             /          |             /
>>        \-B1---B2---B3---U2      \-B1'--B2'--B3'
>
> Now, thanks to U2' being dropped (and A1' *still* being dropped), the
> change in the README that is still in M' is really only in M'. No other
> rebased commit has it. That makes it look as if M' introduced this change
> in addition to the changes that were merged between the merge parents.

Except that original proposal suggests to cosider this a conflict and
to stop for amendment, as U1' and U2' now differ.

-- Sergey