GIT PUSH究竟做了什么?

时间:2014-09-23 21:30:17

标签: git git-push

我似乎无法找到一个很好的解释。

我知道 git pull 的作用:

1) fetch ,即来自服务器的所有额外提交都被复制到本地存储中, origin / master 分支指针移动到提交的末尾链

2) origin / master 分支的 merge master 分支, master 分支指针移动到新创建的提交,而 origin / master 指针保持不变。

我认为 git push 做的事非常相似,但我不确定。我相信它会做其中一个,或类似的东西,或其他东西(?):

  • 复制所有本地提交并在那里进行合并(与 git pull 相反);但在这种情况下,服务器没有我的本地分支,因此我无法看到它合并的内容

OR

  • master 分支合并到 origin / master 中,将生成的提交推送到服务器并将其链接到现有的end-commit旁边,同时移动服务器&# 39; s 主人;这看起来不对,因为我的本地 origin / master 与服务器不同步。

我目前正在使用git进行基本操作,所以我做得很好,但我想完全理解这些内部结构。

5 个答案:

答案 0 :(得分:58)

假设你已经理解了git"对象" model(你的提交和文件等等都是" git数据库中的对象","松散"对象 - 那些没有打包以节省空间的对象 - {{1}等等)...

你是对的:.git/objects/12/34567...检索对象"他们" (git fetch,在这种情况下),你没有,并贴上标签:origin等。更具体地说,您的git在Internet电话(或任何其他合适的传输)上调用它们并询问:您拥有哪些分支,以及哪些提交ID?他们有origin/master且ID为master,因此您的git要求1234567...以及您尚未拥有的所有其他对象,并使您的1234567...成为origin/master指向提交对象1234567...

这里对称的git push部分是这样的:你的git像往常一样在同一个网络电话上调用他们的git,但这一次,而不只是向他们询问他们的分支,你的git告诉他们你的分支和你的 git存储库对象,然后说:"我怎样让你设置你的{{1} } master?"

他们的git会查看您发送的对象(新提交56789ab...以及他们没有的其他任何对象,他们需要接受它)。他们的git然后考虑将他们的 56789ab...设置为master的请求。

作为Chris K already answered,这里没有合并:你的git只是建议他们的git使用这个新的commit-ID覆盖他们的56789ab...。他们决定是否允许这样做。

如果"他们" (无论他们是谁)都没有设置任何特殊规则,git在这里使用的默认规则非常简单:如果更改是"快进"则允许覆盖。它还有一个附加功能:如果使用" force"进行更改,则允许覆盖 。国旗套。在这里设置强制标志通常不是一个好主意,作为默认规则,"只有快进#34;通常是正确的规则。

这里显而易见的问题是:快进究竟是什么?我们马上就能做到这一点;首先,我需要在标签上进行扩展,或者参考"更正式。

Git的参考资料

在git中,分支或标记,甚至隐藏和master之类的内容都是引用。其中大多数都位于git存储库的子目录HEAD中。 (一些顶级引用,包括.git/refs/,正好在HEAD本身。)所有引用都是包含SHA-1 ID的文件 1 ,如{ {1}}。 SHA-1 ID很麻烦,人们无法记住,因此我们使用.git(本例中的标签,git本身的2.1.0版本)等名称来保存它们。

有些参考文献是 - 或者至少是完全静态的。标记7452b4b5786778d5d87f5c90a94fab8936502e20绝不应该引用上面的SHA-1 ID之外的内容。但有些参考文献更具活力。具体而言,您自己的本地分支(如v2.1.0)正在移动目标。一个特殊情况v2.1.0甚至不是它自己的目标:它通常包含移动目标分支的名称。所以"间接"有一个例外。引用:master通常包含字符串HEADHEAD,或者沿着这些行;并且git没有(也不能)强制执行"永远不会改变"参考规则。特别是分支机构发生了很大的变化。

您如何知道引用是否应该更改?嗯,很多这只是按照惯例:分支移动和标签不是。但是你应该问:你怎么知道引用是分支,标记还是什么?

引用的名称空间:ref: refs/heads/masterref: refs/heads/branch

除了特殊的顶级引用外,所有git的引用都在refs/heads/中,如上所述。但是,在refs/tags/目录(或"文件夹"如果你在Windows或Mac上),我们可以拥有一整套子目录。到目前为止,Git有四个定义明确的子目录:refs/包含所有分支,refs/包含所有标记,refs/heads/包含所有"远程跟踪分支&# 34;和refs/tags/包含git' s" notes" (我会在这里忽略它们,因为它们有点复杂)。

由于你的所有分支都在refs/remotes/,因此git可以告诉我们应该允许更改这些分支,并且因为所有标记都在refs/notes/中,所以git可以告诉它们不应该这样。

分支机构的自动运动

当您进行新的提交并且位于refs/heads/之类的分支上时,git将自动移动引用。您的新提交是使用" parent commit"创建的。作为上一个分支提示,一旦您的新提交被安全地保存起来,git会更改refs/tags/以包含提交的ID。换句话说,它确保分支 name master子目录中的引用始终指向 tip-most commit

(事实上,作为存储在存储库中的提交图的一部分的提交集合意义上的分支是由存储库中的提交构成的数据结构。它与分支的唯一连接 name 是分支本身的提示提交存储在具有该名称的引用标签中。这在以后很重要,如果更改或删除分支名称,因为存储库会增加更多提交。现在这只是要记住的事情:"分支提示"之间存在差异,这是"分支名称"指向的分支,以及分支 - as-a-subset-of-commit-DAG。有点不幸的是,git倾向于将这些不同的概念归结为一个名称," branch"。)

究竟是快进?

通常你会看到"快进"在合并的上下文中,通常将合并作为master中的第二步完成。但事实上,"快速转发"实际上是标签移动的属性

让我们画一点提交图。小heads节点表示提交,每个节点都有一个箭头指向左,左 - 上,或左 - 下(或在一种情况下,两个箭头)到其父(或父)。为了能够通过名称引用三个,我将给它们大写字母名称而不是git pull。此外,这个基于角色的艺术作品没有箭头,所以你必须想象它们;只要记住它们都指向左或左,就像三个名字一样。

o

当你要求git更改引用时,你只需要它将新的提交ID粘贴到标签中。在这种情况下,这些标签位于o中,因此是分支名称,因此它们应该能够采用新值。

如果我们告诉git将 o - A <-- name1 / o - o - o - o - B <-- name2 \ / o - C <-- name3 放入refs/heads/,我们就会知道:

B

请注意,提交name1现在具有 no 名称,只有找到 o - A / o - o - o - o - B <-- name1, name2 \ / o - C <-- name3 才能找到左侧的A ...很难,因为o没有名字。提交A已被放弃,这两个提交已经有资格进行垃圾收集&#34;。 (在git中,&#34; sa&#34;幽灵名称&#34;留在&#34; reflog&#34;,这使得A分支在一般情况下保持30天左右。但那& #39;完全是一个不同的主题。)

告诉git将A放入A怎么样?如果我们接下来这样做,我们得到这个:

B

在这里,提交name3仍有一种方法可以找到它:从 o - A / o - o - o - o - B <-- name1, name2, name3 \ / o - C 开始,向下和向左工作,到其他(第二个)父提交,你发现提交{{1 }}。因此提交C 放弃。

像这样更新B 是快进,但更新C

更具体地说,参考变化是&#34;快进&#34;当且仅当对象 - 通常是提交 - 引用用于指向 - 仍然可以通过从 new 位置开始并向后工作,沿着所有可能的向后路径到达。用图表来说,如果旧节点是新节点的祖先,它就是快进。

通过合并

使C成为快进

当您做的唯一事情是添加新提交时,会发生分支名称快进;但是,如果您添加了新的提交,那么您也会合并其他人添加的新提交。也就是说,假设你的回购在你做了一次新的提交之后就有了这个:

name1

此时,向上和向右移动name3&#34;会是一个快进的人。但是,有其他人出现并更新了另一个(push)repo,因此您执行 o <-- master / ...- o - o <-- origin/master 并从中获取新的提交。你的git会移动你的origin/master标签(在你的回购中快速进行操作):

origin

此时,将git fetch移动到origin/master 将是快进,因为它会放弃一次新提交。

但是,您可以执行 o <-- master / ...- o - o - o <-- origin/master 操作,使用两个父提交ID在 origin/master上进行新提交。我们将此标记master(用于合并):

git merge origin/master

您现在可以master返回M并要求他们设置他们的 o - M <-- master / / ...- o - o - o <-- origin/master - 您正在呼叫git push - 等于你的(新)origin,因为为他们,这现在是一个快进的操作!

请注意,您也可以执行master,但请将其保留为不同的stackoverflow发布。 : - )


