Git 分支从哪里开始,它的长度是多少?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17581026/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 08:46:05  来源:igfitidea点击:

Where does a Git branch start and what is its length?

gitversion-controlgit-branch

提问by Andreas Dolk

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

时不时有人问我,git 上的某个分支在什么时候提交,或者某个提交是否已在特定分支上创建。分支的终点非常清楚:那是分支标签所在的位置。但是——它从哪里开始?简单的答案是:在我们创建该分支的那个提交上。但是,据我现在所知,这些信息在第一次提交后丢失了,这就是我问这个问题的原因。

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 Eso 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:

我在提交时创建了分支 B,E所以这就是“开始”。我知道,因为我做到了。但是其他人能以同样的方式识别它吗?我们可以像这样绘制相同的图形:

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 Da member of both branches or can we clearly decide whether it belongs to branch-A or branch-B?

那么,现在看图表,哪个分支开始于E,哪个分支开始于B?commitD是两个分支的成员还是我们可以明确决定它属于分支A还是分支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.

这听起来有点哲学,但事实并非如此。主管有时想知道,何时启动了一个分支(它通常标志着任务的开始)以及某些更改属于哪个分支(以获得某些更改的目的 - 是否需要工作),我会想知道 git 是否提供信息(工具、命令)或定义来正确回答这些问题。

采纳答案by Andreas Dolk

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 中,你可以说每个分支都从根提交开始,这确实是真的。但我想这对你不是很有帮助。您可以做的是定义与其他分支相关的“分支的开始”。一种方法是使用

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

这是Linux Kernel Git 文档中的一个示例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, masteris being compared with the fixesand mhfbranches. 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.

在那个例子中,master正在与fixesmhf分支进行比较。将此输出视为一个表,每个分支由其自己的列表示,每个提交都有自己的行。包含提交的分支将在该提交的行中的列中显示+-

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 headcommit of master:

在输出的最底部,您会看到所有 3 个分支共享一个共同的祖先提交,并且它实际上是以下head提交master

*++ [master] Add 'git show-branch'.

This means that both fixesand mhfwere branched off of that commit in master.

这意味着fixesmhf都从master.

Alternative solutions

替代解决方案

Of course that's only 1 possible way to determine a common base commit in Git. Other ways include git merge-baseto find common ancestors, and git log --all --decorate --graph --onelineor gitk --allto visualize the branches and see where they diverge (though if there are a lot of commits that becomes difficult very quickly).

当然,这只是确定 Git 中公共基础提交的一种可能方法。其他方法包括git merge-base找到共同的祖先,和/git log --all --decorate --graph --onelinegitk --all可视化分支并查看它们的分歧(尽管如果有很多提交很快就会变得困难)。

Other questions from original poster

来自原始海报的其他问题

As for these questions you had:

至于你提出的这些问题:

Is commit Da member of both branches or can we clearly decide whether it belongs to branch-Aor branch-B?

commitD是两个分支的成员还是我们可以清楚地决定它是属于branch-A还是branch-B

Dis a member of both branches, it's an ancestor commit for both of them.

D是两个分支的成员,它是两个分支的祖先提交。

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 whena branch "starts" is not as set in stone as in something like TFS or SVN. You can rebasebranches 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.

在Git中,你可以重写整个历史提交树(S)及其分支机构,所以一个分支“开始”是不是一成不变的,如像TFS或SVN。您可以rebase分支到 Git 树中的任何时间点,甚至可以将其放在根提交之前!因此,您可以使用它在所需的树中的任何时间点“启动”任务。

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.

这是 的一个常见用例git rebase,将分支与来自上游分支的最新更改同步,沿着提交图及时将它们“向前”推送,就好像您“刚刚开始”在分支上工作一样,即使您实际上已经研究了一段时间。如果您愿意,您甚至可以沿着提交图及时将分支推回(尽管您可能必须解决很多冲突,具体取决于分支内容……或者您可能不会)。您甚至可以在开发历史的中间插入或删除一个分支(尽管这样做可能会更改许多提交的提交 shas)。重写历史记录是 Git 的主要功能之一,这使它变得如此强大和灵活。

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

