GIT PUSH究竟做了什么? [英] What does GIT PUSH do exactly?

查看:161
本文介绍了GIT PUSH究竟做了什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我知道 git pull 的作用:


$ b $ 1)一个 fetch ,即所有来自服务器的额外提交都被复制到本地repo和 origin / master 分支指针将 origin / master 分支的合并移到链的末尾



master 分支时, master 分支指针移动到新创建的提交,而 origin / master 指针保持放置状态。 b

我假设 git push 做的事情非常相似,但我不确定。我相信它会执行其中的一个,或类似的,或其他类似的功能(?):


  • 复制所有本地提交并在那里合并(与 git pull 相反);但在这种情况下,服务器没有我的本地 master 分支,所以我看不到它在合并什么



OR




  • 将我的 master 分支合并到 origin / master ,将结果提交到服务器,并将其链接到现有的最终提交,并移动服务器的 master ;这看起来并不正确,因为那时我的本地 origin / master 与服务器不在同步。



我目前使用git进行基本操作,所以我做得很好,但我想完全理解这些内部元素。 解决方案

假设你已经理解git的对象模型(你的提交和文件等等都只是git数据库中的对象,而松散对象 - 那些没有打包以节省空间的对象)存储在 .git / objects / 12/34567 ... 等等)...

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



git push 的部分是对称的这里是这样的:你的git像往常一样在同一个互联网电话上调用他们的git,但是这次,你不要问他们关于他们的分支,你的git告诉他们你的然后说:如何将你的 master 设置为 56789ab ...



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



由于 Chris K已经回答,因此这里没有合并发生:你的git只是建议他们的git用这个新的commit-ID覆盖他们的 master 。这是由他们的git来决定是否允许。



如果他们(他们是谁)没有设置任何特殊规则,那么git使用的默认规则这里很简单:如果更改是快进,则允许覆盖。它有一个附加功能:如果更改是通过设置强制标志来完成的,则覆盖也是允许的 。在这里设置强制标记通常不是一个好主意,因为默认规则仅快速转发通常是正确的规则。



<这里最明显的问题是:快速前进到底是什么?我们马上就会明白这一点;首先我需要在标签上扩展一些,或者引用更加正式。



Git的引用



在git中,一个分支或一个标签,甚至像存储和 HEAD 都是引用。它们中的大多数都位于git仓库的子目录 .git / refs / 中。 (一些顶级引用,包括 HEAD ,在 .git 本身中是正确的。)所有引用是,是包含SHA-1 ID的文件 1 ,例如 7452b4b5786778d5d87f5c90a94fab8936502e20 。 SHA-1 ID很麻烦,人们不可能记住,因此我们使用名称,比如 v2.1.0 (这里是一个标签,git 2.1.0版本身)以保存它们。



有些参考文献是 - 或者至少是打算完全静态的。标记 v2.1.0 不应该引用除上面的SHA-1 ID以外的其他内容。但有些参考文献更具活力。具体而言,您自己的本地分支机构(如 master )是移动目标。一个特殊情况,即 HEAD ,甚至不是它自己的目标:它通常包含移动目标分支的名称。因此,间接引用有一个例外: HEAD 通常包含字符串 ref:refs / heads / master ,或 ref:refs / heads / branch ,或者沿着这些线的东西;而且git不会(也不能)强制引用的永不改变规则。分支尤其变化很大。

您如何知道参考是否应该改变?那么,这只是惯例:分支移动和标签不会。但是你应该问:你怎么知道引用是分支,标签还是什么?

引用的名称空间: refs / heads / refs / tags /



特殊的顶层引用,所有的git引用都在 refs / 中,正如我们上面已经提到的那样。然而,在 refs / 目录(或者如果你在Windows或Mac上是文件夹),我们可以有一个完整的子目录集合。此时,Git拥有四个定义明确的子目录: refs / heads / 包含您的所有分支, refs / tags / 包含所有标签, refs / remotes / 包含您所有的远程追踪分支, refs / notes / refs / c>中,所以你可以使用git的notes(我会在这里忽略它们,因为它们有点复杂)。

头部/ ,git可以告诉这些应该被允许改变,因为所有的标签都在 refs / tags / 中,git可以告诉这些不应该。



分支的自动运动



