简体   繁体   中英

Get commit where merged branch forked from (with intermediate merge)

Lets use the latest available git 2.16.2 and tig 2.3.3.

cd /tmp && mkdir fit && cd fit

git init
touch m1 && git add m1 && git commit -m "master 1"
touch m2 && git add m2 && git commit -m "master 2"
git checkout -b develop
touch d1 && git add d1 && git commit -m "develop 1"
git checkout master
touch m3 && git add m3 && git commit -m "master 3"
git checkout develop
git merge master --no-edit
touch d2 && git add d2 && git commit -m "develop 2"
touch d3 && git add d3 && git commit -m "develop 3"
git checkout master
git merge develop --no-edit
touch m4 && git add m4 && git commit -m "master 4"

git reflog expire --expire=now --all && git gc --prune=now --aggressive

TIG 在此处输入图片说明

It is so easy to retrieve the last commit in develop branch:

git --no-pager show -s --format=%B $(git rev-parse develop)

develop 3

But I couldn't retrieve the first commit in develop branch. So I couldn't find the commit where branch forked from.

git merge-base --fork-point develop
git rev-list develop..master
git rev-list develop master
git rev-list master develop
git rev-list ^develop master

Results are useless.

I've found a solution for question How to get commit where merged branch forked from

git oldest-ancestor master develop
git oldest-ancestor develop master

Results are useless too.

But tig and git log --graph are still able to see that develop 1 was the first commit of the develop branch and this branch were forked from master 2 commit in master .

Is it possible to retrieve master 2 with current git console tools?

 git --no-pager show -s --format=%B $(git rev-parse develop) 

Even simpler:

git --no-pager show -s --format=%B develop

or:

git --no-pager log --no-walk --format=%B develop

( show -s and log --no-walk are nearly the same thing; the key item here is to drop the unnecessary git rev-parse ).

But I couldn't retrieve the first commit in develop branch

The first commit in that branch is master 1 , or 27ee6b8 in your image (the hash ID will vary with the time that the commit is made). This is also the first commit in branch master .

The problem here is that branches do not have "starting points". Branches are , in one sense, the structure—the graph fragment—that one reaches by starting at the ending point and working back to the beginning. This means that some commits are on many branches; typically, the root commit, the first commit you make in a repository, is on every branch (though in a repository with multiple roots, some roots may not be on some branches).

A branch name is, in general—there are some exceptions—synonymous with the tip commit on that branch, which is why you don't need an explicit git rev-parse . The key feature of a branch name, however, is that it moves over time, so that it always names the tip commit of the branch.

See also What exactly do we mean by "branch"?

If you wish to mark some particular commit, in order to remember it later, the usual tool for this is a Git tag. A tag is very much like a branch name, in that it identifies one specific commit. Unlike a branch name, however, a tag is never supposed to move, and Git won't move it automatically.

 git reflog expire --expire=now --all 

Reflogs exist specifically to be able to observe the movement (over time) of references. The reflog for a branch name like develop retains, for 30 or 90 days by default, 1 the hash IDs that develop used to identify. By expiring them, you've removed your ability to go back in time and look at develop@1 , develop@2 , and so on. If you had retained them, you could look for the oldest develop that exists. That might be when it was born, and you can often tell:

05d0c47 master@{37}: clone: from ...

(indicating that master was born at this point).

Unfortunately, reflogs do expire, so this is not completely reliable. The tag is reliable, but may be annoying since git log will decorate commits with their tags. If there's a procedure for finding the interesting commit, you can use that. In this case, there is such a procedure: you want the commit(s) that was or were the merge base(s) of the merge.

To find the merge base, find the merge itself, then find its parents:

m=11c63bc  # this is the merge
p1=$(git rev-parse ${m}^1)
p2=$(git rev-parse ${m}^2)

Now $p1 and $p2 are the two parents of this merge. (A merge can have more than two parents, but most merges have only two.) The common point where these two branches were last merged is the merge base of the two parents:

git merge-base --all $p1 $p2

Since there is only one merge base, this prints just the one commit hash. If there were several, it would print all of them because we used --all . Leaving out --all , we would get one chosen at (apparently) random (the actual one chosen depends on the algorithm used to find the merge bases).

As before, one does not need a lot of temporary variables—we could do:

mbases=$(git merge-base --all ${m}^1 ${m}^2)

since git merge-base takes the same commit-specifier syntax as git rev-parse : the ^1 and ^2 suffixes work the same there (and indeed work the same in most Git commands).


1 The expiration times are configurable. The shorter time, 30 days by default, is for hash IDs that are not reachable from the current value of the reference; the longer 90-day default is for hash IDs that are reachable from the current value of the reference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM