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

旧巷老猫 提交于 2019-12-06 12:34:43

问题


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

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?


回答1:


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


1The 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.



来源:https://stackoverflow.com/questions/49310139/get-commit-where-merged-branch-forked-from-with-intermediate-merge

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!