Where does a Git branch start and what is its length?
不时有人问我,Git上的某个分支启动了什么,或者某个特定分支上是否创建了某个提交。分支的终点非常清楚:这就是分支标签所在的位置。但是-从哪里开始的?简单的答案是:在我们创建分支的提交上。但据我现在所知,这就是为什么我问这个问题,在第一次提交之后就失去了。
只要我们知道分支的提交位置,我们就可以绘制图表来清楚地说明:
1 2 3 4 5 | A - B - C - - - - J [master] \ D - E - F - G [branch-A] \ H - - I [branch-B] |
我在commit
1 2 3 4 5 6 7 | A - B - C - - - - J [master] \ \ F - G [branch-A] \ / D - E \ H - I [branch-B] |
号
现在看图表,哪个分支从
这听起来有些哲学,但实际上并非如此。管理者有时喜欢知道,当一个分支已经开始(它通常标志着一个任务的开始),并且一些变更属于哪个分支(为了得到一些变更的目的——这是工作所需要的),我想知道Git是否提供信息(工具、命令)或定义给正确回答这些问题。
在Git中,可以说每个分支都从根提交开始,这是完全正确的。但我想这对你没什么帮助。相反,您可以做的是定义与其他分支相关的"分支的开始"。一种方法是
1 | git show-branch branch1 branch2 ... branchN |
这将显示输出底部所有指定分支之间的公共提交(如果实际上有公共提交)。
下面是来自
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ 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'. |
号
在这个例子中,将
在输出的最底层,您将看到所有3个分支都有一个共同的祖先提交,实际上它是
1 | *++ [master] Add 'git show-branch'. |
这意味着
替代解决方案
当然,这只是确定Git中公共基提交的1种可能的方法。其他方法包括:用
原海报其他问题
关于这些问题,你有:
Is commit
D a member of both branches or can we clearly decide whether it belongs tobranch-A orbranch-B ?
号
Supervisors sometimes like to know, when a branch has been started (it usually marks the start of a task)...
号
在Git中,您可以重写整个提交树及其分支的历史记录,因此当分支"开始"时,并不像在诸如tfs或svn之类的东西中那样设置在石头中。您可以在Git树的任何时间点上执行cx1〔15〕分支,甚至可以在根提交之前执行!因此,您可以使用它在树中的任意时间点"启动"任务。
对于
这就是为什么提交同时具有编写日期(最初编写提交时)和提交日期(最后提交到提交树时)。您可以将它们视为类似于创建时间日期和上次修改的时间日期。
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).
号
同样,因为Git允许您重写历史,所以您可以(重新)在提交图中几乎任何分支/提交上建立一组更改。
也就是说,您可以在Git中使用的一个工具是
1 2 3 4 5 | # Which branches contains commit X? git branch --all --contains X # Which tags contains commit X? git tag --contains X |
。
关于这个问题的赏金通知要求,
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?
号
除了:
- 根提交是"可以从分支头访问的第一个提交"(不要忘记,可以有多个带有孤立分支的根提交,例如在用于
gh-pages 的github中使用)。 我更愿意考虑一个分支的开始是另一个分支的提交(Tobib的回答没有
~1 ),或者(更简单的)共同祖先。(也在"使用git查找分支点"中),即使OP提到对共同祖先不感兴趣):1git merge-base A master
这意味着:
- 第一个定义给了您一个固定的承诺(除非在大量
filter-branch )的情况下,它可能永远不会改变) - 第二个定义为您提供了一个相对提交(相对于另一个分支),它可以随时更改(可以删除另一个分支)。
第二个对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)
号
分支只是一个错误的标记:由于分支的短暂性(可以重命名/移动/重新平衡/删除/…),您不能用分支模仿"更改集"或"活动"来表示"任务"。
这是一个X Y问题:操作要求一个尝试的解决方案(分支从哪里开始),而不是实际的问题(在Git中什么可以被视为任务)。
要执行此操作(表示任务),可以使用:
- 标记:它们是不可变的(一旦与一个提交相关联,该提交就不再被认为是移动/重新调整基),并且两个命名良好的标记之间的任何提交都可以表示一个活动。
- 一些
git notes 对已创建"工作项"的commit进行记忆(与标记相反,如果commit被修改或重新设置,则可以重写注释)。 - 挂钩(根据提交消息将提交与一些"外部"对象(如"工作项")关联)。这就是Bridge git rtc——IBMRationalTeamConcert——使用预接收钩子所做的工作)重点是:分支的开始并不总是反映任务的开始,而仅仅是可以更改的历史的延续,以及哪些序列应该表示一组逻辑更改。
也许你问错了问题。在我看来,询问一个分支从哪里开始是没有意义的,因为一个给定的分支包含了对每个文件所做的所有更改(即自初始提交以来)。
另一方面,问两个分支在哪里分叉肯定是一个有效的问题。事实上,这似乎正是你想要知道的。换句话说,你不想知道关于一个分支的信息。相反,您希望了解有关比较两个分支的一些信息。
有一点研究显示了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.
号
因此,使用您问题中的示例,您可以得到
1 | git log master..branch-A |
这里有两个不同的关注点。从你的例子开始,
1
2
3
4
5
6
7 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)
号
在我们到达肉食之前,有两个事实观察:
第一个观察:您的主管想要知道的是提交和一些外部工作订单记录之间的映射:什么提交解决bug-43289或featureb?为什么我们要更改
第二个观察:无论是
所以我的答案是,就任何历史而言,分支名称都无关紧要。它们是方便标签,显示哪个提交对于特定于该回购的某个目的是最新的,仅此而已。如果您希望在默认的合并提交消息主题行中使用一些有用的名字对象,那么在合并之前,
将开发人员在提交时签出的任何分支名称与一些外部记录(或任何其他记录)绑定在一起,都将深入到"只要一切正常"领域。别这样。即使在大多数VCS中常见的限制使用情况下,您的
为什么要麻烦?将提示工作的报告编号放在提交消息底部的一个标记行中,然后完成它。
即使是一个相当灵活的预处理钩子来插入这样的标语也很简单:
1 2 3 4 | 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" |
下面是当您需要检查每个提交时的基本预接收钩子循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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&P之后做了一些手工操作,以取消对真实脚本的专门化。键盘到编辑框警告
我认为这可能是一个很好的教育机会。
在这种情况下,问一个相反的问题也许是个好主意——为什么你需要知道它从哪里分支,或者它从哪里分支以任何有用的方式重要?这一点可能很重要,也可能不是很好的原因——许多原因可能与您的团队采用并试图实施的特定工作流有关,并且可能表明您的工作流在某些方面可能得到改进。也许一个改进是找出"正确"的问题——例如,不是"
我不确定这个问题是否真的有一个完全令人满意的答案…
从哲学的角度来看,一个分支机构的历史问题不能从全球的角度来回答。然而,
因此,如果您有一个每个人都需要的中央存储库,您可以使用它的
1 2 | $ git config core.logAllRefUpdates true $ git config gc.reflogExpire never |
然后您可以运行
我将您的示例提交图复制到一个测试存储库中,只需几次推送即可。现在我可以这样做了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $ 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 |
号
所以你可以看到在我的例子中,当
这只显示了它被推到中央回购的历史。例如,如果一个同事在commit
这也不能提供分支机构起源的确切记录。我们不能确定哪个分支机构"拥有"了最初从master分出的
两个分支最初都出现在包含这些提交的中央存储库中,
一些建筑细节
Git将对存储库的修订存储为一系列提交。这些提交包含一个链接,指向自上次提交以来对文件所做的更改的信息,重要的是,还包含一个指向上一次提交的链接。从广义上讲,分支的提交历史是一个单独链接的列表,从最近的修订版一直返回到存储库的根目录。在任何提交时,存储库的状态都是,提交与之前的所有提交结合在一起,一直返回到根目录。
那么头部是什么?什么是分支?
头部是当前活动分支中最新提交的特殊指针。每个分支,包括master1,也是指向其历史上最新修订的指针。
像泥一样清澈?让我们来看一个使用pro-git图书中的图像的例子,希望能稍微澄清一些问题。2
。
在这个图中,我们有一个相对简单的具有4个提交的存储库。
1 2 | testing: 87ab2 -> f30ab -> 34ac2 -> 98ca9 master: f30ab -> 34ac2 -> 98ca9 |
由此我们可以看出,从
这本pro-git的书更详细,绝对值得一读。
现在我们可以解决--
具体问题想象一下我们得到的图表:
氧化镁
Is commit D a member of both branches or can we clearly decide whether it belongs to branch-A or branch-B?
号
知道我们现在知道什么,我们可以看到commit d是从分支指针到根的两个链的成员。因此我们可以说D是两个分支的成员。
Which branch started at E, which one at B?
号
分支A和分支B都源于B的主分支,并且在E.Git上彼此分离。Git本身并不区分哪个分支拥有E。在它们的核心,分支只是从最新到最旧的提交链,以根结尾。
1事实:总分行只是一个普通分行。它与其他任何分支都没有区别。
2该pro-git图书获得了创作共享属性非商业共享类似3.0的许可证(未发布)。
正如@cupcake所解释的,分支没有起点。您只能检查一个分支首次接触另一个分支的位置。在大多数情况下,这可能是你想要的。@代码专家已经解释了引用提交范围的语法。
综上所述:此命令显示了在
江户十一〔七〕号