diff --git a/README.rst b/README.rst index cc3aa71..93857ec 100644 --- a/README.rst +++ b/README.rst @@ -195,8 +195,106 @@ history by using either the ``finish`` or ``simplify`` command. The approach. See [2]_ for more information. ``full`` + don't simplify the incremental merge at all: do all of the intermediate merges and retain them all in the permanent history. + In other words, it transforms this:: + + o---o---1---2---3 BRANCH1 + \ + A---B---C---D BRANCH2 + + into this:: + + o---o---1---2---3 + \ \ \ \ + A---A1--A2--A3 + \ \ \ \ + B---B1--B2--B3 + \ \ \ \ + C---C1--C2--C3 + \ \ \ \ + D---D1--D2--D3 NEW_BRANCH + + This approach retains the complete history and ancestry + information, which gives the maximum flexibility for conducting + future merges. On the other hand, it clutters up the permanent Git + history considerably. + +``border`` + this experimental goal retains the rebase of ``BRANCH2`` onto + ``BRANCH1`` and also the rebase of ``BRANCH1`` onto ``BRANCH2``, + plus a merge commit that includes both branches. In other words, + it transforms this:: + + o---o---1---2---3 BRANCH1 + \ + A---B---C---D BRANCH2 + + into this:: + + o---o---1---2---3 + \ \ + A A2 + \ \ + B B2 + \ \ + C C2 + \ \ + D---D1--D2--D3 NEW_BRANCH + + This approach leaves more history than a simple merge or rebase, + possibly making future merges easier. + +``border-with-history`` + this experimental goal retains the rebase-with-history of + ``BRANCH2`` onto ``BRANCH1`` and also the rebase (without history) + of ``BRANCH1`` onto ``BRANCH2``, plus a merge commit that includes + both branches. In other words, it transforms this:: + + o---o---1---2---3 BRANCH1 + \ + A---B---C---D BRANCH2 + + into this:: + + o---o---1---2---3 + \ \ + A-----------A3 + \ \ + B-----------B3 + \ \ + C-----------C3 + \ \ + D---D1--D2--D3 NEW_BRANCH + + This approach leaves more history and ancestry information than a + simple merge or rebase, possibly making future merges easier. + +``border-with-history2`` + this experimental goal retains the rebase-with-history of + ``BRANCH1`` onto ``BRANCH2`` and also the rebase-with-history of + ``BRANCH2`` onto ``BRANCH1``, plus a merge commit that includes + both branches. In other words, it transforms this:: + + o---o---1---2---3 BRANCH1 + \ + A---B---C---D BRANCH2 + + into this:: + + o---o---1---2---3 + \ \ \ \ + A--- --- ---A3 + \ \ \ \ + B--- --- ---B3 + \ \ \ \ + C--- --- ---C3 + \ \ \ \ + D---D1--D2--D3 NEW_BRANCH + + This approach leaves more history and ancestry information than a + simple merge or rebase, possibly making future merges easier. Technical notes diff --git a/git-imerge b/git-imerge index 87eac6b..b7020e4 100755 --- a/git-imerge +++ b/git-imerge @@ -122,8 +122,11 @@ ZEROS = '0' * 40 ALLOWED_GOALS = [ 'full', - 'rebase-with-history', 'rebase', + 'rebase-with-history', + 'border', + 'border-with-history', + 'border-with-history2', 'merge', 'drop', 'revert', @@ -2864,6 +2867,86 @@ class MergeState(Block): self._set_refname(refname, commit, force=force) + def simplify_to_border( + self, refname, + with_history1=False, with_history2=False, force=False, + ): + i1 = self.len1 - 1 + for i2 in range(1, self.len2): + if not (i1, i2) in self: + raise Failure( + 'Cannot simplify to border because ' + 'merge %d-%d is not yet done' + % (i1, i2) + ) + + i2 = self.len2 - 1 + for i1 in range(1, self.len1): + if not (i1, i2) in self: + raise Failure( + 'Cannot simplify to border because ' + 'merge %d-%d is not yet done' + % (i1, i2) + ) + + i1 = self.len1 - 1 + commit = self[i1, 0].sha1 + for i2 in range(1, self.len2 - 1): + orig = self[0, i2].sha1 + tree = self.git.get_tree(self[i1, i2].sha1) + + # Create a commit, copying the old log message: + if with_history2: + parents = [commit, orig] + msg = ( + self.git.get_log_message(orig).rstrip('\n') + + '\n\n(rebased-with-history from commit %s)\n' % (orig,) + ) + else: + parents = [commit] + msg = ( + self.git.get_log_message(orig).rstrip('\n') + + '\n\n(rebased from commit %s)\n' % (orig,) + ) + + commit = self.git.commit_tree(tree, parents, msg=msg) + commit1 = commit + + i2 = self.len2 - 1 + commit = self[0, i2].sha1 + for i1 in range(1, self.len1 - 1): + orig = self[i1, 0].sha1 + tree = self.git.get_tree(self[i1, i2].sha1) + + # Create a commit, copying the old log message: + if with_history1: + parents = [orig, commit] + msg = ( + self.git.get_log_message(orig).rstrip('\n') + + '\n\n(rebased-with-history from commit %s)\n' % (orig,) + ) + else: + parents = [commit] + msg = ( + self.git.get_log_message(orig).rstrip('\n') + + '\n\n(rebased from commit %s)\n' % (orig,) + ) + + commit = self.git.commit_tree(tree, parents, msg=msg) + commit2 = commit + + # Construct the apex commit: + tree = self.git.get_tree(self[-1, -1].sha1) + msg = ( + 'Merge %s into %s (using imerge border)' + % (self.tip2, self.tip1) + ) + + commit = self.git.commit_tree(tree, [commit1, commit2], msg=msg) + + # Update the reference: + self._set_refname(refname, commit, force=force) + def _simplify_to_path(self, refname, base, path, force=False): """Simplify based on path and set refname to the result. @@ -2976,10 +3059,18 @@ class MergeState(Block): if self.goal == 'full': self.simplify_to_full(refname, force=force) - elif self.goal == 'rebase-with-history': - self.simplify_to_rebase_with_history(refname, force=force) elif self.goal == 'rebase': self.simplify_to_rebase(refname, force=force) + elif self.goal == 'rebase-with-history': + self.simplify_to_rebase_with_history(refname, force=force) + elif self.goal == 'border': + self.simplify_to_border(refname, force=force) + elif self.goal == 'border-with-history': + self.simplify_to_border(refname, with_history2=True, force=force) + elif self.goal == 'border-with-history2': + self.simplify_to_border( + refname, with_history1=True, with_history2=True, force=force, + ) elif self.goal == 'drop': self.simplify_to_drop(refname, force=force) elif self.goal == 'revert': diff --git a/t/test-conflicted b/t/test-conflicted index 262aa03..16d1f4d 100755 --- a/t/test-conflicted +++ b/t/test-conflicted @@ -14,7 +14,10 @@ cd "$TMP" # Clean up detritus from possible previous runs of this test: git checkout master "$GIT_IMERGE" remove --name=c-d || true -for b in c-d-merge c-d-rebase c-d-rebase-with-history c-d-full +for b in c-d-merge \ + c-d-rebase c-d-rebase-with-history \ + c-d-border c-d-border-with-history c-d-border-with-history2 \ + c-d-full do git branch -D $b || true done @@ -37,6 +40,9 @@ git add conflict.txt GIT_EDITOR=cat "$GIT_IMERGE" simplify --goal=merge --branch=c-d-merge "$GIT_IMERGE" simplify --goal=rebase --branch=c-d-rebase "$GIT_IMERGE" simplify --goal=rebase-with-history --branch=c-d-rebase-with-history +"$GIT_IMERGE" simplify --goal=border --branch=c-d-border +"$GIT_IMERGE" simplify --goal=border-with-history --branch=c-d-border-with-history +"$GIT_IMERGE" simplify --goal=border-with-history2 --branch=c-d-border-with-history2 "$GIT_IMERGE" remove git checkout c diff --git a/t/test-unconflicted b/t/test-unconflicted index 3615fa9..e89ed5a 100755 --- a/t/test-unconflicted +++ b/t/test-unconflicted @@ -14,7 +14,10 @@ cd "$TMP" # Clean up detritus from possible previous runs: git checkout master "$GIT_IMERGE" remove --name=a-b || true -for b in a-b-merge a-b-rebase a-b-rebase-with-history a-b-full +for b in a-b-merge \ + a-b-rebase a-b-rebase-with-history \ + a-b-border a-b-border-with-history a-b-border-with-history2 \ + a-b-full do git branch -D $b || true done @@ -26,6 +29,9 @@ git checkout a GIT_EDITOR=cat "$GIT_IMERGE" simplify --goal=merge --branch=a-b-merge "$GIT_IMERGE" simplify --goal=rebase --branch=a-b-rebase "$GIT_IMERGE" simplify --goal=rebase-with-history --branch=a-b-rebase-with-history +"$GIT_IMERGE" simplify --goal=border --branch=a-b-border +"$GIT_IMERGE" simplify --goal=border-with-history --branch=a-b-border-with-history +"$GIT_IMERGE" simplify --goal=border-with-history2 --branch=a-b-border-with-history2 "$GIT_IMERGE" remove git checkout a