首页 > 解决方案 > 在 Git 和 Github 中迷失了分支、起源、上游存储库和两台机器。需要指导

问题描述

第一次在这里发布问题,我希望我做对了。

我在这里遇到了一个复杂的 git 情况,涉及多台机器,一个带有分支的分叉仓库等。

我有两台计算机,一台分叉存储库,在我的 github 帐户上具有两个分支(master 和 feat),配置为“origin”,“upstream”作为属于我的组织的原始存储库。

我想以正确的方式将所有内容从分叉的仓库中提取到第二台机器中。

我做了什么:

  1. 克隆了分叉的仓库
  2. 添加上游(组织回购)作为远程回购
  3. 将所有内容从原始主机拉到本地主机
  4. 创建一个同名的本地专长分支
  5. 将所有东西从原点的专长分支拉到我当地的专长分支

这是机器#2上的情况:

  6     2 34 5
O-O-O-O-O-0-0
  \/
   O
   1

在第一台机器上,我不知道发生了什么:

      3 4 
O-O-O-O-O-0-0
  \
   O
  5,7

然而:

  1. 起源/壮举
  2. 原产地/主人
  3. 上游/主
  4. 本地/主
  5. 本地/壮举
  6. 起源/壮举
  7. 上游/壮举

由零而不是字母“o”表示的提交表示合并提交。第一个是“Merge pull request #17 from mguidoti/master”,第二个是“Merge remote-tracking branch 'origin/comments-and-docstrings' into comments-and-docstrings”,这是专长分支的实际名称.

我的问题是..我做对了吗?我想让源站和本地机器上的专长分支与主分支同步,应该在所有存储库和机器上更新。

我可以使用任何帮助或澄清。

非常感谢,并且,对于新手问题感到抱歉。

标签: gitgithub

解决方案


以下是思考这些问题的方法:

  • 每个提交的哈希 ID 都是唯一的:每个提交都有自己的,并且该提交存在于该存储库中的该哈希 ID 下,或者该提交根本不存在于该存储库中。 每个Git 都同意该提交获得哈希 ID,无论所讨论的 Git 是否具有该提交。他们都以某种方式提前安排了这一点(通过加密哈希技巧)。

  • Gits按名称查找提交:分支名称,此特定 Git 存储库的本地名称,或任何其他名称。每个存储库都维护自己的一组名称。任何名称都只包含一个哈希 ID。

  • Gits通过提交哈希 ID 在同一原始 Git 存储库的不同克隆之间 共享提交。

  • 当 Git 共享时,一个 Git 发送提交,另一个接收。也就是说,您选择一个 Git 存储库并运行或. 这让这个 Git 存储库在名称中存储的 URL 处调用另一个 Git 存储库。git fetch remotegit push remote branchremote

远程名称是一个简单的字符串,例如originor upstream。每个 Git 都有自己的远程名称。

fetch 和 push 的传输机制非常相似,在过程结束时有很大的不同——当然方向也不同。使用git fetch,您将“您的”Git 连接到“他们的”Git 并获取(接收)提交。使用git push,您将自己的连接到他们的,然后发送提交。让我们专注于fetch侧面,因为这里整体更有趣。

还要记住,每个提交本身都会存储其前一个(或级)提交的原始哈希 ID。这就是链接一起提交的内容。当我们像这样画一个分支时:

A--B--C   <--master

我们真正拥有的是分支名称 master包含我们C为了方便而在这里调用的提交的原始哈希 ID。然后C它自己持有 commit 的 hash ID B,它持有 commit 的 hash ID A。因此,我们总是可以通过从最后一个提交(例如存储在分支名称中的 ID)开始并向后工作来找到所有提交。Git 总是需要逆向工作。

所以:当你运行时git fetch origin,你的 Git 调用另一个 Git,使用存储在 name 下的 URL origin他们的Git 列出了他们所有的分支名称和哈希 ID。由于哈希 ID 在所有 Git 存储库中共享,因此您的 Git 可以快速检查每个存储库并查看您是否有该提交。如果你这样做了,你的 Git 说我有哈希 ID H,但如果没有,你的 Git 说我想要哈希 ID H

如果您想要提交,他们的 Git 会为您提供其父级。你的 Git 检查:我有这些 ID 吗? 如果没有,你的 Git 需要它们。如果你已经有了它们,你的 Git 会说不,谢谢,我有那个。 在这个过程结束时,你的 Git 和他们的 Git 有一个他们将发送给你的所有提交的列表,并且他们知道你已经拥有的提交(以及你已经拥有的文件版本)。因此,他们向您发送了您需要的提交,而您已经拥有了其余的,现在您在他们的分支中拥有了您没有的任何提交,以及您已经拥有的所有内容。

换句话说,如果你有:

A--B--C   <-- master

你现在可能有:

A--B--C   <-- master
       \
        D--E

他们确定E的提交在哪里,的父级是,并且的父级是。但是 Git 只能从末端向后查找提交。您需要一些名称来查找 commit 。 masterEDDCE

