61

我今天偶然发现了一些奇怪的东西。我请暑期工作的同事帮我为我的代码设置一个新的远程 git 存储库,但我对他做了什么以及我想做什么感到很困惑。我让他发送他的配置以便能够看到他的遥控器的路径,并发现他没有遥控器。当我问他这个问题时,他这样解释他的工作流程:

  1. 在本地更改某些内容
  2. 犯罪
  3. 移动到远程目录
  4. git 拉 c:\localdir

因此,他没有推送到远程仓库,而是不断地从本地仓库拉到我们服务器上的仓库。有点倒退。当我就此与他对质时,他问我有什么不同,我无法真正回答他,但我认为有什么对不对?

所以我对你们所有人的问题是:推送到遥控器和从遥控器拉出有什么区别?

4

5 回答 5

22

推送到远程:将您必须的一些提交发送到另一个 git 存储库。git repo 被视为“远程”,但它可以是硬盘驱动器另一个文件夹中的 repo。从远程拉取:从远程仓库获取一些提交并将它们合并到您当前的 HEAD 中(您当前的仓库结帐)

您的同事可能使用 pull 而不是 push,因为您的存储库可能不可用(没有运行 git 守护程序,或 gitweb 或 ssh 服务器),但他可以从您的计算机上使用。由于它是一个服务器,他可能不想暴露可能成为攻击媒介的 git 守护进程/服务。

但是,如果您的存储库是共享/可用的,他将能够做到:

  1. 在本地改变一些东西
  2. 犯罪
  3. 推送到您的存储库
于 2012-06-28T08:32:43.490 回答
19

在我看来,您可以让用户将他们的提交推送到某个被认为是“主”的存储库,或者让他们将拉取请求发送给有权修改所述“主”的单个用户。

例如,Github 不会让非贡献者推送到存储库,但会允许他们发送拉取请求,以便贡献者可以集成他们的更改。

于 2012-06-28T08:43:55.977 回答
6

TL;博士

Push、fetch 和 pull 让两个不同的 Git 相互交谈。在一种特殊情况下——包括作为问题基础的那个c:\localdir——两个不同的 Git 存储库位于同一台计算机上,但通常,这两个不同的存储库可以位于任何两台不同的计算机上

  • Push:发送提交并要求他们更新他们的分支。这要求事情最终是正确的。这不能结合并行开发。

  • Pull: runs git fetch,获取提交并让您的 Git 更新您的远程跟踪名称,然后运行第二个 Git 命令来更新您的分支。第二条命令可以结合并行开发。

当存储库位于不同的计算机上时,传输方向往往更为重要,因为您无法轻松切换观点。

除了公认的答案git pull(就目前而言足够准确)之外,和之间还有一些其他关键区别git push。我们需要从这个开始:

push 的反义词是 fetch

Git在这里不小心使用了错误的动词。在 Mercurial 中,我们必须hg pull从另一个存储库获取hg push提交,并将提交发送到另一个存储库。但是 Gitgit pull做了件事:(1)获取提交;(2)签出或合并这些提交。然后 Git 不得不将这两个步骤分开,因为有时您不想立即执行第 2 步。

这意味着在 Git 中,实际相反的git push不是git pull,而是git fetchgit pull命令的意思是:

  1. 运行git fetch;然后
  2. 运行第二个 Git 命令。

第二个命令是事情变得最复杂的地方。如果我们可以省略它——如果我们处理fetch 和 push——它会更简单。我们可以稍后添加第二个命令。

git fetch总是安全的,但git push不是

我们在这里遇到的下一个问题很简单,但是如果您还没有“得到它”,那么它会非常令人困惑,直到您突然“得到它”并且它是有道理的。

当我们拥有一个 Git 存储库时,我们实际上拥有三样东西:

  1. 我们有一个提交数据库(和其他对象,但提交是有趣的部分)。提交是编号的,但数字看起来是随机的。它们不是简单的计数:commit #1 后面没有 commit #2,实际上一开始就没有“commit #1”。这些数字是哈希 ID,它们看起来像随机涂鸦:84d06cdc06389ae7c462434cb7b1db0980f63860例如。

    提交中的内容是完全只读的。每个提交就像每个文件的完整快照。这对于存档非常有用,对于完成任何新工作毫无用处。因此,在普通(非裸)存储库中,我们还有:

  2. 一个普通的日常存储库有一个我们完成工作的地方。我们不会在这里详细介绍这一点,但这对于 fetch-vs-push 很重要并且很重要。 一些存储库故意省略了这个工作区。这些被称为存储库,我们通常在服务器上找到它们。

  3. 最后,每个存储库都有一个名称数据库,包括分支名称。这些名称允许您的 Git 找到您的提交。他们的意思是你不必记住84d06cdblahblahwhatever

