问题
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