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

时间:2013-07-10 21:15:55

标签: git version-control git-branch

我经常被问到,git上某个分支的启动是什么,或者是否在特定分支上创建了某个提交。分支的终点非常清楚:分支标签所在的位置。但是 - 它从哪里开始?琐碎的答案是:在我们创建该分支的那个提交上。但据我所知,这些信息就是为什么我会问这个问题,在第一次提交后丢失了。

只要我们知道我们分支的提交,我们就可以绘制图表以明确:

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

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

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

那么,现在查看图表,哪个分支从E开始,哪个分支在B?提交D是两个分支的成员还是我们可以清楚地决定它是属于分支A还是分支B?

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

8 个答案:

答案 0 :(得分:58)

在Git中,你可以说每个分支都是从根提交开始的,而这确实是真的。但我想这对你没有多大帮助。你可以做的是定义与其他分支相关的“分支的开始”。一种方法是使用

git show-branch branch1 branch2 ... branchN

这将显示输出底部所有指定分支之间的公共提交(如果实际上存在公共提交)。

以下是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'.

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

在输出的最底部,您将看到所有3个分支共享一个共同的祖先提交,并且实际上它是head的{​​{1}}提交:

master

这意味着*++ [master] Add 'git show-branch'. fixes分别在mhf中的提交中分支。

替代解决方案

当然,这只是在Git中确定公共基本提交的唯一方法。其他方式包括master找到共同的祖先,git merge-basegit log --all --decorate --graph --oneline可视化分支并查看它们的分歧(尽管有很多提交很快变得困难)。

来自原始海报的其他问题

至于你有这些问题:

  

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

branch-B是两个分支的成员,它是两个分支的祖先提交。

  

主管有时想知道, 当分支机构启动时(它通常标志着任务的开始) ......

在Git中,您可以重写整个提交树及其分支的历史记录,因此 时,分支“启动”不是一成不变的就像TFS或SVN一样。您可以在Git树中D分支到任何时间点,甚至可以将其放在根提交之前!因此,您可以使用它在您想要的树中的任何时间点“启动”任务。

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

这就是为什么提交带有创作日期(最初创作提交时)和提交日期(提交最后一次提交到提交树时)的原因。您可以将它们视为类似于创建时间日期和最后修改时间日期。

  

主管有时想知道...... 哪些分支属于 (为了达到某些改变的目的 - 是否需要工作)。< / p>

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

话虽这么说,你可以在Git中使用的一个工具来确定哪些分支或标签包含一组更改是git rebase

--contains

答案 1 :(得分:14)

关于这个问题的赏金通知,

  

我有兴趣知道是否认为Git分支除了根提交之外还有一个定义的“开始”提交甚至是有意义的吗?

除了:

之外

这意味着:

  • 第一个定义为您提供固定提交(除非发生大量git merge-base A master ,否则可能永远不会更改)
  • 第二个定义为您提供相对提交(相对于另一个分支),它可以随时更改(另一个分支可以删除)

第二个对git更有意义,这是关于分支之间的合并和转换。

  

主管有时想知道,当分支机构启动时(它通常标志着任务的开始)以及某些变更属于哪个分支(为了达到某些变化的目的 - 是否需要工作)< / p>

分支只是错误的标记:由于分支的瞬态特性(可以重命名/移动/重新定位/删除/ ...),您无法模仿“变更集”或“活动”一个分支,代表一个“任务”。

这是X Y problem:OP要求尝试解决方案(分支在哪里开始)而不是实际问题(可能是什么)被认为是Git中的一项任务。)

要做到这一点(代表任务),你可以使用:

  • 标记:它们是不可变的(一旦与提交相关联,该提交不再被认为是移动/被重新设置),并且两个命名良好的标记之间的任何提交都可以表示活动。
  • 提交一些git notes来记住创建提交的“工作项”(与标记相反,如果修改或重新提交,则可以重写注释)。
  • hooks(基于提交消息将提交关联到某个“外部”对象,如“工作项”)。这就是bridge Git-RTC -- IBM Rational Team Concert -- does with a pre-receive hook) 重点是:分支的开始并不总是反映任务的开始,而只是可以改变的历史的延续,以及哪个序列应该代表一组逻辑变化。

答案 2 :(得分:10)

也许你问的是错误的问题。 IMO,询问分支何处开始是没有意义的,因为给定的分支包括对每个永远文件所做的所有更改(即自初始提交以来)。

另一方面,询问两个分支的分歧绝对是一个有效的问题。事实上,这似乎正是你想知道的。换句话说,您并不真的想知道有关单个分支的信息。相反,您想知道有关比较两个分支的一些信息。

稍微进行了一些研究the gitrevisions man page,其中描述了引用特定提交和提交范围的详细信息。特别是,

  

要排除提交中可到达的提交,请使用前缀^表示法。例如。 ^ r1 r2表示可以从r2到达的提交,但不包括从r1可到达的提交。

     

