当两个人在同一个分支上工作并且多次提交而没有推送时,在最后推送他们的提交之前,他们必须git pull --rebase
或合并(git fetch
/ git merge
)首先是为了获取存储库中的更改。
当他们将更改合并在一起时,在查看源树时,例如gitk,你看到提交的东西看起来像一个不同的"分支"。 git如何区分这些"自动生成的分支"你通过检查它们创建的分支和分支?您可以在创建合并分支后将其命名吗?
修改
我的意思是"自动生成的分支":
我的意思是"命名分支":
在我的理解中,视觉上的分支是历史图分成两部分。第一个图像中代码库的分支是由我在初始问题中讨论的场景创建的,第二个是由git checkout -b v2.0
创建的
答案 0 :(得分:2)
您看到的分支结构通常是在不使用大量单独名称的情况下实现的。为了避免它,你会改变而不是合并。 (这不是名称的问题,实际上,虽然名称之间存在搭配--Git&#39> 分支名称及其< em>远程跟踪名称,以及标记名称,这些都是Git称之为引用的特定形式 - 以及您想要的内容。 )
要记住的第一个也是最重要的事情是Git真的是关于提交。提交是Git的raison d'être。提交通过同时存储数据(源树的完整快照)和元数据来同时执行多项操作。让我们看一下实际的提交:
$ git cat-file -p HEAD | sed 's/@/ /'
tree 982dc557269a91826c64dd7e3c7d63c4ccfefc90
parent 8c8ddbd0821d552ff3c7e1b67c669dd7f11d63d7
author Junio C Hamano <gitster pobox.com> 1515188717 -0800
committer Junio C Hamano <gitster pobox.com> 1515188717 -0800
Git 2.16-rc1
Signed-off-by: Junio C Hamano <gitster pobox.com>
这里的tree
行给出了快照的哈希ID(Git按哈希ID存储所有内容,我们稍后会回过头来看)。 parent
行给出父提交的哈希ID。作者和提交者行告诉你谁写了提交(作者),谁将它放入存储库(提交者),以及时间戳(Unix格式秒 - 自 - 纪元加上时区偏移)。其余的元数据是提交消息。
每个Git 对象 - 有四种类型:提交,树, blob (基本上是内部形式) ()文件)和带注释的标签 - 具有唯一的哈希ID。这个哈希ID是&#34;真名称&#34;该对象,以及Git如何将其存储在存储库数据库中,并查找它。因此,要查找提交,Git需要其中一个哈希ID。
这些哈希ID的问题在于它们对人类毫无用处。所以Git添加了名称,如分支名称,标记名称和远程跟踪名称。所有这些都有一个主要工作:名称记住一个哈希ID。这样,我就可以记住36438dc19dd2a305dddebd44bf7a65f1a220075b
(一个分支名称)或master
(一个带注释的标签名称 - 我将跳过一些细节,而不是试图记住v2.16.0-rc1
。这里)。
有一些关于提交的重要事实,或者事实上任何Git对象:
这意味着您无法更改任何提交内容。但是您可以将对象复制到新的(和不同的)对象。我们还知道每个提交都列出了它的父提交,正如我们在上面列出的提交中看到的那样。我们很快就会利用这个想法。我们还需要一个项目:合并提交是一个提交两个父母(或更多,但我们不打算看这个案例)。
在数学术语中,图是连接这些顶点的一组顶点 V 和边 E 。 (有关示例,请参阅the Wikipedia page上的图表。)在定向图表中,边缘有一个方向:就像城市中的单行道一样,只能沿着边缘向一个方向行进,从顶点(或在我们的例子中,Git提交)到顶点。这些边被称为 arcs ,以提醒我们他们是单向的。 (我不打算在这里覆盖DAG的非循环部分,尽管它在理论意义上很重要。它在Git中自然发生,我们没有采取任何行动,并且我们不必太在乎。)
在我们的例子中,方向总是向后:从后来的子提交中,我们可以跟随通向其父级的弧。如果我们绘制一系列提交,我们会得到这样的结果:
... <-o <-o <-o ...
其中&#34;更新&#34;或者&#34;之后&#34;承诺是向右的。由于存储父哈希,每个子提交指回其父项。由于箭头总是向后指(left-ish),我们可以停止绘制它们,这很好,因为在StackOverflow上没有那么多好的箭头字体。 : - )
请注意,如果我们开始一个新的链,其中一个孩子指向一个早期的父母,我们就会出现分歧:
...--o--o--o--...
\
o--o--...
如果提交是合并提交,则它有两个弧,返回双方父母。这些合并提交重新加入了这些链:
...--o--o--o--o---M
\ /
o--o--o
最后一次提交,标记为M
,是一个包含两个父项的合并提交。
现在,这个一般的新提交,特别是合并提交 - 是分支和其他名称的来源......或者,有时候,不要!
我们上面提到哈希ID(36438dc...
)对人类无用;我们喜欢名称。所以我们有master
这样的名字来查找此ID。 Git也需要它们,至少在某些时候。特别是,虽然Git 可以搜索整个数据库并找到每个对象 - 并且它具有git fsck
和git gc
之类的维护命令这在大型存储库中非常慢。 fast 操作是获取已知的哈希ID并查找带有该ID的数据。
因此,一般来说,我们让Git以master
之类的名称开头,它们会找到像36438dc...
这样的提交。 Git可以使用其信息向我们展示提交,或检查或其他任何内容,尤其是tree
行,有时是parent
。或者我们可以让Git退回历史记录中的一个提交到36438dc...
的父级,这当然是另一个提交。我们可以使用提交的Git提取,或查看其父级,依此类推。
无论我们做什么,它都是启动此过程的名称。这些名称标识了一个特定的提交,我们(或Git)可以向后工作。
当人们去为Git图添加提交时,我们这样做:
$ git clone <url> # at least for the first time
$ cd <repostiory> # as necessary
... do some work ...
$ git add file1 file2 ... # or git add -u, etc
$ git commit
git add
步骤将文件从我们完成工作的工作树复制到索引/登台区域,替换索引/登台区域中的先前版本。 git commit
步骤从索引中现在的任何内容进行新提交:所有旧文件以前的方式,以及更改后的文件,就像我们将它们复制到{{1}的索引中一样}。
当git add
进行新提交时,它会执行以下步骤(不一定按此顺序):
git commit
和author
行的姓名,电子邮件地址和时间戳。让我们仔细看看第2步和第6步。如果我们执行了committer
,那么我们(git checkout master
将其放置)git status
,当前提交的哈希ID是存储在名称on branch master
下的哈希ID。这就是我们获取第2步的哈希ID的地方。在第6步中,我们用我们刚刚创建的新提交替换哈希ID。
换句话说,当我们建立一个分支&#34;成长&#34;通过使用master
和最终git checkout
,我们告诉Git创建新的,永久的,只读的提交对象,其父级是分支提示 之前的提交,并更新名称以指向新提交:
git commit
变为:
...--o--o--* <-- master
名称...--o--o--*--@ <-- master
,实际上,移动指向最新提交。
虽然我们正在进行更改并提交更改,但其他人已完成相同 master
存储库的git clone
,并且一直在添加自己的提交。您自己的存储库(Tom&#39,repo)现在可能有这个:
origin
在这里,我们使用名称 @ <-- master
/
...--o--o--* <-- origin/master
- 远程跟踪名称而不是分支名称 - 来记住Git存储库中origin/master
的位置在master
。分支名称origin
是&#34;汤姆的主人&#34;而不是&#34;起源的主人&#34;。它们最初都指向同一个提交,即我标记为master
的提交,但从那时起,您使用新的唯一ID进行了新提交。
与此同时,Sharon 也做了*
并一直在工作:
git clone
现在她在她的存储库中进行了新的提交,该存储库获取了一个新的唯一ID,与您的新ID和唯一ID不同:
...--o--o--* <-- master, origin/master
如果我们以某种方式将Sharon的存储库与您的存储库结合起来,那就让我们看看我们得到了什么。请记住,每个提交都由其哈希ID唯一标识,因此三个中间行提交在您的存储库和Sharon中是相同的:
...--o--o--* <-- origin/master
\
● <-- master
这种分叉/分支行为已经发生,即使我们尚未将您的存储库与Sharon的组合在一起。它发生在某种虚拟意义上:一旦我们进行了组合,它就会存在。
所以,让我们说Sharon现在运行 @ <-- (Tom's master)
/
...--o--o--* <-- origin/master
\
● <-- (Sharon's master)
。她的Git将在git push origin master
调用第三个Git并将其提交给origin
。然后,她的Git会要求●
的Git将origin
origin
设置为指向master
。如果一切顺利,它可能会做,●
的Git现在有:
origin
当你的 Git调用...--o--o--*--● <-- master
并下载新的提交时,你的Git会得到提交origin
,这对你的Git来说是新的。您的Git会通过更新您自己的●
来记住origin
主人的位置,并告诉您:
origin/master
这与我们之前绘制的图表相同 - 唯一的区别是我们在同一行上绘制了提交 @ <-- master
/
...--o--o--*--● <-- origin/master
,而不是在下面的行上。
现在这是你的工作,因为这已经落入你的存储库(而不是Sharon的),对此做了些什么。如果您只是天真地运行●
,则会将您的提交git merge
与Sharon的@
合并:
●
这个合并会像任何普通提交一样被添加,除了代替一行 @--M <-- master
/ /
...--o--o--*--● <-- origin/master
之外,它有两个:一个用于parent ...
提交,一个用于Sharon&#39; s {{1} }。
如果您改为使用@
,则将<{em>> 您的●
提交复制到新的改进提交。原始git rebase
与新原始版本之间的差异在于您的新版本将由Sharon构建,因此其父级将为@
:
@
随着时间的推移,使用●
代替 @
/
...--o--o--*--● <-- origin/master
\
○ <-- master
将为您提供线性结构而非分支结构。
答案 1 :(得分:1)
在gitk中说明的git历史记录中没有“自动生成”。每个点代表人类创建的提交。两个点之间的每一行显示提交之间的关系。如果一行从一个提交上升到另一个提交,则上部提交是较低提交的子节点,较低提交是较高提交的父节点。
请注意,您可以创建指向您希望的任何提交的标记和分支。如果您想在特定提交中使用静态标记,只需使用git tag
创建标记。
在我的理解中,视觉上的分支就是历史图形分成两部分。
分支只是指向提交的指针。当您签出分支然后再进行另一次提交时,分支将移至新提交。这意味着在同一次提交中创建两个分支并提交两组不同的更改会导致您看到的分叉。这是一个例子。
假设您有一个类似于
的提交历史记录A-B <- branchA, branchB
你做了
$ git checkout branchA
# make some changes
$ git commit -am 'Changes on branchA'
现在您的历史记录如下:
A-B <- branchB
\
C <- branchA
然后你做
$ git checkout branchB
# make some changes
$ git commit -am 'Changes on branchB'
现在历史看起来像这样:
A-B-D <- branchB
\
C <- branchA
请注意提交B
处的“fork”。这是由两个不同分支上的两个提交引起的。这里没有什么是自动的。历史反映了人类程序员的行为。