同样,因为 Git 允许您重写历史记录,所以您可以(重新)根据您想要的提交图中的几乎任何分支/提交来(重新)建立一组更改。 git rebase从字面上看,您可以自由地移动整个分支(尽管您可能需要随时解决冲突,具体取决于您将分支移动到的位置和它包含的内容)。

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:

话虽如此,您可以在 Git 中用来确定哪些分支或标签包含一组更改的工具之一是--contains

# Which branches contains commit X?
git branch --all --contains X

# Which tags contains commit X?
git tag --contains X

回答by VonC

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?

我很想知道将 Git 分支视为具有定义的“开始”提交而不是根提交是否有意义?

It kind of does except:

除了:

That means:

这意味着:

  • the first definition gives you a fixed commit(which might never change except in case of massive filter-branch)
  • the second definition gives you a relative commit(relative to anotherbranch) which can change at any time (the other branch can be deleted)
  • 第一个定义为您提供了一个固定的提交(可能永远不会改变,除非是大规模的filter-branch
  • 第二个定义为您提供了一个可以随时更改的相对提交(相对于另一个分支)(另一个分支可以被删除)

The second one makes more sense for git, which is all about merge and rebase between branches.

第二个对 git 更有意义,它是关于分支之间的合并和变基。

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

这是一个XY 问题:OP 要求尝试解决方案(分支从哪里开始)而不是实际问题(在 Git 中可以视为任务)。

To do that (representing a task), you could use:

为此(代表一项任务),您可以使用:

  • tags: they are immutable (once associated to a commit, that commit is no longer supposed to move/be rebased), and any commits between two well-named tags can represent an activity.
  • some git notesto 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).
  • hooks (to associate a commit to some "external" object like a "work item", based on the commit message). That is what the bridge Git-RTC -- IBM Rational Team Concert -- does with a pre-receive hook) The point is: the start of a branch does not always reflect the start of a task, but merely the continuation of an history which can change, and whom sequence should represent a logical set of changes.
  • 标签:它们是不可变的(一旦与提交相关联,该提交不再应该移动/重新定位),并且两个命名良好的标签之间的任何提交都可以代表一个活动。
  • 一些git notes提交以记住已创建所述提交的“工作项”(与标签相反,如果提交被修改或重新定位,则可以重写注释)。
  • hooks(根据提交消息将提交关联到某个“外部”对象,例如“工作项”)。这就是桥梁 Git-RTC —— IBM Rational Team Concert —— 用 pre-receive 钩子所做的事情)重点是:分支的开始并不总是反映任务的开始,而只是历史的延续哪个可以改变,谁的顺序应该代表一组逻辑的变化。

回答by jthill

There are two separate concerns here. Starting from your example,

这里有两个不同的问题。从你的例子开始,

A - B - C - - - - J     [master]
     \
      \       F - G     [branch-A]
       \     /
        D - E
             \
              H - I     [branch-B]

[...] 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 get the purpose of some change - was it required for the work)

A - B - C - - - - J     [master]
     \
      \       F - G     [branch-A]
       \     /
        D - E
             \
              H - I     [branch-B]

[...] 主管有时想知道,分支何时启动(通常标志着任务的开始)以及某些更改属于哪个分支(以获得某些更改的目的 - 是否需要工作)

two factual observations before we get to the meat:

在我们开始吃肉之前,有两个事实观察:

First observation: what your supervisor wants to know is the mapping between commits and some external workorder-ish record: what commits address bug-43289 or featureB? Why are we changing strcatusage in longmsg.c? Who's going to pay for the twenty hours between your previous push and this one? The branch names themselves don't matter here, what matters is the commits' relationships with external administrative records.