此设置操作经常出现,因此有一个简写。如果你有两个提交r1和r2(根据上面的SPECIFYING REVISIONS中解释的语法命名),你可以要求从r2可以访问的提交,不包括那些可以通过^ r1 r2从r1到达的提交,它可以写成r1。 .r2。

因此,使用您问题中的示例,您可以获得branch-Amaster不同的提交

git log master..branch-A

答案 3 :(得分:9)

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

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

我不确定这个问题的答案是否真的存在......

答案 4 :(得分:9)

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

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

[...]主管有时想知道,当分支机构启动时(它通常标志着任务的开始)以及某些变更所属的分支机构(为了达到某些变化的目的)是否需要工作)

在我们得到肉之前的两个事实观察:

首先观察:你的主管想知道的是提交和一些外部工作顺序记录之间的映射:什么提交地址bug-43289或featureB?我们为什么要更改strcatlongmsg.c的使用情况?谁会支付您之前推送和此推送之间的二十小时?分支机构名称本身并不重要,重要的是承诺&#39;与外部行政记录的关系。

第二个观察:首先发布branch-Abranch-B(通过合并或rebase或推送),提交D和E中的工作必须随时使用,而不是通过任何后续操作。这些提交的当前内容完全没有区别。分支名称在这里也不重要。重要的是承诺&#39;通过祖先图表相互关系。

所以我的答案是,就历史而言,分支机构名称根本不重要。它们是便利标签,显示哪些提交是针对特定于该回购的某些目的的当前提交,仅此而已。如果你想在默认的合并提交消息主题行中使用一些有用的名字对象,git branch some-useful-name在合并之前提示,并合并它。无论如何,他们都是相同的提交。

将开发人员在提交时签署的任何分支名称与某些外部记录(或任何其他内容)结合起来,只要一切正常,就会很好。&#34;领土。不要这样做。即使在大多数VCS中常见的限制使用,你的D-E-{F-G,H-I}将很快发生,然后你的分支命名约定必须适应处理它,然后会出现更复杂的东西,。 。

为什么要这么麻烦?将提交工作的报告编号放在提交消息底部的标语行中,然后完成。 git log --grep(和一般的git)有充分的理由非常快。

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

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" "$1"

这里是您需要检查每次提交时的基本预接收挂钩循环:

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

内核项目使用这样的标语来进行版权签收和代码审查录制。它真的不会变得更简单或更健壮。

请注意,我在c&amp; p之后进行了一些手工操作,以对真正的脚本进行去专业化。键盘到编辑框警告

答案 5 :(得分:6)

从哲学的角度来看,分支历史的问题无法在全球意义上得到回答。但是,reflog会跟踪该特定存储库中中每个分支的历史记录

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

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

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

实施例

我通过几次推送到测试存储库中再现了您的示例提交图。现在我可以做这样的事情:

$ 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

所以你可以看到,在我的例子中,当branch-a首次出现时,它被指向提交F,并且第二次推送到中央服务器将其向前推送到提交{{ 1}}。当G首次出现时,它被指向提交branch-b,而它还没有看到任何更新。

注意事项

这仅显示历史,因为它被推送到中央仓库。例如,如果同事在提交I处启动branch-A,但在推送之前将其重新设置为提交A,则该信息将不会反映在中央存储库的reflog中。

这也没有提供分支开始的确切记录。我们无法确切地说出哪个分支“拥有”提交的BD最初是从主分支出来的。它们是在E上创建的,然后由branch-a获取,或者相反?

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

答案 6 :(得分:5)

正如@cupcake所解释的那样,没有一个分支的起点。您只能检查一个分支首先触及另一个分支的位置。在大多数情况下,这可能就是你的意思。 @ code-guru已经解释了引用提交范围的语法。

全部放在一起:此命令显示branch-A中第一次提交之前的第一次提交,但不显示master中的第一次提交:

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

答案 7 :(得分:5)

一些建筑细节

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

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

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

清除泥土?让我们看一个使用Pro Git book中的图像的示例,希望能在某种程度上澄清一些事情。 2

Simple Git Tree

在此图中,我们有一个相对简单的存储库,包含4个提交。 98ca9是根。有两个分支,主要和测试。主分支处于提交f30ab,而测试分支处于87ab2。我们目前正在主分支中工作,因此HEAD指向主分支。我们的示例存储库中分支的历史记录是(从最新到最旧):

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

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

Pro Git书详细介绍了这一点,绝对值得一读。

现在我们可以解决 -

具体问题

将我们得到的图表归化为:

Fancy like a pinky up drinking tea.

  

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

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

  

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

分支-A和分支-B都来自B处的主分支,并且在E处彼此分开.Git本身不区分哪个分支拥有E.在它们的核心,分支只是提交链从最新到最老,最后到根。

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

2 Pro Git图书使用知识共享署名 - 非商业性使用 - ShareAlike 3.0 Unported许可证授权。