Git分支在哪里开始,它的长度是多less?

每隔一段时间,我会问,在git上启动某个分支是什么时候,或者某个分支上是否创build了某个提交。 分支的终点非常明确:分支标签所在的位置。 但是 – 它从哪里开始? 微不足道的答案是:在我们创build分支的那个提交上。 但是据我所知,这些信息就是为什么我问这个问题,在第一次提交之后就失败了。

只要我们知道我们分手的地方,我们可以画出图表来说明:

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

我已经在提交E创build分支B,所以这是“开始”。 我知道,因为我做到了。 但是其他人能否以同样的方式来认识它? 我们可以像这样绘制相同的图表:

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

那么,现在看图,哪个分支在E开始,哪个分支在B ? 提交D是两个分支的成员还是可以明确地决定它是属于分支A还是分支B?

这听起来有些哲学性,但实际上并非如此。 主pipe有时候想知道,什么时候分支已经开始了(通常标志着一个任务的开始),以及某些变更属于哪个分支(为了达到某种变化的目的 – 这是工作所需要的)想知道git是否提供了信息(工具,命令)或定义来正确回答这些问题。

在Git中,你可以说每一个分支都是从根提交开始的,而且这是完全正确的。 但我想这对你并不是很有帮助。 你可以做的是定义“分支的开始”与其他分支的关系。 你可以做的一个方法是使用

 git show-branch branch1 branch2 ... branchN 

并且会向您显示输出底部的所有指定分支之间的常见提交(如果实际上存在常见提交)。

下面是show-branch的Linux Kernel Git文档的一个例子

 $ 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个分支共享一个共同的祖先提交,而实际上它是masterhead提交:

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

这意味着这两个fixesmhf分开了master承诺。

替代scheme

当然,这只是一种可能的方式来确定Git中的一个通用的基础提交。 其他方式包括git merge-base来寻找共同的祖先,而git log --all --decorate --graph --oneline或者gitk --all – 所有可视化的分支,看看它们在哪里分歧(尽pipe如果有很多提交这很快就变得困难)。

其他来自原始海报的问题

至于这些问题你有:

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

D是两个分支的成员,这是他们两个的祖先承诺。

主pipe有时候想知道, 当一个分支已经开始(通常标志着一个任务的开始)

在Git中,你可以重写整个提交树和它们的分支的历史,所以一个分支“启动”不像TFS或SVN那样固定的时候。 你可以将分支重定位到Git树的任何时间点,甚至把它放在根提交之前! 因此,您可以使用它在您想要的树中的任何时间点“开始”任务。

这是git rebase一个常见用例,可以将上游分支的最新更改与分支进行同步,以便按照提交graphics及时推送它们,就好像您刚刚开始在分支上工作一样尽pipe你已经有一段时间了。 如果你愿意的话,你甚至可以按照提交图推回分支(尽pipe你可能需要解决很多冲突,具体取决于分支的内容,或者你可能不会)。 你甚至可以在开发历史中插入或删除一个分支(尽pipe这样做可能会改变很多提交的提交)。 重写历史logging是Git的主要function之一,它使得它非常强大和灵活。

这就是为什么提交同时带有创builddate(最初创build提交时)和提交date(提交最后提交到提交树时)的原因。 你可以把它们想象成创build时间date和最后修改时间date。

主pipe有时候想知道…… 某些变化属于哪个分支 (为了达到某种变化的目的 – 是工作所需要的)。

同样,因为Git允许你重写历史,所以你可以(重新)在提交图中的任何分支/提交上进行一系列更改。 git rebase字面上允许你自由地移动你的整个分支(尽pipe你可能需要根据你移动分支的位置以及它包含的内容来解决冲突)。

这就是说,你可以在Git中使用的工具之一来确定哪些分支或标签包含一系列变化是--contains

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

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

我有兴趣知道是否将Git分支作为除root提交以外的已定义的“开始”提交,甚至是有意义的?