嗯,他们的名字master,,,差不多就够了。但它会自动与您的姓名冲突master。所以你的 Git 会重命名它们的分支名称。你的 Git 将它们master变成你的origin/master. 这origin/部分来自您以 . 名称调用他们的 Git 的事实origin。这些origin/master样式名称是远程跟踪名称。(Git 称它们为远程跟踪分支名称,但我认为最好将分支一词从这个短语中剔除——它只会把它弄乱并削弱已经过度加载的词分支。)

在此特定git fetch之后,您的 Git 将创建或更新您origin/master以指向新获得的提交E

A--B--C   <-- master
       \
        D--E   <-- origin/master

如果你运行git fetch upstream并调用一些名为 的 Git upstream,它们会有一些提交。例如,也许他们有 commitD作为他们的 master. 但是你现在有D,所以你告诉他们:不,谢谢,已经有了D 如果这是列表的末尾,您的 Git 现在会创建您的to upstream/mastermatch :upstreammaster

A--B--C   <-- master
       \
        D   <-- upstream/master
         \
          E   <-- origin/master

请注意,每个 Git 都有自己的一组名称。他们有他们的分支名称,你有你的分支名称,你的 Git 将他们的分支名称重命名为你的远程跟踪名称。您的远程跟踪名称是通过将您的远程名称origin或粘贴在其分支upstream名称前面来构建的(加上斜线以防止它们一起运行)。

如果您希望他们(无论他们是谁,这些存储库位于何处)更改分支名称,那么您可以这样做。 你如何做到这一点因人而异。最简单的方法是git push,但git push需要您有权在这些存储库上写入。

如果您无权在他们的存储库上写入,您将需要他们(无论他们是谁)授予您权限或自行进行更新。这就是分叉的用武之地,GitHub 和其他提供商。分叉本质上是一个克隆,但提供者保存了一些额外的链接。如果您有一个无法写入的存储库,则可以将其分叉到您自己的副本中。你可以写在这个副本上。因此,现在您在 GitHub(或其他提供者)上有一个可以写入的存储库。

您可以git fetch从原始存储库(大多数人称之为这个存储库)中访问它upstream,但您不能git push访问它。您可以git fetch从您的 fork-clone(您可能会调用upstream)中复制到此副本。最后,您在计算机上(而不是在 GitHub 上)拥有自己的Git 存储库。最后一个有你的分支,以及所有那些远程跟踪名称。 git push

现在我们可以回到git push. 这个命令与 Git 的对立面一样接近fetch(反之亦然)。你运行,例如:

git push origin master

并且你的 Git 从源头调用 Git——例如你在 GitHub 上的 fork——并向 GitHub 提供他们需要但没有的任何提交,就像 fetch 向你提供你需要但没有的任何提交一样有。但是一旦你给了他们这些提交,剩下的就不一样了。

他们的 Git——你的 GitHub 存储库,GitHub 为你管理的,你正在调用的 GitHub 存储库origin——有自己的分支名称。(这些名称最初是从您分叉的存储库中复制的。1)所以也许他们有:

A--B--C--D--E   <-- master

而您,在您的存储库中,已将您的存储库重新排列为以这种方式阅读:

A--B--C--D--E   <-- origin/master
             \
              F   <-- master

因此,您将向他们发送 commit F,这是他们需要的,但随后要求他们将 master设置为直接指向F. 他们没有设置远程跟踪名称!他们设置了一个分支名称。

现在,在这种情况下,他们有点像你。此存储库位于GitHub 上,但 GitHub 正在为管理它。尽管如此,这就是您必须执行此步骤的方式:F例如,您从笔记本电脑上的 Git 发送提交。然后,您要求 GitHub 的 Git-for-you 存储库将 master设置为指向这个新的提交F。由于您被允许这样做,并且它满足推送的所有其他要求,因此他们会这样做。现在他们A-B-C-D-E-F链,他们他们的 master指向F

您的Git 在您的笔记本电脑上看到他们——<code>origin——接受了更新他们的master. 所以你的 Git 现在更新你的origin/master,现在你有:

A--B--C--D--E--F   <-- master, origin/master

如果您的Git 存储库中有upstream/master标签,则此处不会发生任何事情。不过,您可以随时运行,让您的 Git 调用他们的 Git(GitHub 上不属于您的 Git),然后查看他们是否移动了他们的分支名称和/或有任何您没有的新提交。git fetch upstream

如果他们确实有新的提交,您可以选择它们。此时,您可以对它们做任何您想做的事情,包括将它们发送到origin. 但是,当然,要做到这一点,您必须设置origin' 分支名称以指向这些提交。你会用git push origin. 这是它变得有点棘手的地方。 (也就是说,之前的所有东西都很容易!至少,相比之下。)


