From 5e04dff0977a58ba8970e1450320ff9ad4e410b8 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 1 Jan 2020 10:49:56 -0800 Subject: [PATCH] filter-repo: add new --no-ff option Some projects have a strict --no-ff merging policy. With the default behavior of --prune-degenerate, we can prune merge commits in a way that transforms the history into a fast-forward merge. Consider this example: * There are two independent commits or branches, named B & C, which are both built on top of A so that history look like this diagram: A \ \ \ B \ -C * Someone runs the following sequence of commands: * git checkout A * git merge --no-ff B * git merge --no-ff C * This will result in a history that looks like: A---AB---AC \ \ / / \ B / \ / -C- * Later, someone comes along and runs filter-repo, specifying to remove the only path(s) that were modified by B. That would naturally remove commit B and the no-longer-necessary merge commit AB. For someone using a strict no-ff policy, the desired history is A---AC \ / C However, the default handling for --prune-degenerate would notice that AC merely merges C into its own ancestor A, whereas the original AC merged C into something separate (namely, AB). So, it would say that AC has become degenerate and prune it, leaving the simple history of A \ C For projects not using a strict no-ff policy, this simpler history is probably better, but for folks that want a strict no-ff policy, it is unfortunate. Provide a --no-ff option to tweak the --prune-degenerate behavior so that it ignores the first parent being an ancestor of another parent (leaving the first parent unpruned even if it is or becomes degenerate in this fashion). Signed-off-by: Elijah Newren --- git-filter-repo | 11 ++++ t/t9390-filter-repo.sh | 1 + t/t9390/degenerate-keepme-noff | 113 +++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 t/t9390/degenerate-keepme-noff diff --git a/git-filter-repo b/git-filter-repo index c1ff923..4532a16 100755 --- a/git-filter-repo +++ b/git-filter-repo @@ -1870,6 +1870,11 @@ EXAMPLES "merge commits have no file changes, they can be pruned. The " "default ('auto') is to only prune empty merge commits which " "become degenerate (not which started as such).")) + parents.add_argument('--no-ff', action='store_true', + help=_("Even if the first parent is or becomes an ancestor of another " + "parent, do not prune it. This modifies how " + "--prune-degenerate behaves, and may be useful in projects who " + "always use merge --no-ff.")) callback = parser.add_argument_group(title=_("Generic callback code snippets")) callback.add_argument('--filename-callback', metavar="FUNCTION_BODY", @@ -3034,6 +3039,12 @@ class RepoFilter(object): self._orig_graph.is_ancestor(orig_parents[cur], orig_parents[other]): continue + # Some folks want their history to have all first parents be merge + # commits (except for any root commits), and always do a merge --no-ff. + # For such folks, don't remove the first parent even if it's an + # ancestor of other commits. + if self._args.no_ff and cur == 0: + continue # Okay so the cur-th parent is an ancestor of the other-th parent, # and it wasn't that way in the original repository; mark the # cur-th parent as removable. diff --git a/t/t9390-filter-repo.sh b/t/t9390-filter-repo.sh index ea7508f..4003759 100755 --- a/t/t9390-filter-repo.sh +++ b/t/t9390-filter-repo.sh @@ -47,6 +47,7 @@ filter_testcase empty less-empty-keepme --path keepme --prune-empty=never \ filter_testcase degenerate degenerate-keepme --path moduleA/keepme filter_testcase degenerate degenerate-moduleA --path moduleA filter_testcase degenerate degenerate-globme --path-glob *me +filter_testcase degenerate degenerate-keepme-noff --path moduleA/keepme --no-ff filter_testcase unusual unusual-filtered --path '' filter_testcase unusual unusual-mailmap --mailmap ../t9390/sample-mailmap diff --git a/t/t9390/degenerate-keepme-noff b/t/t9390/degenerate-keepme-noff new file mode 100644 index 0000000..d28c7a4 --- /dev/null +++ b/t/t9390/degenerate-keepme-noff @@ -0,0 +1,113 @@ +feature done +blob +mark :1 +data 10 +keepme v1 + +reset refs/heads/master +commit refs/heads/master +mark :2 +author Full Name 2000000000 +0100 +committer Full Name 2000000000 +0100 +data 2 +A +M 100644 :1 moduleA/keepme + +blob +mark :3 +data 10 +keepme v2 + +commit refs/heads/branchO +mark :4 +author Full Name 2000050000 +0100 +committer Full Name 2000050000 +0100 +data 2 +G +from :2 +M 100644 :3 moduleA/keepme + +commit refs/heads/branchI +mark :5 +author Full Name 2000070000 +0100 +committer Full Name 2000070000 +0100 +data 29 +I: Merge commit 'D' into 'H' +from :4 +merge :2 + +commit refs/heads/branchO +mark :6 +author Full Name 2000080000 +0100 +committer Full Name 2000080000 +0100 +data 29 +J: Merge commit 'H' into 'D' +from :2 +merge :4 + +blob +mark :7 +data 10 +keepme v3 + +commit refs/heads/branchO +mark :8 +author Full Name 2000092000 +0100 +committer Full Name 2000092000 +0100 +data 2 +L +from :6 +M 100644 :7 moduleA/keepme + +commit refs/heads/master +mark :9 +author Full Name 2000099000 +0100 +committer Full Name 2000099000 +0100 +data 29 +P: Merge commit 'M' into 'N' +from :2 +merge :8 + +blob +mark :10 +data 10 +keepme v4 + +commit refs/heads/master +mark :11 +author Full Name 3000000000 +0100 +committer Full Name 3000000000 +0100 +data 2 +Q +from :9 +M 100644 :10 moduleA/keepme + +blob +mark :12 +data 10 +keepme v5 + +commit refs/heads/master +mark :13 +author Full Name 3000030000 +0100 +committer Full Name 3000030000 +0100 +data 2 +T +from :11 +M 100644 :12 moduleA/keepme + +blob +mark :14 +data 10 +keepme v6 + +commit refs/heads/master +mark :15 +author Full Name 3000060000 +0100 +committer Full Name 3000060000 +0100 +data 2 +W +from :13 +M 100644 :14 moduleA/keepme + +done