它有一种除了:

  • 根提交是“ 可以从分支HEAD 访问的第一个提交”(并且不要忘记可以有多个行分支提交的根提交,例如在GitHub中用于gh-pages
  • 我更喜欢把一个分支的开始考虑为另一个分支的创build( tobib的回答没有~1 ),或者(更简单) 共同的祖先
    (即使OP提到对共同的祖先不感兴趣也在“ 用Gitfind一个分支点 ”):

     git merge-base A master 

这意味着:

  • 第一个定义给你一个固定的提交 (这可能永远不会改变,除非是大量的filter-branch
  • 第二个定义给你一个相对的提交 (相对于另一个分支),它可以随时改变(另一个分支可以被删除)

第二个对git更有意义,这就是分支之间的合并和重组。

主pipe有时候想知道,一个分支何时开始(通常标志着一个任务的开始)以及某个分支属于哪个分支(为了达到某种改变的目的 – 这是工作所需要的)

分支仅仅是错误的标记:由于分支的瞬时性(可以被重命名/移动/重定位/删除/ …),你不能模仿一个分支的“变更集”或“活动”代表一个“任务”。

这是一个XY问题 :OP要求尝试解决scheme (分支开始的地方)而不是实际的问题 (在Git中可能被认为是一个任务)。

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

  • 标签 :它们是不可变的(一旦关联到一个提交,那个提交不再被移动/被重新绑定),并且两个有名称的标签之间的任何提交都可以表示一个活动。
  • 一些git notes记住“工作项目”表示提交已经创build(与标签相反,如果提交被修改或重新发布,便可以重写注释)。
  • 钩子(根据提交消息将一个提交与某个“外部”对象(如“工作项目”)相关联)。 这就是Git-RTC(IBM Rational Team Concert)与一个预先接收钩子的桥梁 )关键是:分支的开始并不总是反映任务的开始,而仅仅是历史的延续哪些可以改变,哪些序列应该代表一组逻辑变化。

也许你问的是错误的问题。 国际海事组织,它是没有意义的,要问一个分支开始,因为一个给定的分支包括对每个文件(即从最初的提交)以来所做的所有更改。

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

有一些研究发现了gitrevisions手册页 ,它描述了提交具体提交和提交范围的细节。 尤其是,

要排除提交可达的提交,使用前缀^符号。 例如^ r1 r2表示提交从r2可达,但不包括从r1可达的。

这套操作经常出现,以至于有一个简写。 当你有两个提交r1和r2(按照上面的规定说明的语法来命名)时,你可以要求可以从r2到达的提交,不包括那些可以从r1到达^ r1 r2的提交,它可以写成r1。 .r2。

所以,使用你的问题的例子,你可以得到提交branch-Amaster发散

 git log master..branch-A 

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

在这种情况下提出一个反问题可能是一个好主意 – 为什么你需要知道它从哪个分支中分出来,或者从分支出来的任何有用的方式是否重要? 可能有也可能没有很好的理由,这很重要 – 许多原因可能与您的团队采用并试图执行的特定工作stream程捆绑在一起,并可能表明可能以某种方式改进工作stream程的领域。 也许有一个改进就是要弄清楚什么是“正确”的问题,比如说,而不是“分支从哪里分支”,也许是“什么分支不包含branch-B引入的修复/新function” branch-B “…

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

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

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

主pipe们有时想知道,一个分支何时开始(通常标志着一个任务的开始),以及哪个分支属于哪个分支(为了达到某种改变的目的 – 是否需要这项工作)

两个事实观察之前,我们去肉:

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

第二种观察: branch-Abranch-B是否先发布(通过合并或重定位或推送),则提交D和E中的工作必须正确执行,而不会被后续操作复制。 这些犯下的东西现在什么都没有什么区别。 分支名称在这里也不重要。 重要的是通过血统图来承诺彼此的关系。


所以我的回答是,就历史而言,分支名称根本就不重要。 他们是方便的标签,显示哪些提交目前是为了特定于该回购的目的,仅此而已。 如果你想在默认的merge-commit消息主题行中使用git branch some-useful-namegit branch some-useful-name在合并前git branch some-useful-name ,然后合并它。 他们是相同的提交任一方式。

把任何一个分支名称与开发者在提交外部logging(或者任何东西)时检查出来的分支名称联系起来,只要“一切正常”的领域都深入到“一切正常”。 不要这样做。 即使在大多数VCS中常见的限制使用,您的DE-{FG,HI}将会更早发生,然后您的分支命名约定将不得不适应处理,然后会出现更复杂的情况。 。 。

何必? 将提交工作的报告编号放在提交信息底部的标语中,然后完成。 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, eg 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 

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

请注意,我在c&p之后做了一些手工修改,以使实际脚本变得非常特殊。 键盘到编辑框的警告

从哲学的angular度来看,分支机构的历史问题不能从全球的angular度来回答。 然而, reflog会跟踪每个分支在特定存储库中的历史logging。

因此,如果你有一个单独的中央仓库,每个人都可以使用它,你可以使用它的reflog来跟踪这个信息( 这个问题的更多细节)。 首先,在该中央存储库上,确保loggingreflog并永远不会被清除:

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

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

我把你的示例提交图复制到一个testing库中。 现在我可以做这样的事情:

 $ 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 ,并且第二次推送到中央服务器将其向前移动以提交Gbranch-b刚刚成立的时候,就被指出了承诺I ,而且还没有看到任何更新。

注意事项

这只显示了推向中央回购的历史。 例如,如果一个同事在提交A启动了branch-A A ,然后在推送之前将其重新提交到了提交B ,则该信息将不会反映在中央存储库的引用日志中。

这也没有提供分支开始的确切logging。 我们不能确定哪个分支“拥有”提交DE ,最初与主分离。 它们是在branch-a -a上创build,然后由branch-b拾取,或者反过来呢?

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

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

把它放在一起:这个命令显示在branch-A但不在master branch-A中的第一次提交之前的第一次提交:

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

一些build筑细节

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

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

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

清除泥浆? 让我们来看看使用Pro Git书籍中的图像的例子,希望能够澄清一些事情。 2

简单的Git树

在这个图表中,我们有一个相对简单的存储库,有4个提交。 98ca9是根。 有两个分支,主人和testing。 主分支在提交f30ab而testing分支在87ab2 。 我们目前在master分支上工作,所以HEAD指向master分支。 我们样本库中分支的历史logging(从最新到最旧):

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

从这里我们可以看出,这两个分支是从f30ab开始的,所以我们也可以说testing是在这个提交分支。

Pro Git的书进入了更多的细节,这绝对值得一读。

现在我们可以解决 –

具体问题

将我们得到的图简化:

看起来像小茶一样地喝着茶。

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

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

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

分支-A和分支-B都来自B的主分支,并且在E处彼此分离。Git本身不区分哪个分支拥有E.在它们的核心,分支仅仅是从最新到最古老的结局在根。


1有趣的事实:主分支只是一个普通的分支。 这与其他分支没有区别。

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