Re: [SCRIPT/RFC 0/3] git-commit --onto-parent (three-way merge, no working tree file changes)
- Date: Mon, 4 Dec 2017 03:33:35 +0100
- From: Igor Djordjevic <igor.d.djordjevic@xxxxxxxxx>
- Subject: Re: [SCRIPT/RFC 0/3] git-commit --onto-parent (three-way merge, no working tree file changes)
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
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
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
(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,
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
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
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
(15) git commit --onto B --merge
..., producing that graph (4) shown in the quote above, I would
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
Thus, `git commit` seems it should be up to the task, whichever
approach user opts for :)