当你运行时git fetch,你的 Git 会调用其他 Git,通常是通过网络电话在一个https://ssh://地址。你可以c:\localdir用 a or或其他什么来调用其他 Git /mnt/some/path。在这种特殊情况下,您的计算机可能会与自己对话——但通常它会与另一台计算机对话,它拥有自己完全独立的 Git 存储库。其他 Git 存储库也可以拥有所有这三个。如果它在服务器上,它可能是一个裸存储库,并且没有工作区。然而,它总是有自己的提交数据库和自己的名称数据库。

这意味着你的Git 有你的提交(也许还有他们的)和你的分支名称。 他们的Git 有他们的提交(也许你也有)和他们的分支名称。使用git fetch,您可以让您的 Git 调用他们的 Git 并获取他们的提交(所以现在您拥有自己的和他们的);使用git push,你让你的 Git 调用他们的 Git,并给他们你的提交(所以现在他们有他们的和你的)。

到目前为止,fetch 和 push 之间的主要区别在于数据传输的方向 使用 fetch可以获得提交,使用 push可以提交提交。 但差异并不止于此。

完成git fetch后,您的 Git 知道所有提交。这很好——但我们刚刚注意到Git 用来查找提交的提交编号是看起来很丑陋的乱七八糟的乱七八糟的东西。所以要做的是获取他们所有的分支名称——他们用来查找提交的名称——并将它们复制到您自己的 Git 中,但将它们更改远程跟踪名称。例如,它们变成了你的。如果他们有,您的 Git 会创建或更新您的,依此类推。这意味着永远不要碰你自己的任何分支,这就是为什么它总是安全的。你要么得到新的提交,要么没有。你永远不会git fetchmainorigin/maindeveloporigin/developgit fetch您自己的任何提交。然后,您的 Git 会在必要时更新您的远程跟踪名称。然后就完成了。这就是整个正常git fetch操作:如果合适,引入一些提交,如果合适,更新一些非分支名称。1

的最后一部分git push,就在完成之前,包含一个请求。你的 Git 要求他们的 Git 取悦,如果可以的话,改变他们的一些名字。例如,如果你运行git push origin develop,你的 Git 会发送你有的任何提交,他们没有,他们需要完成操作,然后它会发送一个礼貌的请求:如果可以,请让你的分支名称develop找到 commit ________您的 Git 使用您的分支名称develop找到的提交填充此空白。

这里的主要区别是git fetch更新您的远程跟踪名称git push要求他们更新他们的分支名称。 如果他们正在进行开发,他们可能会认为更新他们的分支名称是不合适的。


1您可以通过多种方式运行git fetch并告诉它更新您自己的分支名称。这不是偶然发生的;你必须Git 去做。你不应该让 Git 去做。如果你是 Git Master,这条规则就变成了:你可能不应该让 Git 去做。


第二条命令

现在是时候查看git pull调用的第二个命令了。嗯,时间差不多了。首先我们应该看看 Git 如何找到提交。

我之前提到过,Git 使用分支名称查找提交。这是真的,但不是完整的画面。我还提到了远程跟踪名称。Git 可以找到具有远程跟踪名称的提交。这更完整,但仍然不是真正的完整。下面是 Git 的一整套技巧:

  • 如果你给它原始哈希 ID,Git 总能找到一个提交。好吧,如果它实际上在您的存储库中那么您可能需要先使用它来获取它。如果 Git无法从哈希 ID 中找到提交,那只是意味着它还没有在您的存储库中。只需使用从某些具有它的 Git 中获取它然后就可以了。git fetchgit fetch

  • Git 可以从名称中找到提交。各种名称都可以在这里使用:分支名称maindevelop,远程跟踪名称origin/mainorigin/develop,标签名称v1.2,甚至时髦的特殊用途名称。Git 有很多你不经常看到的东西。将名称转换为哈希 ID 的规则在gitrevisions 文档中进行了描述。

  • Git 可以从另一个提交中找到一个提交。 这导致了gitrevisions中的许多规则。这句话在这里用粗体表示,因为它非常重要。

  • 最后,Git 可以通过各种搜索操作找到提交,也在gitrevisions中进行了描述。

gitrevisions 里面有很多东西,你不需要记住所有的东西。请记住,有很多方法可以找到提交。使用git log,然后剪切和粘贴哈希 ID 是一种很好的方法,但有时您可能想尝试各种快捷方式。但是,请记住另外一件事:git log通过使用提交来查找提交来查找提交

