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 <newren@gmail.com>
pull/43/head
Elijah Newren 4 years ago
parent 41787ff365
commit 5e04dff097

@ -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.

@ -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

@ -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 <user@organization.tld> 2000000000 +0100
committer Full Name <user@organization.tld> 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 <user@organization.tld> 2000050000 +0100
committer Full Name <user@organization.tld> 2000050000 +0100
data 2
G
from :2
M 100644 :3 moduleA/keepme
commit refs/heads/branchI
mark :5
author Full Name <user@organization.tld> 2000070000 +0100
committer Full Name <user@organization.tld> 2000070000 +0100
data 29
I: Merge commit 'D' into 'H'
from :4
merge :2
commit refs/heads/branchO
mark :6
author Full Name <user@organization.tld> 2000080000 +0100
committer Full Name <user@organization.tld> 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 <user@organization.tld> 2000092000 +0100
committer Full Name <user@organization.tld> 2000092000 +0100
data 2
L
from :6
M 100644 :7 moduleA/keepme
commit refs/heads/master
mark :9
author Full Name <user@organization.tld> 2000099000 +0100
committer Full Name <user@organization.tld> 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 <user@organization.tld> 3000000000 +0100
committer Full Name <user@organization.tld> 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 <user@organization.tld> 3000030000 +0100
committer Full Name <user@organization.tld> 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 <user@organization.tld> 3000060000 +0100
committer Full Name <user@organization.tld> 3000060000 +0100
data 2
W
from :13
M 100644 :14 moduleA/keepme
done
Loading…
Cancel
Save