joachim
joachim

Reputation: 30781

how do I determine whether git history is linear?

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

Answers (4)

torek
torek

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

prahlad venkata
prahlad venkata

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

Andrew Walker
Andrew Walker

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

LeGEC
LeGEC

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

Related Questions