Where does a Git branch start and what is its length?
Every now and then I'm asked, on what commit a certain branch on git starts or if a certain commit has been created on a specific branch. The end point of a branch is pretty clear: that's where the branch label sits. But - where did it start? The trivial answer would be: on that commit where we created that branch. But that information is, as far as I know now, and that's why I'm asking the question, lost after the first commits.
As long as we know the commit where we branched off, we can draw the graph to make it clear:
A - B - C - - - - J [master]
D - E - F - G [branch-A]
H - - I [branch-B]
I've created branch-B at commit E
so that's the "start". I know that, because I did it. But can others recognize it the same way? We could draw the same graph like that:
A - B - C - - - - J [master]
F - G [branch-A]
/
D - E
H - I [branch-B]
So, looking at the graph now, which branch started at E
, which one at B
? Is commit D
a member of both branches or can we clearly decide whether it belongs to branch-A or branch-B?
This sounds somewhat philosophical but it really isn't. Supervisors sometimes like to know, when a branch has been started (it usually marks the start of a task) and to which branch some changes belong to (to get the purpose of some change - was it required for the work) and I'd like to know if git offers infos (tools, commands) or definitions to answer those questions correctly.
In Git, you could say that every branch starts at the root commit, and that would be quite literally true. But I guess that's not very helpful for you. What you could do instead is to define "the start of a branch" in relation to other branches. One way you can do this is to use
git show-branch branch1 branch2 ... branchN
and that will show you the common commit between all specified branches at the bottom of the output (if there is, in fact, a common commit).
Here's an example from the Linux Kernel Git documentation for show-branch
$ git show-branch master fixes mhf
* [master] Add 'git show-branch'.
! [fixes] Introduce "reset type" flag to "git reset"
! [mhf] Allow "+remote:local" refspec to cause --force when fetching.
---
+ [mhf] Allow "+remote:local" refspec to cause --force when fetching.
+ [mhf~1] Use git-octopus when pulling more than one heads.
+ [fixes] Introduce "reset type" flag to "git reset"
+ [mhf~2] "git fetch --force".
+ [mhf~3] Use .git/remote/origin, not .git/branches/origin.
+ [mhf~4] Make "git pull" and "git fetch" default to origin
+ [mhf~5] Infamous 'octopus merge'
+ [mhf~6] Retire git-parse-remote.
+ [mhf~7] Multi-head fetch.
+ [mhf~8] Start adding the $GIT_DIR/remotes/ support.
*++ [master] Add 'git show-branch'.
In that example, master
is being compared with the fixes
and mhf
branches. Think of this output as a table, with each branch represented by its own column, and each commit getting its own row. Branches that contain a commit will have a +
or -
show up in their column in the row for that commit.
At the very bottom of the output, you'll see that all 3 branches share a common ancestor commit, and that it is in fact the head
commit of master
:
*++ [master] Add 'git show-branch'.
This means that both fixes
and mhf
were branched off of that commit in master
.
Alternative solutions
Of course that's only 1 possible way to determine a common base commit in Git. Other ways include git merge-base
to find common ancestors, and git log --all --decorate --graph --oneline
or gitk --all
to visualize the branches and see where they diverge (though if there are a lot of commits that becomes difficult very quickly).
Other questions from original poster
As for these questions you had:
Is commit D
a member of both branches or can we clearly decide whether it belongs to branch-A
or branch-B
?
D
is a member of both branches, it's an ancestor commit for both of them.
Supervisors sometimes like to know, when a branch has been started (it usually marks the start of a task) ...
In Git, you can rewrite the history of the entire commit tree(s) and their branches, so when a branch "starts" is not as set in stone as in something like TFS or SVN. You can rebase
branches onto any point in time in a Git tree, even putting it before the root commit! Therefore, you can use it to "start" a task at any point in time in the tree that you want.
This is a common use case for git rebase
, to sync branches up with the latest changes from an upstream branch, to push them "forward" in time along the commit graph, as if you had "just started" working on the branch, even though you've actually been working on it for a while. You could even push branches back in time along the commit graph, if you wanted to (though you might have to resolve a lot of conflicts, depending on the branch contents...or maybe you won't). You could even insert or delete a branch from right in the middle of your development history (though doing so would probably change the commit shas of a lot of commits). Rewriting history is one of the primary features of Git that makes it so powerful and flexible.
This is why commits come with both an authored date (when the commit was originally authored), and a committed date (when the commit was last committed to the commit tree). You can think of them as analogous to create time-date and last-modified time-date.
Supervisors sometimes like to know... to which branch some changes belong to (to get the purpose of some change - was it required for the work).
Again, because Git allows you to rewrite history, you can (re)base a set of changes on pretty much any branch/commit in the commit graph that you want. git rebase
literally allows you to move your entire branch around freely (though you might need to resolve conflicts as you go, depending on where you move the branch to and what it contains).
That being said, one of the tools you can use in Git to determine which branches or tags contains a set of changes is the --contains
:
# Which branches contains commit X?
git branch --all --contains X
# Which tags contains commit X?
git tag --contains X
The bounty notice on this question asks,
I'd be interested in knowing whether or not thinking about Git branches as having a defined "beginning" commit other than the root commit even makes sense?
It kind of does except:
gh-pages
) I prefer considering the start of a branch being the commit of another branch from which said branch has been created (tobib's answer without the ~1
), or (simpler) the common ancestor .
(also in "Finding a branch point with Git?", even though the OP mentioned being not interested in common ancestors):
git merge-base A master
That means:
filter-branch
) The second one makes more sense for git, which is all about merge and rebase between branches.
Supervisors sometimes like to know, when a branch has been started (it usually marks the start of a task) and to which branch some changes belong to (to get the purpose of some change - was it required for the work)
Branches are simply the wrong marker for that: due to the transient nature of branches (which can be renamed/moved/rebased/deleted/...), you cannot mimick a "change set" or an "activity" with a branch, to represent a "task".
That is an XY problem: the OP is asking for an attempted solution (where does a branch starts) rather than the actual problem (what could be considered a task in Git).
To do that (representing a task), you could use:
git notes
to a commit to memorize to which "work item" said commit has been created (contrary to tags, notes can be rewritten if the commit is amended or rebased). Perhaps you are asking the wrong question. IMO, it doesn't make sense to ask where a branch starts since a given branch includes all changes made to every file ever (ie since the initial commit).
On the other hand, asking where two branches diverged is definitely a valid question. In fact, this seems to be exactly what you want to know. In other words, you don't really want to know information about a single branch. Instead you want to know some information about comparing two branches.
A little bit of research turned up the gitrevisions man page which describes the details of referring to specific commits and ranges of commits. In particular,
To exclude commits reachable from a commit, a prefix ^ notation is used. Eg ^r1 r2 means commits reachable from r2 but exclude the ones reachable from r1.
This set operation appears so often that there is a shorthand for it. When you have two commits r1 and r2 (named according to the syntax explained in SPECIFYING REVISIONS above), you can ask for commits that are reachable from r2 excluding those that are reachable from r1 by ^r1 r2 and it can be written as r1..r2.
So, using the example from your question, you can get the commits where branch-A
diverges from master
with
git log master..branch-A
链接地址: http://www.djcxy.com/p/23610.html
上一篇: Git:如何在提交之间来回移动
下一篇: Git分支从哪里开始,它的长度是多少?