Web lists-archives.com

Re: [SCRIPT/RFC 0/3] git-commit --onto-parent (three-way merge, no working tree file changes)




Hi Hannes,

On 01/12/2017 18:23, Johannes Sixt wrote:
> 
> > To work with `--onto-parent` and be able to commit on top of any of
> > the topic branches, you would need a situation like this instead:
> >
> >   (1)  ...C      <- topic C
> >           |
> >      ...A |      <- topic A
> >          \|
> >        ...o I    <- integration
> >          /|
> >      ...B |      <- topic B
> >           |
> >        ...D      <- topic D
> 
> This is a very, VERY exotic workflow, I would say. How would you
> construct commit I when three or more topics have conflicts?
> merge-octopus does not support this use-case.

But I`m not interested in constructing commit I in the first place, 
only help working with it once it`s already there (which shouldn`t be 
too uncommon in case of unrelated, non-conflicting topics) - but I 
already agreed my example could have been a bit too esoteric, 
distracting attention from the important part :)

I`ll continue on this further below, where you commented on those 
more common scenarios.

Here, let`s try to look at the whole situation from a "patch queue" 
perspective instead, starting from something like this:

(5) ---O---I  <- integration <- HEAD

... and then making progress like this - first, commit A1 onto O, 
starting "topicA" branch at the same time, even, maybe using syntax 
like `git commit --onto-parent O -b topicA`:

(6) ---O---J  <- integration <- HEAD               [ J = I + A1 ]
        \ /
         A1   <- topicA

..., then commit B1 onto O to start "topicB" branch:

(7)      B1   <- topicB
        / \
    ---O---K  <- integration <- HEAD               [ K = J + B1 ]
        \ /
         A1   <- topicA

..., then add one more commit (patch) onto B1:

(8)      B1---B2   <- topicB
        /      \
    ---O--------L  <- integration <- HEAD          [ L = K + B2 ]
        \      /
         A1---/    <- topicA

..., and one more, B3:

(9)      B1---B2---B3   <- topicB
        /           \
    ---O-------------M  <- integration <- HEAD     [ M = L + B3 ]
        \           /
         A1--------/    <- topicA

We can also start a new topic branch (queue), commit C1:

(10)     B1---B2---B3   <- topicB
        /           \
    ---O-------------N  <- integration <- HEAD     [ N = M + C1 ]
       |\           /|
       | A1--------/ /  <- topicA
       \            /
        C1---------/    <- topicC

And lets make one more "topicA" related commit A2:

(11)     B1---B2---B3   <- topicB
        /           \
    ---O-------------P  <- integration <- HEAD     [ P = N + A2 ]
       |\           /|
       | A1---A2---/ /  <- topicA
       \            /
        C1---------/    <- topicC


Notice how HEAD never leaves "integration" branch, and underlying 
commit is recreated each time? It`s like a live branch we`re working 
on, but we`re not actually committing to.

No branch switching (and no working tree file changes caused by it), 
and we`re working on multiple topics/branches simultaneously, being 
able to instantly test their mutual interaction as we go, but also 
creating their separate (and "clean") histories at the same time.

I guess this would make most sense with simple topics we _could_ 
practically work on at the same time without making our life too 
complicated - what stands for "git add --patch", too, when working on 
multiple commits at the same time.

Once satisfied, of course each topic branch would need to be tested 
separately, and each commit, even - all the same as with "git add 
--patch" commits.

And "git add --patch" can still be used here, too, to distribute 
partial changes, currently existing together inside the working tree, 
to different topic branches, at the time of making the commit itself.

Does this approach make more sense in regards to "git commit 
--onto-parent" functionality I`m having in mind? Or I`m dreaming too 
much here...? :)

> > Once there, starting from your initial position:
> >
> > >     ...A    ...C            <- topics A, C
> > >         \       \ E
> > >       ---o---o---o---o I    <- integration <- HEAD
> > >             /       /
> > >         ...B    ...D        <- topics B, D
> >
> > ... and doing something like `git commit --onto B --merge` would
> > yield:
> > 
> >     (3) ...A    ...C            <- topics A, C
> >           \       \ E
> >         ---o---o---o---o I'   <- integration
> >               /       /|
> >           ...B    ...D |      <- topic D
> >               \        |
> >                f-------'      <- topic B
> >
> > ... where (I' = I + f) is still true.
> 
> I am not used to this picture. I would not think that it is totally
> unacceptable, but it still has a hmm-factor.

Main idea is not to pile up uninteresting merge commits inside 
(throwaway) integration branch, needlessly complicating history, but 
pretend as we just made the integration merge, where fixup commit f 
was already existing in its topic branch prior the merge.

> > If that`s preferred in some
> > cases, it could even look like this instead:
> >
> >   (4) ...A    ...C             <- topics A, C
> >           \       \ E  I
> >         ---o---o---o---o---F   <- integration
> >               /       /   /
> >           ...B    ...D   /     <- topic D
> >               \         /
> >                f-------'       <- topic B
> >
> > ... where F(4) = I'(3), so similar situation, just that we don`t
> > discard I but post F on top of it.
> 
> This is very acceptable.

And I can see logic behind this case, too (alongside that other one, 
where they can both be supported, for different scenarios).

> Nevertheless, IMO, it is not the task of git-commit to re-compute a
> merge commit. It would be OK that it commits changes on top of a
> branch that is not checked out. Perhaps it would even be OK to remove
> the change from the current workspace (index and worktree), because
> it will return in the form of a merge later, but producing that merge
> is certainly not the task of git-commit.

Just to make sure we`re on the same page - you mean conceptually, or 
practically? Or both...? :)

Because practically, we don`t do any merge operations/computations 
(as can be seen inside the script), so we really don`t produce any 
merges :)

Having a merge commit being updated as an outcome is just a 
consequence of altering our history to pretend fixup commit already 
existed elsewhere, being brought into our current state from there - 
and that "current state" is updated accordingly to reflect the change 
we made in the history that led to it.

Let`s look at a different example, no merges involved:

(12) ---A---B---C---D  <- HEAD

Here, we can do `git commit --onto-parent C` to make the history look 
like this:

(13) ---A---B---C---E---D'  <- HEAD

..., where we practically inserted commit E between our HEAD and 
commit C, causing D to be updated to D' accordingly (rebased, 
practically).

Idea is the same with merges involved, updating HEAD commit to 
reflect altered history.

In situation (12), we could also do `git commit --onto-parent C 
--amend`, ending up with history like this instead:

(14) ---A---B---F---D'  <- HEAD

..., where F is amended C, E being added to it, instead of being a 
separate commit.

So there are quite some possibilities here.

But as said, I can understand a wish to have a commit elsewhere 
without "merging" it in, too (changes removed from current workspace), 
or "merging" it inside a separate commit (not updating existing 
HEAD one).

And saying that, making the merge commit at the same time could then 
be observed as no more than a shortcut, too, like being able to 
create a new branch on checkout using "-b" option.

So even conceptually, it would make sense to wish for something like 
this:

(15) git commit --onto B --merge

..., producing that graph (4) shown in the quote above, I would 
think? 

But even in that case, do note no merge is really happening, we just 
alter history of our current state, where we already are.

Splitting changes out of HEAD to a commit directly preceding it, on a 
different branch, and still keeping those changes in HEAD, naturally 
causes our current HEAD state to have multiple parents, what is 
perceived as a merge commit, even without a merge operation leading 
to it.

Thus, `git commit` seems it should be up to the task, whichever 
approach user opts for :)

Regards, Buga