每个提交存储两件事:它有所有文件的完整快照,正如我们前面提到的,但它元数据:关于提交本身的信息。例如,这包括提交人的姓名和电子邮件地址。它还包括另一个姓名和电子邮件地址(“提交者”与“作者”),以及两个日期和时间戳。它在这个元数据中有很多东西,Git 本身的关键是它有在这个提交之前提交的原始哈希 ID 。

这一切都意味着,在 Git 中,提交形成了一个向后看的链。合并提交存储了两个或多个先前的提交哈希 ID,因此从合并中,我们可以沿着两条链倒退,甚至可能不止两条。在任何非空存储库中,至少还有一个提交,它不会向后指向:这是历史结束或开始的地方,具体取决于您如何看待它。但是大多数提交只存储一个哈希 ID,给我们一个简单的链:

... <-F <-G <-H

如果Hhere 代表某个链中最后一次提交的哈希 ID,并且如果我们有某种方法可以找到commit H,我们也可以找到 commit G。那是因为 commitH存储了之前 commit 的原始哈希 ID G。所以,从G中,我们可以找到 commit F,因为G存储了 的哈希 ID FF当然还存储了一个哈希 ID,等等——所以通过从 开始H,然后向后工作,一次提交一个,我们可以找到所有以 . 结尾的提交H

Git 中的分支名称只记录最后一次提交的哈希 ID。我们说分支名称指向最后一个提交,然后最后一个提交指向倒数第二个提交,它又指向一个更早的提交,依此类推。

并行开发

假设我们从某个中央服务器(例如 GitHub)克隆一些存储库。我们得到了大量的提交。我们的git clone操作实际上是通过创建一个新的空存储库,然后复制他们所有的提交,但不复制他们的分支名称。然后,在用提交填充我们存储库的提交数据库并为其分支名称创建远程跟踪名称之后,我们的 Git 会创建一个新的分支名称。

我们得到的分支名称是我们用git clone'-b选项选择的。如果我们不选择一个,我们得到的名字就是他们的Git 推荐的名字。这些天通常是这样main。有时这是他们唯一的分支名称。如果是这样,我们将获得一系列提交,以及一个远程跟踪名称origin/main

...--F--G--H   <-- origin/main

然后我们的 Git 将创建我们自己的以main匹配他们的main(然后是我们的新的):git checkoutgit switchmain

...--F--G--H   <-- main (HEAD), origin/main

我们现在可以工作并进行新的提交。无论我们做出什么新的提交,他们都会得到新的、普遍唯一的哈希 ID。让我们在我们的:上做两个新的提交: main

             I--J   <-- main (HEAD)
            /
...--F--G--H   <-- origin/main

现在让我们假设,无论如何,他们的Git 已经将两个新的提交添加到他们的 main. 这些新提交将获得新的通用唯一哈希 ID。当我们运行时git fetch origin,我们会选择新的提交:

             I--J   <-- main (HEAD)
            /
...--F--G--H
            \
             K--L   <-- origin/main

注意我们的工作和他们的工作是如何不同的。 当有并行开发时会发生这种情况。当没有并行开发时不会发生这种情况:如果他们没有获得两个新的提交,我们仍然会有我们的——我们对他们的记忆——指向 commit 。我们的新提交添加到.origin/mainmainHI-JH

如果我们没有并行开发,我们git push现在可能可以

假设我们没有任何并行开发。我们现在运行:

git push origin main

将我们的新I-J提交发送给他们,并要求他们将他们 main的指向设置为 commit J。如果他们服从,他们会得到:

...--F--G--H--I--J   <-- main

(请注意,他们没有origin/main,我们不在乎他们HEAD是什么,而不是我已经告诉过你我们HEAD的内容)。

如果我们确实有并行开发,这是一个问题

如果他们有:

...--F--G--H--K--L   <-- main

当我们运行时,在他们的存储库中git push,我们将向他们发送我们的 I-J. 但是我们的 commitI连接回 commit H。然后,我们的 Git 会要求他们设置他们 main的指向 commit J

             I--J   <-- (polite-request: set main to point here)
            /
...--F--G--H--K--L   <-- main

如果他们服从这个要求,他们将失去他们的K-L。所以他们会拒绝这个请求。我们将看到的具体错误是声称这不是快进

有可能,根据权限,2强迫他们无论如何都要服从。但是,正如脚注 1 中一样,这不是您应该做的事情,至少在您真正理解“丢失”提交的概念之前不要这样做。


