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

And*_*s_D 64 git version-control git-branch

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

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

A - B - C - - - - J     [master]
     \
      D - E - F - G     [branch-A]
           \
            H - - I     [branch-B]
Run Code Online (Sandbox Code Playgroud)

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

A - B - C - - - - J     [master]
     \
      \       F - G     [branch-A]
       \     /
        D - E
             \
              H - I     [branch-B]
Run Code Online (Sandbox Code Playgroud)

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

这听起来有点哲学,但实际上并非如此.监事有时想知道,当一个分支已经开始(它通常标志着一个任务开始)和其分支的一些变化属于(得到一些改变的目的 - 是它需要的工作),我会想知道git是否提供信息(工具,命令)或定义来正确回答这些问题.

小智 60

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

git show-branch branch1 branch2 ... branchN
Run Code Online (Sandbox Code Playgroud)

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

这是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'.
Run Code Online (Sandbox Code Playgroud)

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

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

*++ [master] Add 'git show-branch'.
Run Code Online (Sandbox Code Playgroud)

这意味着,fixesmhf被分支在该承诺master.

替代方案

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

来自原始海报的其他问题

至于这些问题,你有:

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

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

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

在Git中,您可以重写整个提交树及其分支的历史记录,因此分支"启动"时不像TFS或SVN那样粗略.您可以rebase在Git树中分支到任何时间点,甚至将它放在根提交之前!因此,您可以使用它在您想要的树中的任何时间点"启动"任务.

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

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

主管有时想知道...... 一些变化属于哪个分支(为了达到某些变化的目的 - 是否需要工作).

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

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

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

# Which tags contains commit X?
git tag --contains X
Run Code Online (Sandbox Code Playgroud)

  • 因此看起来git"only"提供*分支标签*但不关心*branches*,因为我们经常看到它们:具有第一个和最后一个的提交列表.这一点都不错:它给了我们很大的灵活性.但是如果我们想跟踪*分支*(开始,内容,结束......),那么我们显然要么严格地组织存储库,要么在回购之外跟踪它们. (4认同)

Von*_*onC 14

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

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

它除了:

这意味着:

  • 第一个定义为您提供了一个固定的提交(除非是大规模的,否则可能永远不会改变filter-branch)
  • 第二个定义为您提供相对提交(相对于另一个分支),可以随时更改(另一个分支可以删除)

第二个对git更有意义,git是关于分支之间的合并和rebase.

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

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

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

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

  • 标签:它们是不可变的(一旦与提交相关联,该提交不再被认为是移动/被重新定位),并且两个命名良好的标签之间的任何提交都可以表示活动.
  • 一些git notes提交记忆到哪个"工作项"表示已经创建了提交(与标记相反,如果修改或重新提交提交,则可以重写注释).
  • hooks(根据提交消息将提交关联到某个"外部"对象,如"工作项").这就是桥接Git-RTC - IBM Rational Team Concert - 使用预接收钩子做的事情.重点是:分支的开始并不总是反映任务的开始,而仅仅是历史的延续哪个可以改变,哪个序列应该代表一组逻辑变化.


Cod*_*ice 10

也许你问的是错误的问题.海事组织,它没有任何意义了问一个分支开始,因为这个分支包括对每个文件所做的所有更改(即从最初的承诺).

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

一些研究发现了gitrevisions手册页,它描述了引用特定提交和提交范围的细节.特别是,

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

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

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

git log master..branch-A
Run Code Online (Sandbox Code Playgroud)

  • @Andreas_D您需要对分支进行编号,或对提交进行编号.使用两者的字母变得非常混乱. (2认同)

twa*_*erg 9

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

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

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


jth*_*ill 9

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

A - B - C - - - - J     [master]
     \
      \       F - G     [branch-A]
       \     /
        D - E
             \
              H - I     [branch-B]
Run Code Online (Sandbox Code Playgroud)

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

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

第一个观察:你的主管想知道的是提交和一些外部工作顺序记录之间的映射:什么提交地址bug-43289或featureB?我们为什么要改变strcat用法longmsg.c?谁会支付你之前的推动和这一次之间的二十小时?分支名称本身并不重要,重要的是提交与外部管理记录的关系.

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


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

将开发人员在提交时签署的任何分支名称与某些外部记录(或任何内容)结合起来,只要"只要一切正常",就会很好.不要这样做.即使在大多数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"
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

内核项目使用这样的标语来进行版权签收和代码审查记录.它实际上不会变得更简单或更强大.

请注意,我在c&p之后做了一些手工操作来去除真正的脚本.键盘到编辑框警告

  • "将提交作品的报告编号放在提交消息底部的标语中,然后完成." 我同意,这通常是任何类型的git-task跟踪系统的启动方式.+1 (2认同)

Mat*_*nry 6

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

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

$ git config core.logAllRefUpdates true
$ git config gc.reflogExpire never
Run Code Online (Sandbox Code Playgroud)

然后你可以跑来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
Run Code Online (Sandbox Code Playgroud)

所以,你可以看到,在我的例子中,当branch-a第一应运而生,有人指出在提交F,而第二推到中央服务器移至其转发给提交G.然而,当它branch-b首次出现时,它指向了提交I,并且还没有看到任何更新.

注意事项

这只显示了它被推到中央仓库的历史.例如,如果一个同事开始branch-A在提交A,但随后重订其提交上B推之前,这些信息不会在中央资料库的引用日志反映.

这也没有提供分支开始位置的确切记录.我们无法确切地说出哪个分支"拥有"提交D并且E最初是从主人那里分出来的.他们是创造了branch-a,然后通过branch-b,或反过来?

两个分支最初都出现在包含这些提交的中央存储库中,并且reflog确实告诉我们哪个分支首先出现在中央存储库中.然而,这些提交可能已被"绕过"数之间的最终用户信息库,通过format-patch等,所以即使我们知道哪个分支指针负责运送他们到中央服务器第一,我们不知道他们的最终起源.


tob*_*bib 5

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

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

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


the*_*heB 5

一些建筑细节

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

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

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

像泥一样清楚?让我们看一个使用Pro Git书中的图像的例子,希望能在某种程度上澄清一些事情.2

简单的Git树

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

testing:  87ab2 -> f30ab -> 34ac2 -> 98ca9
 master:           f30ab -> 34ac2 -> 98ca9
Run Code Online (Sandbox Code Playgroud)

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

Pro Git书详细介绍了它,它绝对值得一读.

现在我们可以解决 -

具体问题

将图表归化为:

想要喝一杯小茶.

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

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

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

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


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

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