第一个观察:你的主管想知道的是提交和一些外部工作指令记录之间的映射:什么提交解决了 bug-43289 或 featureB?为什么我们要改变 的strcat用法longmsg.c?谁会为您之前的推送和这次推送之间的 20 小时买单?分支名称本身在这里并不重要,重要的是提交与外部管理记录的关系。

Second observation: whether branch-Aor branch-Bgets published first (via say merge or rebase or push), the work in commits D and E has to go with it right then and not be duplicated by any subsequent operation. It makes no difference at all what was current when those commits were made. Branch names don't matter here, either. What matters is commits' relations with each other via the ancestry graph.

第二个观察:是否branch-A还是branch-B被首次公布(比如通过合并或重订或推),在提交作品D和E具有去用它当时的权利不受任何后续操作被复制。进行这些提交时的当前内容完全没有区别。分支名称在这里也不重要。重要的是通过祖先图提交彼此之间的关系。



So my answer is, so far as any history is concerned branch names don't matter at all. They're convenience tags showing which commit is current for some purpose specific to that repo, nothing more. If you want some useful moniker in the default merge-commit message subject line, git branch some-useful-namethe tip before merging, and merge that. They're the same commits either way.

所以我的回答是,就任何历史而言,分支名称根本不重要。它们是方便标签,显示哪个提交是针对该存储库特定目的的当前提交,仅此而已。如果您想在默认的合并提交消息主题行中使用一些有用的绰号,请在合并git branch some-useful-name之前提供提示,然后将其合并。无论哪种方式,它们都是相同的提交。

Tying whatever branch name the developer had checked out at the time of commit with some external record -- or anything at all -- is deep into "all fine so long as everything works" territory. Don't Do It. Even with the restricted usage common in most VCS's, your D-E-{F-G,H-I}will occur sooner rather than later, and then your branch naming conventions will have to be adapted to handle that, and then something more complicated will show up, . . .

将开发人员在提交时签出的任何分支名称与一些外部记录(或任何其他记录)捆绑在一起,都深入到“只要一切正常,一切都好”的领域。不要这样做。即使在大多数 VCS 中都有常见的受限用法,您D-E-{F-G,H-I}迟早会发生,然后您的分支命名约定必须适应处理,然后会出现更复杂的东西,. . .

Why bother? Put the report number(s) prompting the work in a tagline at the bottom of your commit messages and be done with it. git log --grep(and git in general) is blazingly fast for good reason.

何苦?将提示工作的报告编号放在提交消息底部的标语中,然后完成。 git log --grep(和一般的 git)有充分的理由非常快。

Even a fairly flexible prep hook to insert taglines like this is trivial:

即使是一个相当灵活的准备挂钩来插入这样的标语也是微不足道的:

branch=`git symbolic-ref -q --short HEAD`                     # branch name if any
workorder=`git config branch.${branch:+$branch.}x-workorder`  # specific or default config
tagline="Acme-workorder-id: ${workorder:-***no workorder supplied***}"
sed -i "/^ *Acme-workorder-id:/d; $a$tagline" ""

and here's the basic pre-receive hook loop for when you need to inspect every commit:

这是当您需要检查每个提交时的基本预接收钩子循环:

while read old new ref; do              # for each pushed ref
        while read commit junk; do      # check for bad commits

                # test here, e.g. 
                git show -s $commit | grep -q '^ *Acme-workorder-id: ' \
                || { rc=1; echo commit $commit has no workorder associated; }
                # end of this test

        done <<EOD
        $(git rev-list $old..$new)
EOD
done
exit $rc

The kernel project uses taglines like this for copyright signoff and code-review recording. It really couldn't get much simpler or more robust.

内核项目使用这样的标语来进行版权签收和代码记录。它真的不能变得更简单或更强大。

Note that I did some hand-mangling after c&p to de-specialize real scripts. Keyboard-to-editbox warning

