GIT PUSH 到底做了什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26005031/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 02:31:41  来源:igfitidea点击:

What does GIT PUSH do exactly?

gitgit-push

提问by Bogdan Alexandru

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

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

I know what git pulldoes:

我知道git pull做什么:

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

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

2) a mergeof the origin/masterbranch into the masterbranch, the masterbranch pointer moving to the newly created commit, while the origin/masterpointer staying put.

2)合并所述的原点/主分支到支路,该支指针移动到新创建的提交,而原点/主指针停留放。

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

我认为git push做了一些非常相似的事情,但我不确定。我相信它会执行其中之一,或类似的事情,或其他(?):

  • copies all local commits and makes a merge there (the reverse of what git pulldoes); but in this case, the server does not have my local masterbranch, so I can't see what is it merging
  • 复制所有本地提交并在那里进行合并(与git pull所做的相反);但在这种情况下,服务器没有我本地的master分支,所以我看不到它在合并什么

OR

或者

  • merges my masterbranch 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/masteris not in sync with the server's.
  • 将我的master分支合并到origin/master,将结果提交推送到服务器并将其链接到现有的最终提交旁边,同时移动服务器的master;这似乎不对,因为那时我的本地来源/主服务器与服务器的不同步。

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

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

回答by torek

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)...

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

You are correct: git fetchretrieves objects "they" (origin, in this case) have that you don't, and sticks labels on them: origin/masterand 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 masterand 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/masterpoint to commit object 1234567....

您是对的:git fetch检索“他们”(origin在本例中为 )拥有而您没有的对象,并在其上贴上标签:origin/master等等。更具体地说,您的 git 在互联网电话(或任何其他合适的传输)上调用他们的并询问:您有哪些分支,以及那些提交 ID 是什么?他们有master并且 ID 是1234567...,所以你的 git 要求1234567...你还没有需要的任何其他对象,并origin/master指出提交 object 1234567...

The part of git pushthat 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 theirbranches, your git tells them about yourbranches and yourgit repository objects, and then says: "How about I get you to set your masterto 56789ab...?"

git push这里对称的部分是这样的:你的 git 像往常一样在同一个互联网电话上调用他们的 git,但是这一次,不是仅仅询问他们分支,你的 git 告诉他们你的分支和你的git 存储库对象,然后说:“我让你把你的设置master56789ab...怎么样?”

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 theirmasterto 56789ab....

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

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

正如Chris K 已经回答的那样,这里没有发生合并:您的 git 只是建议他们的 gitmaster用这个新的提交 ID覆盖他们。由他们的 git 决定是否允许这样做。

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 alsoallowed 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 rightrule.

如果“他们”(无论他们是谁)没有设置任何特殊规则,那么 git 在这里使用的默认规则非常简单:如果更改是“快进”,则允许覆盖。它还有一个附加功能:如果更改是在设置了“force”标志的情况下完成的,则允许覆盖。在这里设置强制标志通常不是一个好主意,因为默认规则“仅快进”通常是正确的规则。

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

Git的参考

In git, a branch, or a tag, or even things like the stash and HEADare 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 .gititself.) All a reference is, is a file1containing 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.

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

Some references are—or at least are intended to be—totally static. The tag v2.1.0should 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 nameof the moving-target branch. So there's one exception for "indirect" references: HEADusually 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.

一些引用是——或者至少是打算——完全静态的。标签v2.1.0不应引用上述 SHA-1 ID 以外的内容。但有些参考资料更具动态性。具体来说,您自己的本地分支机构(例如master)是移动目标。一种特殊情况,HEAD甚至不是它自己的目标:它通常包含移动目标分支的名称。所以“间接”引用有一个例外:HEAD通常包含字符串ref: refs/heads/master,或ref: refs/heads/branch,或类似的东西;并且 git 不会(也不能)对引用强制执行“永不更改”规则。特别是分支机构变化很大。

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.

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

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).

除了特殊的顶级引用之外,所有 git 的引用都在refs/我们上面已经提到过。但是,在refs/目录(或“文件夹”,如果您使用的是 Windows 或 Mac)中,我们可以拥有一整套子目录。在这一点上,Git 有四个明确定义的子目录:refs/heads/包含所有分支、refs/tags/包含所有标签、refs/remotes/包含所有“远程跟踪分支”和refs/notes/包含 git 的“注释”(我将在此处忽略,因为它们有点复杂的)。

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.