1 实际上,git引用总是从各个子目录中的单个文件开始,但如果引用很长时间没有更新,它往往会得到&# 34;填充&#34; (以及所有其他大多数静态引用)到一个包含完整打包引用的文件中。这只是一个节省时间的优化,这里的关键不依赖于确切的实现,而是使用git的origin/masterM命令来提取当前的SHA-1来自引用,或更新引用以包含新的SHA-1。

答案 1 :(得分:5)

它只执行复制,不进行合并。

更具体地说,它复制了本地存储/分支中对象存储的部分,并且从远程端丢失。这包括提交对象,引用,树和blob。

标签是一个值得注意的例外,它们需要包含--tags标志。

以下博客文章git is simpler than you think包含更多详细信息。

答案 2 :(得分:4)

我最简单的描述是,只需执行以下操作:(假设您执行 git push origin master

  • 将远程仓库中不存在的本地提交复制到远程仓库
  • 移动origin / master(在本地git和远程git中)指向相同的本地/主提交
  • 推送不合并

HOWEVER ,它会检查您的本地/主人是否基于来源/主人。从概念上讲,它意味着在git图中,从本地/主人可以直接返回到origin / master(不是本地git的origin / master,而是远程repo上的master),只需向下移动&#34;向下&# 34;表示在推送之前没有对远程仓库进行任何修改。否则推送将被拒绝

答案 3 :(得分:1)

来自the manual的技术性,行话负载答案如下:

  

git push“在发送时使用本地引用更新远程引用   完成给定引用所必需的对象。“

基本上,它是复制信息,以确保您的遥控器与您的本地仓库保持同步。但什么是refs,什么是对象?解读手册:

  • Refs manual entry是“以简单名称存储[对象的SHA-1值]的文件,因此您可以使用该指针而不是原始SHA-1值” [找到与之相关的内容]。您可以通过导航到目标中的.git/refs/heads/<branch name>.git/refs/remotes/origin/<branch name>等目录来查看它们。

  • 对象(manual entry)包括提交,树,blob和标记(默认情况下不会推送最后一个)。例如,从another SO answer引用Mark Longair,“提交记录了该时间点源代码的确切内容,包括日期,作者姓名和对父提交的引用”。

因此,当您git push时,git使用本地引用(由您键入git commit创建)来更新远程上的等效文件,从而更新指向最近提交的指针,然后更新任何新内容你创建的内容被作为对象复制到git的系统中,标有一些元数据和SHA-1引用。

作为参考内容的额外说明here in the Github API docs他们会显示API调用的示例JSON结果,要求在给定的回购中使用ref。它可能有助于您了解不同的信息如何相互关联。

答案 4 :(得分:0)

下图可能解释了这一点:

推动之前:

Before push

推送后:

After push

Git push将复制当前分支中目标分支中缺少的所有提交( a38de,893cf,756ae ),并将指针移至目标分支和远程跟踪分支中,并移至本地的同一提交中科。请注意,它不会执行任何合并。推送失败将被拒绝。