2 Git作为分布式没有这种权限检查,但是大多数托管服务,例如 GitHub,都添加了它。如果您设置自己的托管服务,您也应该考虑添加它的方法。


面对并行开发,我们需要一种结合工作的方式

让我们假设,无论以何种方式,我们发现自己处于这种情况:

             I--J   <-- main (HEAD)
            /
...--F--G--H
            \
             K--L   <-- origin/main

我们现在需要的是一种将我们的工作(我们为提交提交所做的工作)I他们的工作(无论他们是谁)结合起来的方法:他们为提交提交所做的工作。JK-L

Git 有很多组合工作的方法,但我们不会在这里详细介绍。执行此操作的两种主要方法是 withgit merge和 with git rebase。因此,在git fetch导致这种分叉之后——我们和他们都有的提交——我们将需要第二个 Git 命令,可能是git mergegit rebase

第二个命令的正确选择部分是见仁见智的问题。这里没有一个普遍正确的选择。但这是什么git pull

  • 你提前选择了一个选项,甚至在你看到你是否有这种“需要组合工作”作为git fetch你即将运行的结果之前。请注意,您尚未运行 this git fetch,即使您现在正在做出此决定。

  • 然后,在决定之后,您可以git pull使用一个或两个选项运行 a,说明如何处理此问题,或者使用配置设置说明如何处理此问题,或者根本没有选项,这意味着如果需要,请使用git merge

git pull现在运行git fetch. 这将获得他们拥有的任何您没有的新提交,并更新您的远程跟踪名称。3 然后查看是否需要进行特殊的第二次联合作业。如果是这样,它将使用它来组合工作。如果没有,它只会对最新的提交执行git checkoutor ,同时还会将您当前的分支名称向前移动。4git switch


3在非常过时的 Git 版本(早于 1.8.4)中,git pull 不会更新远程跟踪名称。如果您遇到这些古老的 Git 版本之一,请注意这一点。

4这里有两点需要注意:

  • Git 称之为快进合并。这实际上不是一个合并,所以这是一个糟糕的名字。(Mercurial 只是将其称为更新。)从 Git 2.0 开始,您可以告诉git pull执行快进操作:如果需要工作组合,将执行提取,但随后会因错误而停止。这可能是从一开始就应该做的事情,也可能是最终会做的事情,但出于兼容性的原因,它今天不会这样做。git pullgit pull

    如果您确实可以选择,并且如果您喜欢git pull,我建议您使用git pull --ff-only或配置pull.ffto only, with git config pull.ff only。(我个人倾向于只是运行git fetch,然后git log或者一些类似的操作来检查,然后git merge --ff-only手动运行,但是我的习惯在 Git 2.0 之前就已经定型了。)

  • git switch命令是 Git 2.23 中的新命令。对于这种特殊情况,git switch两者之间没有真正的区别。git checkout添加新命令是因为 Git 人员发现它git checkout复杂了——它有很多模式——而且它的一些模式具有破坏性。这种破坏有时甚至会影响经验丰富的 Git 用户。(这已得到修复:自 2.23 起,git checkout这些情况下的错误现在已解决。)为了使 Git 更加用户友好,git checkout将其拆分为两个单独的命令。使用新命令是个好主意,但旧命令仍然有效,因为 Git 必须长期兼容。


概括

Push 发送提交并要求他们更新他们的分支。这要求事情最终是正确的。这不能结合并行开发。

拉取提交并让您的 Git 更新您的远程跟踪名称,然后运行第二个 Git 命令来更新您的分支。第二条命令可以结合并行开发。

您可以通过使用代替来避免立即运行第二个命令。如果您想在决定如何使用它之前查看您正在处理的内容,这将非常有用。git fetchgit pull

于 2021-03-29T08:52:33.880 回答
2

没有,回购是彼此的副本,拉和推只是方向流。与您同事方法的不同之处在于他添加了第 4 个不需要的命令。

于 2012-06-28T08:27:38.507 回答
2

是的,它正在倒退。

原则工作流程是:

  1. 在本地改变一些东西
  2. 犯罪
  3. 推送到远程目录

不推送到远程的一个用例(另一个由 Dolanor 解释)是在远程上签出工作副本(即它不是裸仓库)。当他想要推送在远程盒子上签出的分支时(例如master:master),这不会成功,因为禁止推送到签出的分支。

在我看来,这是跳到远程机器并拉而不是从本地机器推送的唯一用例。

于 2012-06-28T08:28:32.217 回答