-1

Is it possible to compare git commits programatically or with their integer hex values? Are later commits greater in integer than its previous one? So for example, if we can get the hashes in pseudocode

int commit_a = myrepo.getCurrentCommit();
int commit_b = myrepo.getPreviousCommit();

if(commit_a > commit_b)
printf("Commit A is later than Commit B");
else
printf("Commit B is later than Commit A");

NOTE: I have edited my question to ask if there are git libraries that can do the above example. Or how to implement my own.

Nederealm
  • 447
  • 4
  • 15
  • No; hashes are, for all intents and purposes, random numbers drawn from a uniform distribution. Whether one hash is less than or greater than another says nothing about the commits that produced the hashes. – chepner Oct 30 '18 at 15:35
  • Pick any repository you like, and it should be obvious after a few moment's inspection that commit hashes are *not* ordered in the way you ask about. – chepner Oct 30 '18 at 15:44

2 Answers2

2

Commits are identified by their "sha", which is best considered not as an integer but as an opaque binary value. It does not increment and is not directly comparable with another commit.

However, inherent in each commit is enough information to determine that commit's history, so you could conceivably compare commits based on history. There's a pretty solid question and answer about how to do that already: How can I tell if one commit is a descendant of another commit?

erik258
  • 14,701
  • 2
  • 25
  • 31
1

Commits in Git form a Directed Acyclic Graph or DAG.

In any graph, we have an adjacency list (since a graph is defined as G = (V, E) where V and E are the vertex and edge sets respectively): any given vertex / node in the graph is either one hop away from some other vertex, through some edge, or is further away or is not connected at all:

  C--D
 /
B------E  G--H
 \    /
  A  F

Node A connects to B (and vice versa), B connects to C and E, and so on. G and H connect to each other, but not to the rest of the graph, so this graph consists of two disjoint sub-graphs. (These edges do not have directions.)

In a directed graph, there is a notion of predecessor and successor as each connection has an arrow:

A->B->C
   |
   v
   D

Here A connects to B, and B connects to both C and D. So B is the successor of A, and C and D are both successors of B (but are not connected to each other). Put in terms of predecessors, A is the predecessor of B, and B is the predecessor of both C and D.

If the graph has no loops—is acyclic—we can perform a transitive closure operation using the predecessor/successor operations. Git's graph is both directed and acyclic—it's a DAG—so we can do this iwth Git.

Inside Git, the arrows are actually backwards (for implementation reasons), but we still perform the same transitive closure. When we do so, we find that A is an ancestor of B, C, and D:

A <-B <-C
    ^
    |
    D

because we can start from any of these commits and work our way back to A. B is an ancestor of C and D. The opposite relationship is descendant: D is a descendant of A.

One must be a bit careful here. There is no ancestor or descendant relationship between C and D. That is, C is not an ancestor of D, but C is not a descendant of D either. You cannot just say "not ancestor therefore descendant": the two may simply be not relatable. This is also true for some commits if the DAG has disjoint subgraphs, which is allowed:

A <-B <-C   <-- master1

D <-E   <-- master2

While D is an ancestor of E, D has no relationship to any of A, B, or C.

In any case, the Git command that tests for "ancestor-ness" is:

git merge-base --is-ancestor

which takes two commit hash IDs (or other commit specifiers) and answers, as a true or false query (via exit status with 0 meaning "yes, is an ancestor"), the question: is the commit named by the first argument an ancestor of the commit named by the second. That is:

hash1=$(git rev-parse $thing1^{commit}) || die "$thing1 does not name a git commit"
hash2=$(git rev-parse $thing2^{commit}) || die "$thing2 does not name a git commit"
if git merge-base --is-ancestor $hash1 $hash2; then
    echo "$thing1 is an ancestor of $thing2"
else
    if git merge-base --is-ancestor $hash2 $hash1; then
        echo "$thing1 is a descendant of $thing2"
    else
        echo "$thing1 and $thing2 are not relatable"
    fi
fi

The "not relatable" answer emerges because, as we saw above, we get only a partial order, not a total order, from our predecessor/successor notion.

(For Git's purposes, a commit is its own ancestor, so git merge-base --is-ancestor $hash1 $hash1 is always true.)

torek
  • 448,244
  • 59
  • 642
  • 775