当您进行新的提交并且位于分支例如 master ,git会自动 移动引用。你的新提交是以它的父提交作为前一个分支提示创建的,一旦你的新提交被安全地保存了,git就会改变 master 来包含新的提交。换句话说,它确保在子目录中的引用 name 始终指向 tip-大多数提交



(实际上,分支是存储在存储库中的提交图的一部分,是一个由存储库中的提交构成的数据结构,它与分支 name 的唯一连接是分支本身的提交提交存储在具有该名称的引用标签中,稍后重要,当分支名称被更改或删除时,因为存储库增长了更多的提交,现在只需记住一点:分支提示(分支名称指向的地方)和branch-as-a-sub-of-commit-DAG。有点不幸,git倾向于将这些不同的概念归入一个单一的名称,分支。)



什么是 快进?



通常你会你可以在合并的上下文中看到快进,通常在 git pull 中作为第二步完成合并。但实际上,快进实际上是一个标签移动的属性

让我们画一点提交图。小 o 节点表示提交,每个节点都有一个箭头指向左,左和右或左右(或者在一种情况下,两个箭头)到其父母(或父母)。为了能够引用三个名字,我会给他们大写字母名称而不是 o 。此外,这个基于人物的艺术品没有箭头,所以你必须想象它们;请记住,他们都指向左侧或左侧,就像三个名字一样。

  o  -  A<  -  name1 
/
o - o - o - o - B < - name2
\ /
o - C < - name3

当您要求git更改引用时,只需要让它将新的提交ID粘贴到标签中即可。在这种情况下,这些标签位于 refs / heads / 中,因此是分支名称,所以它们应该能够接受新的值。



如果我们告诉git将 B 放入 name1 中,我们得到:

  o  -  A 
/
o - o - o - o - B < - name1,name2
\ /
o - C < - name3

请注意,提交 A 现在具有 no 名称,并且仅在其左侧找到 o 通过找到 A ...这很难,因为 A 没有名字。提交 A 已被放弃,并且这两个提交已成为垃圾收集的条件。 (在git中,在reflog中留下了一个鬼名,这个名字使得 A 这个分支在一般情况下保持了30天,但这完全是一个完全不同的主题。)



告诉git将 B 放入 name3 C>?如果我们接下来这样做,我们得到这个:

  o  -  A 
/
o - o - o - o - B< - name1,name2,name3
\ /
o - C

在这里,commit C 仍然有办法找到它:从 B 开始,和其他(第二个)父提交,并且您发现提交 C 。所以提交 C not放弃。



更新 name1 就像 不是快进,但更新 name3 p>

更具体地说,当且仅当引用用于指向的对象(通常是提交)通过启动时仍然可以访问时,引用更改是快进从新 放置并向后工作,用图表来说,如果旧节点是新节点的祖先,那么它是快进的。



制作 push

可以快速转发,当您唯一要做的就是添加新的提交时,会发生分支名称快速转发;但是如果你添加了新的提交,你还可以合并任何其他人添加的新提交。也就是说,假设你的repo在你做了一个新的commit之后就有了它:

  o < -  master 
/
...- o - o< - origin / master



<此时,将 origin / master 向上和向右移动将是快进。但是,其他人出现并更新了其他( origin )回购,因此您执行 git fetch 并获得一个新的承诺。你的git会移动你的 origin / master 标签(在你的repo快进操作中,就像它发生的那样):

  o < -  
/
...- o - o - o < - 原点/主


此时,将 origin / master 移动到 master



然而,您可能不会快速前进,因为它会放弃该新提交。可以执行 git merge origin / master 操作在 master ,有两个父母提交ID。我们来标记这个 M (用于合并):

  o  -  M <  -  master 
/ /
...- o - o - o< - origin / master

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



请注意,您也可以执行 git rebase ,但让我们将其留给不同的计算器发布。 : - )




1 实际上,git引用总是作为单独的文件开始子目录,但是如果引用长时间没有更新,它往往会被打包(连同所有其他主要是静态引用)到一个充满打包引用的文件中。这只是一个节省时间的优化,这里的关键不是依赖于确切的实现,而是使用git的 rev-parse update-ref 命令从引用中提取当前的SHA-1,或更新引用以包含新的SHA-1。


I can't seem to find a good explanation of this.

