Shower thought: Rebase and merge each have their strengths and weaknesses.
- Rebase results in a clean, linear history and allows the developer to resolve conflicts within the context of the commit being replayed.
Git bisects are also easierNevermind - apparently, git bisects work with merge-based workflows. - Merge never loses the old history, and commits the merging step as if it was a legitiment piece of work and effort that needs to be documented. Indeed, some merges can be non-trivial and resolving conflicts are often cases where bugs are introduced.
Then, why not have a different kind of "merge" command that merges each commit one by one, so that, if you only log the commits from the first parent of every merge commit, you'll get a very clean linear git history, and if you need to, you can traverse throught the merge commit's second parents to look back at the original commits. Here, the merge commits fully describe how the "old" commits map into the "new" commits.
Best of both worlds?
Of course, there are probably many corner cases that needs to be resolved first.
A1---A2---A3 master
\
B1----B2----B3 feature
git checkout feature
git remap master
A1---A2---A3 master
\ \
\ B1'---B2'---B3' feature
\ / / /
`--B1----B2----B3
git checkout master
git merge feature
> fast-forward merge
A1---A2---A3---B1'---B2'---B3' master
\ / / /
`-----B1----B2----B3
Rebase --onto may not work properly anymore: there are unwanted commits which we don't want merged in:
.- Commit gets lost if merged automatically, since X1 is already an ancestor
|
v
A1---A2---A3---B1'---B2'---B3'---X1'---X2'---X3'---X4'---X5' master
\ / / / / / / / /
\ B1----B2----B3 / / / / /
\ / / / / / /
X1------------------+-----X2----X3----X4----X5
Squashing = octopus merge:
A1---A2---A3-----------B13'---B4' master
\ .-----+-----/ /
\ / / / /
B1----B2----B3------B4
Dropping: Note: dangerous if commit is needed to be merged later on? What if it is re-mapped?
A1---A2---A3---B1'--------B3' master
\ / /
`-----B1----B2----B3
A1---A2---A3---B1'--------B3' master
\ / /
`-----B1----B2----B3
\
B2'---C1 feature
A1---A2---A3---B1'--------B3'---B2'---C1' master
\ / / / /
`-----B1----B2----B3 / /
\ / /
B2'---+-----C1
Fixup: e.g. correcting commit message, or changing file contents in previous commit B1:
A1---A2---A3---A4---A5 master
A1---A2---A3'---A4'---A5' master
\ / / /
A3----A4----A5
Some possible solutions regarding the dropped commits:
- Upon
git remap
, do arebase
of the original commits first to detach them from the original commits and ensure that any other references (e.g. branches) to those commits no longer reference the same commits. That way, when merging the dropped commits back into the master branch, it does not get silently removed. - Prevent
git remap
commands with drop instructions or the use of the--onto
variant when there are other references to the dropped commits? This is iffy.
Suppose the following interactive rebase was done:
pick A
squash B
squash C
fixup D
fixup E
edit F
with the following linear graph:
A---B---C---D---E---F
Then rebase will stop at B, C and F to let the user write the commit message.
TODO: consider what should happen when the intention was to squash, but it was aborted and commit was split. Consider what should happen when the intention was to edit, but it was instead split into two commits, with the first commit squashed to the previous one.
The case when some of B2 should be in B1, some of B3 should be in B2, and some of B4 should be in B3:
A1---A2---A3---B12'--B23'--B34'--B4 master
\ .~'/ .~'/ .~'/ .~'
\ .~' /.~' /.~' /.~'
B1----B2----B3----B4
I know this gist is probably very outdated now compared with my current ideas, but I need somewhere to jot this down:
https://www.git-scm.com/docs/git-range-diff
The cost matrix and least cost assignment algorithm mentioned in the bottom might be useful for us to figure out how to reassociate the commits when we perform an undo operation.