请注意,我在 c&p 之后做了一些手工修改,以取消对真实脚本的专业化。键盘到编辑框警告

回答by Code-Apprentice

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(i.e. 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 pagewhich describes the details of referring to specific commits and ranges of commits. In particular,

一些研究发现了gitrevisions 手册页,其中描述了引用特定提交和提交范围的详细信息。特别是,

To exclude commits reachable from a commit, a prefix ^ notation is used. E.g. ^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.

要从提交中排除可访问的提交,使用前缀 ^ 表示法。例如 ^r1 r2 表示可从 r2 访问的提交,但排除可从 r1 访问的提交。

这个集合操作经常出现,所以有一个简写。当您有两个提交 r1 和 r2(根据上面的 SPECIFYING REVISIONS 中解释的语法命名)时,您可以请求可从 r2 访问的提交,不包括那些通过 ^r1 r2 从 r1 可访问的提交,它可以写为 r1。 .r2。

So, using the example from your question, you can get the commits where branch-Adiverges from masterwith

因此,使用从你的问题的例子,你可以得到提交,其中branch-A从发散master

git log master..branch-A

回答by twalberg

I think this is probably a good opportunity for education. gitdoesn't really record the starting point of any branch. Unless the reflog for that branch still contains the creation record, there's no way to definitively determine where it started, and if the branch has merges in it anywhere, it may in fact have more than one root commit, as well as many different possible points where it might have been created and started to diverge from its original source.

我认为这可能是一个很好的教育机会。git并没有真正记录任何分支的起点。除非该分支的 reflog 仍然包含创建记录,否则无法明确确定它从哪里开始,并且如果该分支在任何地方合并,它实际上可能有多个根提交,以及许多不同的可能点它可能被创建并开始偏离其原始来源。

It might be a good idea to ask a counter question in such cases - why do you need to know where it branched from, or does it matter in any useful way where it branched from? There might or might not be good reasons that this is important - many of the reasons are probably tied up in the specific workflow your team has adopted and is trying to enforce, and may indicate areas where your workflow might be improved in some way. Perhaps one improvement would be figuring out what the "right" questions to ask - for example, rather than "where did branch-Bbranch from", maybe "what branches do or don't contain the fixes/new features introduced by branch-B"...

在这种情况下,问一个反问题可能是个好主意 - 为什么你需要知道它从哪里分支,或者它从哪里分支以任何有用的方式重要?这很重要可能有也可能没有充分的理由 - 许多原因可能与您的团队采用并试图执行的特定工作流程有关,并且可能表明您的工作流程可能会以某种方式改进。也许一项改进是弄清楚要问什么“正确”的问题 - 例如,而不是“branch-B从哪里分支”,也许“哪些分支包含或不包含由branch-B“......

I'm not sure that a completely satisfactory answer to this question really exists...

我不确定这个问题是否真的存在一个完全令人满意的答案......

回答by Matt McHenry

From a philosophical point of view, the question of a branch's history cannot be answered in a global sense. However, the reflogdoes track each branch's history in that particular repository.

从哲学的角度来看,一个分支的历史问题不能在全球范围内得到回答。但是,reflog确实会跟踪该特定存储库中每个分支的历史记录。

Thus, if you have a single central repository that everyone pushes to, you can use its reflogto track this information (some more details in this question). First, on that central repository, ensure the reflog is recorded and never gets cleaned out:

因此,如果您有一个每个人都推送到的中央存储库,您可以使用它reflog来跟踪此信息(此问题中的更多详细信息)。首先,在该中央存储库上,确保记录了 reflog 并且永远不会被清除:

$ git config core.logAllRefUpdates true
$ git config gc.reflogExpire never

Then you can run git reflog <branchname>to inspect the branch's history.

然后您可以运行git reflog <branchname>以检查分支的历史记录。

Example

例子

I reproduced your sample commit graph with a few pushes into a test repository. Now I can do this sort of thing:

我复制了您的示例提交图,并将其推送到测试存储库中。现在我可以做这样的事情:

$ git log --graph --all --oneline --decorate
* 64c393b (branch-b) commit I
* feebd2f commit H
| * 3b9dbb6 (branch-a) commit G
| * 18835df commit F
|/  
* d3840ca commit E
* b25fd0b commit D
| * 8648b54 (master) commit J
| * 676e263 commit C
|/  
* 17af0d2 commit B
* bdbfd6a commit A

$ git reflog --date=local master branch-a branch-b
64c393b branch-b@{Sun Oct 11 21:45:03 2015}: push
3b9dbb6 branch-a@{Sun Oct 11 21:45:17 2015}: push
18835df branch-a@{Sun Oct 11 21:43:32 2015}: push
8648b54 master@{Sun Oct 11 21:42:09 2015}: push
17af0d2 master@{Sun Oct 11 21:41:29 2015}: push
bdbfd6a master@{Sun Oct 11 21:40:58 2015}: push

So you can see that in my example, when branch-afirst came into existence, it was pointed at commit F, and that a second push to the central server moved it forward to commit G. Whereas when branch-bfirst came into existence, it was pointed at commit I, and it hasn't seen any updates yet.

所以你可以看到,在我的例子中,当branch-a第一次出现时,它被指向 commit F,并且第二次推送到中央服务器将它向前移动到 commit G。而当它branch-b第一次出现时,它指向的是 commit I,并且还没有看到任何更新。

Caveats

注意事项

This shows only the history as it was pushed to the central repo. If for example a coworker started branch-Aat commit A, but then rebased it onto commit Bbefore pushing it, that information would not be reflected in the central repository's reflog.

这仅显示推送到中央回购的历史记录。例如,如果同事从branch-Acommit开始A,但B在推送之前将其重新设置为 commit ,则该信息将不会反映在中央存储库的 reflog 中。

This also doesn't provide a definitive record of where a branch began. We can't really say for sure which branch "owns" commits Dand Ethat initially forked off of master. Did they get created on branch-aand then picked up by branch-b, or the other way around?

这也没有提供分支开始位置的明确记录。我们不能真正确定哪个分支“拥有”提交DE并且最初是从 master 分支出来的。它们是被创建branch-a然后被 接收branch-b,还是相反?

Both branches initially appeared on the central repository containing those commits, and the reflogdoes tell us which branch showed up on the central repository first. However, these commits may have been "passed around" amongst several end-user repositories, via format-patch, etc. So even though we know which branch pointer was responsible for carrying them to the central server first, we don't know their ultimate origin.

两个分支最初都出现在包含这些提交的中央存储库中,并且reflog确实告诉我们哪个分支首先出现在中央存储库中。然而,这些提交可能已经在几个最终用户存储库之间“传递”,通过format-patch等等。所以即使我们知道哪个分支指针负责首先将它们传送到中央服务器,我们也不知道它们的最终来源

回答by theB

Some architectural details

一些建筑细节

Git stores the revisions to a repository as a series of commits. These commits contain a link to information about the changes to the files since the last commit and, importantly, a link to the previous commit. In broad terms a branch's commit history is a singly linked list from the most recent revision all the way back to the repository's root. The state of the repository at any commit is that commit combined with all the commits prior to it all the way back to the root.

Git 将修订存储为一系列提交。这些提交包含有关自上次提交以来文件更改信息的链接,重要的是,还包含指向前一次提交的链接。从广义上讲,一个分支的提交历史是一个从最新修订一直回到存储库根目录的单链表。任何提交时存储库的状态是该提交与之前的所有提交相结合,一直回到根目录。

So what's the HEAD? And what's a branch?

那么什么是HEAD?什么是分支?

The HEAD is a special pointer to the newest commit in the currently active branch. Each branch, including the master1, is also a pointer to the latest revision in it's history.

HEAD 是指向当前活动分支中最新提交的特殊指针。每个分支,包括 master 1,也是指向其历史记录中最新修订的指针。

Clear as mud? Let's look at an example using an image from the Pro Git book, that will hopefully clarify things somewhat.2

清如泥?让我们看一个使用Pro Git book 中的图像的示例,这有望在一定程度上澄清事情。2

Simple Git Tree

简单的 Git 树

In this diagram we have a relatively simple repository with 4 commits. 98ca9is the root. There are two branches, master and testing. The master branch is at commit f30abwhile the testing branch is at 87ab2. We are currently working in the master branch, so the HEAD is pointing to the master branch. The history for the branches in our sample repository are (from newest to oldest):

在这个图中,我们有一个相对简单的存储库,有 4 个提交。 98ca9是根。有两个分支,master 和 testing。master 分支处于 commit 状态,f30ab而 testing 分支处于87ab2. 我们目前在 master 分支工作,所以 HEAD 指向 master 分支。我们示例存储库中分支的历史记录是(从最新到最旧):

testing:  87ab2 -> f30ab -> 34ac2 -> 98ca9
 master:           f30ab -> 34ac2 -> 98ca9

From this we can see that the two branches are the same starting at f30ab, so we can also say that testing was branches at that commit.

从中我们可以看到两个分支从 开始是相同的f30ab,所以我们也可以说测试是那个提交的分支。

The Pro Git book goes into a lot more detail, and it's definitely worth a read.

Pro Git 书更详细,绝对值得一读。

Now we can address--

现在我们可以解决——

The Specific Question

具体问题

Fancifying the diagram we get:

幻想我们得到的图表:

Fancy like a pinky up drinking tea.

喜欢喝茶的小指。

Is commit D a member of both branches or can we clearly decide whether it belongs to branch-A or branch-B?

提交 D 是两个分支的成员还是我们可以明确决定它属于分支 A 还是分支 B?

Knowing what we now know, we can see that commit D is a member of both chains leading from the branch pointers to the root. Therefore we can say that D is a member of bothbranches.

知道我们现在所知道的,我们可以看到提交 D 是从分支指针到根的两条链的成员。因此我们可以说 D 是两个分支的成员。

Which branch started at E, which one at B?

哪个分支从 E 开始,哪个分支从 B 开始?

Both branch-A and branch-B originate from the master branch at B, and diverge from each other at E. Git itself doesn't distinguish which branch owns E. At their core, the branches are just the chain of commits from newest to oldest ending up at the root.

分支 A 和分支 B 都起源于 B 的 master 分支,在 E 处彼此发散。 Git 本身不区分哪个分支拥有 E。在它们的核心,分支只是从最新到提交的链最老的结束于根。



1Fun Fact: The master branch is just an ordinary branch. It is no different from any other branch.

1趣闻:master 分支只是一个普通的分支。它与任何其他分支没有什么不同。

2The Pro Git book is licensed with the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

2Pro Git 书籍已获得知识共享署名-非商业性使用-相同方式共享 3.0 未移植许可。

回答by tobib

As @cupcake explained, there is no starting point of a branch. You can only check where one branch first touched another one. This is probably what you wnat in most cases. @code-guru already explained the syntax to refer to ranges of commits.

正如@cupcake 所解释的,分支没有起点。您只能检查一个分支第一次接触另一个分支的位置。在大多数情况下,这可能是您想要的。@code-guru 已经解释了引用提交范围的语法。

Putting it all together: This command shows the first commit before the first commit that was in branch-Abut not in master:

将它们放在一起:此命令显示在第一个提交之前的第一个提交,branch-A但不是在master

git show `git rev-list branch-A ^master --topo-order | tail -n 1`~1

git show `git rev-list branch-A ^master --topo-order | tail -n 1`~1