由于您的所有分支都在 中refs/heads/,git 可以告诉您应该允许更改这些,并且由于您的所有标签都在 中refs/tags/,因此 git 可以告诉您这些不应该更改。

Automatic motion of branches

树枝的自动运动

When you make a new commit, and are on a branch like master, git will automaticallymove 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 masterto contain the ID of the newcommit. In other words, it makes sure that the branch name, the reference in the headssub-directory, always points to the tip-most commit.

当你进行一个新的提交,并且在一个像 的分支上时master,git 会自动移动引用。你的新提交是用它的“父提交”作为前一个分支提示创建的,一旦你的新提交被安全保存,git 就会更改master为包含提交的 ID 。换句话说,它确保分支名称heads子目录中的引用)始终指向最尖端的提交

(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 nameis 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".)

(实际上,从作为存储在存储库中的提交图的一部分的提交集合的意义上讲,分支是由存储库中的提交构成的数据结构。它与分支名称的唯一联系是分支本身的提示提交存储在具有该名称的引用标签中。这在以后很重要,如果以及何时随着存储库增加更多提交而更改或删除分支名称。现在只需要记住:有“分支提示”(即“分支名称”指向的地方)和分支作为提交的子集的子集之间的差异。有点不幸的是,git 倾向于将这些不同的概念混为一谈名称,“分支”。)

What exactly isa 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.

通常您会在合并的上下文中看到“快进”,通常将合并作为git pull. 但实际上,“快进”其实是一个标签移动的属性。

Let's draw a little bit of a commit graph. The little onodes 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节点代表提交,每个节点都有一个箭头,指向其父节点(或父节点)的左、左上或左下(或在一种情况下,两个箭头)。为了能够通过名称引用三个,我会给它们大写字母名称而不是o. 此外,这个基于角色的艺术作品没有箭头,所以你必须想象它们;请记住,它们都指向 left 或 left-ish,就像三个名字一样。

            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.

当您要求 git 更改引用时,您只需要求它在标签中粘贴一个新的提交 ID。在这种情况下,这些标签存在于refs/heads/分支名称中,因此它们应该能够采用新值。

If we tell git to put Binto name1, we get this:

如果我们告诉 git put Binto name1,我们会得到:

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

Note that commit Anow has noname, and the oto the left of it is found only by finding A... which is hard since Ahas no name. Commit Ahas 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 Aaround for 30 days in general. But that's a different topic entirely.)

请注意,提交A现在没有名称,o它左侧的 只能通过查找A...找到,因为A没有名称,这很难。CommitA已被放弃,这两个 commit 已成为“垃圾收集”的资格。(在 git 中,在“reflog”中留下了一个“ghost name”,它使分支A在一般情况下保持30 天左右。但这完全是一个不同的主题。)

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

告诉 git put Bintoname3怎么样?如果我们接下来这样做,我们会得到:

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

Here, commit Cstill has a way to find it: start at Band work down-and-left, to its other (second) parent commit, and you find commit C. So commit Cis notabandoned.

在这里, commitC仍然有一种方法可以找到它:开始B并向下和向左工作,到它的另一个(第二个)父提交,然后您找到 commit C。因此,提交C放弃。

Updating name1like this is nota fast-forward, but updating name3is.

name1像这样的更新不是快进,但更新name3.

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 newplace 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 pushbe a fast-forward, by merging

push通过合并使 a成为快进

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 fetchand get a new commit from them. Your git moves your origin/masterlabel (in a fast-forward operation on your repo, as it happens):

在这一点上,origin/master“向上和向右”移动将是一个快进。但是,其他人出现并更新了另一个 ( origin) 存储库,因此您执行 agit fetch并从他们那里获得新的提交。你的 git 移动你的origin/master标签(在你的 repo 的快进操作中,当它发生时):

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

At this point, moving origin/masterto masterwould notbe a fast-forward, as it would abandon that one new commit.

在这一点上,移动origin/mastermaster不会是快进,因为这将放弃一个新的提交。

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

但是,您可以使用两个父提交 ID执行一个git merge origin/master操作来对您的进行新提交master。让我们标记这个M(用于合并):

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

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

您现在可以git push返回origin并要求他们将他们的(master您正在调用的)origin/master设置为您的(new) M,因为对他们来说,这现在是一个快进操作!

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

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



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-parseand update-refcommands to extract the current SHA-1 from a reference, or update a reference to contain a new SHA-1.

1事实上,git 引用总是从各个子目录中的单个文件开始,但如果引用长时间没有更新,它往往会被“打包”(以及所有其他大部分静态引用)到一个充满压缩参考的单个文件中。这只是一个节省时间的优化,这里的关键不依赖于确切的实现,而是使用 gitrev-parseupdate-ref命令从引用中提取当前的 SHA-1,或者更新引用以包含新的 SHA- 1.

回答by Chris K

It only performs a copy, no merge.

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

More specifically it copies the parts of the object store that are in the local repo/branch and are missing from the remote side. This includes, commit objects, refs, trees and blobs.

更具体地说,它复制本地存储库/分支中的对象存储部分,而远程端缺少这些部分。这包括提交对象、引用、树和 blob。

Tags are a notable exception, they require the --tags flag to be included.

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

The following blog post, git is simpler than you thinkhas more detail.

下面的博文,git比你想象的简单,有更多的细节。

回答by palazzo train

My simplest description is, push just do the following: (assuming you do git push origin master)

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

  • Copy the local commits that are not existed in the remote repo to the remote repo
  • Move the origin/master (both in your local git and in the remote git) to point to the same local/master commit
  • Push DOES NOT merge
  • 将远程仓库中不存在的本地提交复制到远程仓库
  • 移动 origin/master(在本地 git 和远程 git 中)指向同一个本地/主提交
  • 推不合并

HOWEVER, It will check whether your local/master is based on the origin/master. Conceptually, it means in the git graph, from local/master you can go back directly to origin/master (not the origin/master of your local git, but the master on the remote repo) by only moving "downward", meaning no modification was made to remote repo before your push. Otherwise push will be rejected

但是,它会检查您的本地/主服务器是否基于源/主服务器。从概念上讲,这意味着在 git 图中,您可以从 local/master 直接返回到 origin/master(不是本地 git 的 origin/master,而是远程 repo 上的 master),只需“向下”移动,这意味着没有在您推送之前对远程仓库进行了修改。否则推送将被拒绝

回答by Aidan Miles

The technical, jargon-laden answer from the manualis as follows:

从技术,行话载货回答手册如下:

git push"updates remote refs using local refs, while sending objects necessary to complete the given refs."

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

So basically, it is copying information, so as to make sure your remote is up to date with your local repo. But what are refs, and what are objects? Paraphrasing the manual:

所以基本上,它是复制信息,以确保您的遥控器与您的本地存储库保持同步。但是什么是引用,什么是对象?解释手册:

  • Refs manual entryare files which "store the SHA-1 value [of an object, like a commit] under a simple name so you can use that pointer rather than the raw SHA-1 value" [to find the content associated with it]. You can see them by navigating to directories like .git/refs/heads/<branch name>, or .git/refs/remotes/origin/<branch name>in your repo.

  • Objects (manual entry) include commits, trees, blobs, and tags (last of which are not pushed by default). As an example, quoting Mark Longair from another SO answer, "a commit records the exact content of the source code at that point in time with the date, author's name, and references to parent commits".

  • 参考手册条目是“以简单名称存储 [对象的 SHA-1 值,例如提交] 的文件,以便您可以使用该指针而不是原始 SHA-1 值”[以查找与其关联的内容] . 您可以通过导航到.git/refs/heads/<branch name>, 或.git/refs/remotes/origin/<branch name>您的存储库中的目录来查看它们。

  • 对象(手动输入)包括提交、树、blob 和标签(默认情况下不推送最后一个)。例如,引用另一个 SO 答案中的Mark Longair 的话,“提交记录了该时间点源代码的确切内容以及日期、作者姓名和对父提交的引用”。

Thus, when you git push, git uses local refs (created by you typing git commit) to update equivalent files on the remote, thus updating pointers to most recent commits, and then any new content that you've created gets copied into git's system as objects, labeled with some metadata and SHA-1 refs.

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

As an extra illustration of what a ref is, here in the Github API docsthey show example JSON results of API calls asking for refs in a given repo. It might help you understand how the different pieces of information relate to each other.

作为 ref 是什么的额外说明在 Github API 文档中,他们显示了 API 调用的示例 JSON 结果,在给定的 repo 中要求 refs。它可能会帮助您了解不同的信息是如何相互关联的。

回答by Minh Nguyen

Following diagram may explain it:

下图可以解释它:

Before push:

推送前:

Before push

推送前

After push:

推送后:

After push

推后

Git push will copy all commits from current branch that are missing in destination branch (a38de, 893cf, 756ae) and move the pointers both in destination branch and remote tracking branch to the same commit in local branch. Note that it will not perform any merge. Push will get rejected if it fails.

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