start point of commits to merge
Is it possible to specify the start point of the commits in branch to be merged (the upstream argument of git-rebase).
I'm asking because my work is on a stable branch of upstream and now I'm going to rebase my commits to a newer stable branch from upstream. The two branches share many commits doing the same things. And I only want to rebase my commits.
In general, rebasing only part of a branch is not a well-formed operation in a DAG-based history. However, it just occurred to me that the removal of a commit or sequence of commits from a branch could be framed as an incremental merge. Suppose you have two commits, A and B, and you want to remove A from the history:
o - A - B
It is possible to compute A⁻¹, the inverse of A, in the context of A (this cannot conflict):
o - A - A⁻¹
|
B
Now if you merge these two "branches", you get
o - A - A⁻¹
| |
B - B'
where the merge commit, B' is effectively commit B without commit A.
This can be generalized to removing an arbitrary number of contiguous commits followed by an arbitrary number of commits that you want to retain. I don't see an obvious way to generalize it to removing multiple intervals of unwanted commits except for the trivial approach of removing them one interval at a time.
But this workflow is not yet supported by git-imerge, so you would have to do at least part of it manually. If you would like to work on implementing this in git-imerge, I would be happy to help you get started.
I think this would be a cool feature to have. Just to make sure Im on the same track.
Say you have a history like so:
o - A - B - C
\ - D - E - F - G - H
And I want to rebase G and beyond onto A,B,C:
o - A - B - C - G' - H'
\ - D - E - F
In order to do this with git imerge, the following would need to happen:
- "Remove" commits before the first commit of the rebase but after the first common parent between the branch you are rebasing from and the branch you are rebasing onto, in this case (A). In order to do this, iterate backwards.
o - A - D - E - F - G - H
| | |
F⁻¹ - G' - H''
o - A - D - E - F - G - H
| | | |
| F⁻¹ - G' - H''
| | |
E⁻¹ ------ G'' - H''
o - A - D - E - F - G - H
| | | | |
| | F⁻¹ - G' - H''
| | | |
| E⁻¹ -------- G'' - H''
| | |
D⁻¹ ---------------- G''' - H'''
And possibly for completeness sake (although hopefully this last merge is a near noop)
o - A - D - E - F - G - H
| | | | | |
| | | F⁻¹ - G' - H''
| | | | |
| | E⁻¹ ------ G'' - H''
| | | |
| D⁻¹ ---------------- G''' - H'''
| | |
+---------------------------- G''''- H''''
At this point, if you ignore the intermediate steps we have
- G'''' - H''''
/
o - A - B - C
\
- D - E - F
Now, the problem has been reduced to a more simpler rebase at which git-imerge would essentially be rerun.
o - A - B - C
| |
G'''' G'''''
| |
H'''' -------- H'''''
And we have our end result. I suppose I would be interested in the kind of merge conflicts this procedure would uncover or if conflicts would seem redundant (i.e. slowly removing a piece of the code base). But maybe this redundancy is desired to ensure a smooth iterative process?
Also, would this same process work if you had merges in your branch to rebase from history? I'm not sure how well git reverts merges...
Edit: Fixed diagrams and note to self to never try type ascii diagrams directly into github's text editor.
@muff1nman: I think that your "remove commits" steps can be done as a single incremental merge:
o - A - D - E - F - G - H
|
F⁻¹
|
E⁻¹
|
D⁻¹
Each of these three revert commits (since they are applied in reverse order) necessarily applies cleanly. For example, the state of the tree after D⁻¹ is exactly the state of the tree after A.
Once the incremental merge is done:
o - A - D - E - F - G - H
| | |
F⁻¹ - G' - H'
| | |
E⁻¹ - G'' - H''
| | |
D⁻¹ - G''' - H'''
then all you have to do is glue together the trees in the order [A, G''', H'''] and you have effectively deleted D, E, and F from this history.
The second step, as you describe, is a standard git imerge rebase.
But you could even reduce these two steps to a single step, by starting the incremental merge like this:
o - A - D - E - F - G - H
|
F⁻¹
|
E⁻¹
|
D⁻¹
|
B
|
C
B and C necessarily apply cleanly on top of D⁻¹, because D⁻¹ has the same tree as A. Once you've filled in the incremental merge
o - A - D - E - F - G - H
| | |
F⁻¹ - G₁ - H₁
| | |
E⁻¹ - G₂ - H₂
| | |
D⁻¹ - G₃ - H₃
| | |
B - G₄ - H₄
| | |
C - G₅ - H₅
the final answer is [A, B, C, G₅, H₅].
But, even in this simple case, there is the question: is it more efficient to do it this way (remove [D, E, F] from the history and then rebase [G₃, H₃] onto [A, B, C]), or would it be more efficient to rebase [D, E, F, G, H] onto [A, B, C] and then remove [D', E', F'] from the result? (Actually there are a bunch of other possibilities, too.) That would be a straight rebase:
o - A - B - C
| | |
D - D₁ - D₂
| | |
E - E₁ - E₂
| | |
F - F₁ - F₂
| | |
G - G₁ - G₂
| | |
H - H₁ - H₂
followed by removal:
o - A - B - C - D₂ - E₂ - F₂ - G₂ - H₂
| | |
F₂⁻¹ - G₃ - H₃
| | |
E₂⁻¹ - G₄ - H₄
| | |
D₂⁻¹ - G₅ - H₅
Then the answer is [A, B, C, G₅, H₅].
I don't think that these two methods are equivalent. Which one is easier probably depends on a case-by-case basis.
I do like the simplification you present and it makes sense. However, how would the end history end up? I think it would be nice if the situation in your second diagram:
o - A - D - E - F - G - H
| | |
F⁻¹ - G' - H'
| | |
E⁻¹ - G'' - H''
| | |
D⁻¹ - G''' - H'''
could be simplified like so:
o - A - G''' - H'''
My thoughts are that I would like to be able to hide D,E,F from the history entirely. How is this done in the usual git-imerge case of a merge? I.e. going from:
A - B - C
| | |
D - D'- D''
| | |
E - E'- E''
to
A - B - C
| |
D |
| |
E - - - M
The possiblity for different methods is not something I thought of before and ideally I think it would be cool for the user to choose a method. For now, however, I think the first option presents a more logical progression of merging.
Ah, I must have overlooked the simplify, remove and finish options.