Reputation: 30781
Given two git commits, how can I to determine, ideally with plumbing commands, whether the history between them is linear?
In other words, I want to know whether I have this:
A-B-C-D-E-F-G
rather than this:
/-C-\
A-B -E-F-G
\-D-/
Upvotes: 4
Views: 1442
Reputation: 488183
"Between" is a slippery notion in graphs.
If you have something like your example, LeGEC's answer is fine (though I would simplify it slightly further to git rev-list --count --min-parents=2 A..G
and check for a nonzero result).
But suppose the graph fragment looks like this:
A--B--C
\
G
/
D--E--F
Is A..G
to be considered linear? Here A
is a root commit but it doesn't really matter, we could use B..G
or C..G
just as well. The issue here is that there is another root commit, D
, which is reachable from G
but is not a descendant of A
. The commit that is a merge commit that is in the set of commits "reachable from G
, excluding commits reachable from A
" is G
itself. Or:
A--B--C--G
/
D--F
/
E
Here there are three root commits, A
, D
, and E
. The set of commits reachable from G
minus the set reachable from A
leaves all of B
, C
, D
, E
, F
, and G
, and both C
and F
are merge commits.
If you wish to constrain the test to "commits that are descendants of A
and ancestors of G
, including G
but excluding A
itself", add --ancestry-path
to the check for merges:
git rev-list --count --min-parents=2 --ancestry-path A..G
This will count the embedded loop in your example as nonlinear, but count my second fragment as linear (commits C
and F
are not descendants of A
in the last graph, for instance). My first case would not be considered linear here since commit G
is selected and is a merge commit, so for this particular case, you would have to use A..G^@
as your range. (This is a slight correction from earlier: we want all parents of G
, not just the first one; we will rely on --ancestry-path
to pick the right one(s).)
Upvotes: 0
Reputation: 371
git rev-list --min-parents=2 --count A..G
will give number of commits having more than one parent. Count '0' indicates a linear history.
Upvotes: 3
Reputation: 42470
If you're happy to compromise on something higher level than porcelain, you could use gitpython, looking for commits with one or more parents.
import git
def is_linear(commit):
ncommits = len(commit.parents)
if ncommits == 0:
return True
elif ncommits == 1:
return is_linear(commit.parents[0])
else:
return False
repo = git.Repo('.')
print is_linear(repo.commit())
Upvotes: 0
Reputation: 51850
git log --min-parents=2
will only display commits who have at least 2 parents (e.g : merge commits)
You can run :
# for esthetics : --oneline will output one single line per filtered commit
git log --oneline --min-parents=2 A..G
# if output is empty : linear history
# and :
git log --oneline --min-parents=2 A..G | wc -l
# should give you the number of merge commits between A and G
Upvotes: 3