I know what git pull does:

1) a fetch, i.e. all the extra commits from the server are copied into the local repo and the origin/master branch pointer moves to the end of the commit chain

2) a merge of the origin/master branch into the master branch, the master branch pointer moving to the newly created commit, while the origin/master pointer staying put.

I assume git push does something very similar, but I don't know for sure. I believe it does one of these, or something similar, or something else (?):

  • copies all local commits and makes a merge there (the reverse of what git pull does); but in this case, the server does not have my local master branch, so I can't see what is it merging

OR

  • merges my master branch into the origin/master, pushing the resulting commit to the server and linking it next to the existing end-commit, also moving the server's master; this doesn't seem right because then my local origin/master is not in sync with the server's.

I'm currently using git for basic operations so I'm doing fine, but I want to fully understand these internals.

解决方案

Assuming you already understand git's "objects" model (your commits and files and so on are all just "objects in the git database", with "loose" objects—those not packed up to save space—stored in .git/objects/12/34567... and the like)...

You are correct: git fetch retrieves objects "they" (origin, in this case) have that you don't, and sticks labels on them: origin/master and the like. More specifically, your git calls up theirs on the Internet-phone (or any other suitable transport) and asks: what branches do you have, and what commit IDs are those? They have master and the ID is 1234567..., so your git asks for 1234567... and any other objects needed that you don't already have, and makes your origin/master point to commit object 1234567....

The part of git push that is symmetric here is this: your git calls up their git on the same Internet-phone as usual, but this time, instead of just asking them about their branches, your git tells them about your branches and your git repository objects, and then says: "How about I get you to set your master to 56789ab...?"

Their git takes a look at the objects you sent over (the new commit 56789ab... and whatever other objects you have that they didn't, that they would need to take it). Their git then considers the request to set their master to 56789ab....

As Chris K already answered, there is no merging happening here: your git simply proposes that their git overwrite their master with this new commit-ID. It's up to their git to decide whether to allow that.

If "they" (whoever they are) have not set up any special rules, the default rule that git uses here is very simple: the overwrite is allowed if the change is a "fast forward". It has one additional feature: the overwrite is also allowed if the change is done with the "force" flag set. It's usually not a good idea to set the force flag here, as the default rule, "only fast forwards", is usually the right rule.

The obvious question here is: what exactly is a fast forward? We'll get to that in a moment; first I need to expand a bit on labels, or "references" to be more formal.

Git's references

In git, a branch, or a tag, or even things like the stash and HEAD are all references. Most of them are found in .git/refs/, a sub-directory of the git repository. (A few top-level references, including HEAD, are right in .git itself.) All a reference is, is a file1 containing an SHA-1 ID like 7452b4b5786778d5d87f5c90a94fab8936502e20. SHA-1 IDs are cumbersome and impossible for people to remember, so we use names, like v2.1.0 (a tag in this case, version 2.1.0 of git itself) to save them for us.

Some references are—or at least are intended to be—totally static. The tag v2.1.0 should never refer to something other than the SHA-1 ID above. But some references are more dynamic. Specifically, your own local branches, like master, are moving targets. One special case, HEAD, is not even a target of its own: it generally contains the name of the moving-target branch. So there's one exception for "indirect" references: HEAD usually contains the string ref: refs/heads/master, or ref: refs/heads/branch, or something along those lines; and git does not (and cannot) enforce a "never change" rule for references. Branches in particular change a lot.

How do you know if a reference is supposed to change? Well, a lot of this is just by convention: branches move and tags don't. But you should then ask: how do you know if a reference is a branch, or a tag, or what?

Name spaces of references: refs/heads/, refs/tags/, etc.

Other than the special top-level references, all of git's references are in refs/ as we already noted above. Within the refs/ directory (or "folder" if you're on Windows or Mac), though, we can have a whole collection of sub-directories. Git has, at this point, four well-defined subdirectories: refs/heads/ contains all your branches, refs/tags/ contains all your tags, refs/remotes/ contains all your "remote-tracking branches", and refs/notes/ contains git's "notes" (which I will ignore here as they get a bit complicated).

Since all your branches are in refs/heads/, git can tell that these should be allowed to change, and since all your tags are in refs/tags/, git can tell that these should not.

Automatic motion of branches