1 GitHub 的克隆过程与您自己的有点不同:当您克隆存储库时,您的 Git 将其分支名称重命名为您的远程跟踪名称,然后在您的克隆中只创建一个分支名称。当您使用 GitHub 的“fork a repository”按钮时,它们会克隆另一个存储库,但在此克隆中创建的分支名称与您刚刚 fork 的存储库中的分支名称完全相同。目前,这些名称都拥有与分叉存储库相同的哈希 ID。但由于这是一个克隆,这些名称现在对于这个分支是私有的:其他人对原始名称所做的任何更改都不会在此处反映出来。


如何从origin/master更新upstream/master

假设您最初 fork 他们的存储库,然后克隆您的 fork 并添加upstream到其中,并且所有三个都是同步的。你在本地 Git 存储库中有这个:

A--B--C   <-- master, origin/master, upstream/master

现在upstream做一个新的提交,我们称之为D. 你跑git fetch upstream起来捡起来:

A--B--C   <-- master, origin/master
       \
        D   <-- upstream/master

您现在可以将您的masterto 指向D,然后git push origin master。那将发送Dorigin请求origin说明。masterD

但是,如果您还没有准备好这样做,无论出于何种原因,该怎么办?好吧,git push origin master您可以运行:

git push origin upstream/master:master

这里冒号左边的名字:是 your upstream/master,它标识了 commit D。这就是他们——<code>origin——需要的提交。您的 Git 将此提交发送到他们的 Git,然后将其添加到他们的 ( origin's) 存储库中。然后你的 Git 要求他们的 Git 将名称设置在冒号的右侧:你的 Git 要求他们 ,origin他们 master的指向设置为 commit D

如果这一切正常,你的 Git 现在有:

A--B--C   <-- master
       \
        D   <-- upstream/master, origin/master

它代表了所有三个存储库的状态:每个人都有所有四个提交,调用第三个master,他们-<code>origin 和--upstream调用第四个master

当你准备好后,你也可以移动你master的指向来提交D

何时移动每个名称取决于您。他们是你的名字!即使是远程跟踪名称也是您的,但只有让您的 Git 根据git fetchorgit push操作的结果移动它们才有意义,因为这些名称的全部意义在于记住它们的存储库中的内容。

同样,这里的关键是共享的提交。这些名称仅对查找提交有用。特别是,分支名称会找到属于该分支的最后一个提交。因此,分支名称会随着时间的推移而移动。通常,他们移动以便他们有更多的提交。可以通过强制将名称“向后”拉出,这样就很难找到以后的提交。一些操作,例如git commit --amend,进行一个新的提交,然后从侧面拉出一个名称,就像这样:

          I   <-- master (HEAD)
         /
...--G--H   <-- origin/master

此时您可能会认为提交I是错误的,并使用git commit --amend它来修复它。这并没有——<em>不能——实际上改变I。普遍认可的哈希 ID 技巧不会让这种情况发生。所以你的 Git 只是做了一个新的和改进的提交,我们可以调用它J或者I'表明它是新的和改进的I. 您的 Git 没有I'回溯到,而是回溯到:II'H

          I   [abandoned]
         /
...--G--H   <-- origin/master
         \
          I'  <-- master (HEAD)

提交I实际上并没有消失——至少暂时不会。2 但是你的名字master被拉到一边,好像是为了指向新的和改进的I'。只要您从未在任何地方发送过提交I或向任何人展示其哈希 ID,其他人甚至都不知道您这样做了。他们没有看到它发生,你没有向他们展示,他们也不必关心。

但是,如果您发送了其他commit I,他们可能会通过他们的某些名字找到它。这是事情变得艰难的时候。你可以要求他们,不管他们是谁,扔掉错误的提交。如果您控制存储库,就像您origin在 GitHub 上的分叉一样,您可以git push --force丢弃错误的提交。不过,一般来说,这是一条要避免的道路:至少充满烦恼,偶尔还会有更严重的痛苦。在具体情况下,当大家都事先同意这一切的时候,烦恼是轻微的,没关系。

使用git rebase会导致同样令人讨厌的情况:如果你们都事先同意它,或者如果其他人还没有看到这些提交,那很好。否则,由您决定是否值得进行新的和改进的提交。

最后,还有pull requests,它是特定于主机提供者的东西。这些不是 Git 的一部分!您可以通过将提交发送到您的 fork,然后使用可点击的 Web 界面按钮让 GitHub 将这些提交发送给您分叉的存储库的所有者,要求他们(人类)合并这些提交来执行这些操作。您在此过程中发送的提交必须在您的fork 中,因此您通常按以下顺序执行此操作:git push origin ...然后使用 Web 界面。

如果当您调用的管理 GitHub 存储库的人upstream 接受您的提交时,您git fetch upstream将看到您的提交(及其原始哈希 ID)并更新您的upstream/*远程跟踪名称。或者,他们可能不会按原样接受您的提交:他们可能会制作自己的修改版本,具有不同的哈希 ID。这至少有点烦人,就像整个修改或变基的情况一样:您现在可能不得不放弃原始提交并在其副本上重建任何新工作。


2从某个名称开始并向后工作无法达到的提交最终会被死神收集器收割,git gc.


推荐阅读