When you make a new commit, and are on a branch like master, git will automatically move the reference. Your new commit is created with its "parent commit" being the previous branch-tip, and once your new commit is safely saved away, git changes master to contain the ID of the new commit. In other words, it makes sure that the branch name, the reference in the heads sub-directory, always points to the tip-most commit.

(In fact, the branch, in the sense of a collection of commits that is part of the commit-graph stored in the repository, is a data structure made out of the commits in the repository. Its only connection with the branch name is that the tip commit of the branch itself is stored in the reference label with that name. This is important later, if and when branch names are changed or erased as the repository grows many more commits. For now it's just something to keep in mind: there's a difference between the "branch tip", which is where the "branch name" points, and the branch-as-a-subset-of-commit-DAG. It's a bit unfortunate that git tends to lump these different concepts under a single name, "branch".)

What exactly is a fast forward?

Usually you see "fast forward" in the context of merge, often with the merge done as the second step in a git pull. But in fact, "fast forwarding" is actually a property of a label move.

Let's draw a little bit of a commit graph. The little o nodes represent commits, and each one has an arrow pointing left, left-and-up, or left-and-down (or in one case, two arrows) to its parent (or parents). To be able to refer to three by name I'll give them uppercase letter names instead of o. Also, this character-based artwork doesn't have arrows, so you have to imagine them; just remember that they all point left or left-ish, just like the three names.

            o - A   <-- name1
          /
o - o - o - o - B   <-- name2
      \       /
        o - C       <-- name3

When you ask git to change a reference, you simply ask it to stick a new commit ID into the label. In this case, these labels live in refs/heads/ and are thus branch names, so they are supposed to be able to take on new values.

If we tell git to put B into name1, we get this:

            o - A
          /
o - o - o - o - B   <-- name1, name2
      \       /
        o - C       <-- name3

Note that commit A now has no name, and the o to the left of it is found only by finding A ... which is hard since A has no name. Commit A has been abandoned, and these two commits have become eligible for "garbage collection". (In git, there's a "ghost name" left behind in the "reflog", that keeps the branch with A around for 30 days in general. But that's a different topic entirely.)

What about telling git to put B into name3? If we do that next, we get this:

            o - A
          /
o - o - o - o - B   <-- name1, name2, name3
      \       /
        o - C

Here, commit C still has a way to find it: start at B and work down-and-left, to its other (second) parent commit, and you find commit C. So commit C is not abandoned.

Updating name1 like this is not a fast-forward, but updating name3 is.

More specifically, a reference-change is a "fast forward" if and only if the object—usually a commit—that the reference used to point-to is still reachable by starting from the new place and working backwards, along all possible backwards paths. In graph terms, it's a fast-forward if the old node is an ancestor of the new one.

Making a push be a fast-forward, by merging

Branch-name fast-forwards occur when the only thing you do is add new commits; but also when, if you've added new commits, you've also merged-in whatever new commits someone else added. That is, suppose your repo has this in it, after you've made one new commit:

             o   <-- master
           /
...- o - o       <-- origin/master

At this point, moving origin/master "up and right" would be a fast-forward. However, someone else comes along and updates the other (origin) repo, so you do a git fetch and get a new commit from them. Your git moves your origin/master label (in a fast-forward operation on your repo, as it happens):

             o   <-- master
           /
...- o - o - o   <-- origin/master

At this point, moving origin/master to master would not be a fast-forward, as it would abandon that one new commit.

You, however, can do a git merge origin/master operation to make a new commit on your master, with two parent commit IDs. Let's label this one M (for merge):

             o - M  <-- master
           /   /
...- o - o - o   <-- origin/master

You can now git push this back to origin and ask them to set their master—which you are calling origin/master—equal to your (new) M, because for them, that's now a fast-forward operation!

Note that you can also do a git rebase, but let's leave that for a different stackoverflow posting. :-)


1In fact, git references always start out as individual files in various sub-directories, but if a reference doesn't get updated for a long while, it tends to get "packed" (along with all the other mostly-static references) into a single file full of packed references. This is just a time-saving optimization, and the key here is not to depend on the exact implementation, but rather to use git's rev-parse and update-ref commands to extract the current SHA-1 from a reference, or update a reference to contain a new SHA-1.

这篇关于GIT PUSH究竟做了什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