是否可以在不污染之前拉取请求的情况下提交分叉?如果不是,如何将主分支变成分支?

Jim*_*ris 0 git branch github pull-request git-fork

参考:大约 9 年前的以下问题:
\n不分叉就拉取请求?

\n

背景:
\n我正在关注 GitHub/Git,并且遇到了问题。\xc2\xa0 我认真地搜索过,但没有找到任何可以解决这个特定问题的东西 - 我发现的最接近的是上面提到的问题。

\n

问题:
\n我“分叉”了一个存储库,打算做一些工作,对我自己的分叉进行更改,然后创建一个返回到原始项目的拉取请求,作为对其做出贡献的一种方式。

\n

我终于想通了,并能够成功创建包含我提议的更改的拉取请求。

\n

请注意,我还想做一些其他事情来为这个项目做出贡献,在创建拉取请求后,我继续工作并对本地副本进行了额外的提交,包括导入一些技术文档等。

\n

显然,出于某种未知的原因,在我发出拉取请求后,拉取请求“拥有”我的原始存储库的分叉,此后我所做的任何事情都将成为该拉取请求的一部分- 无论它是否是相关与否,我是否将其推送到项目的分支,是否将其添加到 PR 中,或者其他什么。\xc2\xa0 它看起来就像魔法一样,并且只有在我删除/恢复我自己的存储库分支中的更改。

\n

这是否意味着与该项目有关的所有工作都必须完全停止,直到 PR 被接受和/或拒绝?\xc2\xa0 如果是这样,其他人,尤其是一家使用单一代码库的公司能够成功完成工作吗?

\n

当然,我确信这是可能的,人们一直这样做。

\n

我所做的研究尚未披露任何似乎可以解决这个特定问题的内容,但是不同问题的其他答案似乎暗示了这样一个事实:一旦您分叉了一个存储库并创建了一个拉取请求,该拉取请求似乎确实拥有” “您本地存储库的实例 - 缓解这种情况的唯一方法是:

\n
    \n
  • 分叉仓库。
  • \n
  • 创建存储库的整个分支并进行工作。
  • \n
  • 提交到该分支并创建拉取请求,然后放弃该分支
  • \n
\n

要完成额外的工作,无论在项目的哪个阶段,您都必须:

\n
    \n
  • 创建一个全新的分支
  • \n
  • 做任何你想做的、应该与原始工作分开的工作。
  • \n
  • 提交到新分支,创建拉取请求,然后放弃分支。
  • \n
\n

对于你想做的任何额外工作,“冲洗并重复”,最终得到一个比圣诞树有更多树枝的叉子。

\n

这引发了几个问题:

\n
    \n
  1. 这是真的吗?\xc2\xa0 我理解正确吗?
  2. \n
  3. 为什么?\xc2\xa0 这似乎不必要地复杂和令人费解,尤其是对于单个贡献者而言。
  4. \n
\n

最后也是最重要的问题:

\n

3. 如何清理我的本地副本?\xc2\xa0 显然我应该克隆存储库,然后创建一个分支来工作,然后创建拉取请求。\xc2\xa0 (即没有办法获取我更新的“ main”,将其转换为分支,然后重新创建原始主分支,以便我可以创建其他分支来执行其他工作?)

\n

我犹豫是否要“破解”现有的存储库,试图解决问题,因为我不想污染原始的拉取请求或把上游项目的事情搞砸。

\n

谢谢!

\n

tor*_*rek 5

注意:这很长,但你确实需要了解这些事情。我已经用完了空间(字符数限制为 30k),所以我将把它分成两个单独的答案。 第 2 部分在这里第 3 部分在这里

\n

虽然“拉取请求”不是 Git 的一部分(它们特定于 Git Hub \xe2\x80\x89 1),但即使不专门提及 GitHub,我们也可以对它们说一些话。然后我们可以稍后插入 GitHub 特定的项目。那么让我们从这个开始:

\n
    \n
  • Git 就是关于提交。虽然 Git 提交包含文件,但 Git 真正关注的并不是文件,而是提交。而且,虽然我们使用分支名称来查找提交,但 Git 也不是真正与分支名称有关:它实际上只是与提交有关。

    \n
  • \n
  • 这意味着您需要了解有关提交的所有信息:提交是什么以及每个提交以及连续的一串提交可以为您做什么。

    \n
  • \n
\n

因此,我们将从快速概述提交开始,然后连续查看其中的字符串。

\n
\n

1 Bitbucket 也有“拉取请求”,但它们略有不同,GitLab 有“合并请求”,它们又相同但又不同。所有这些都建立在 Git 本身的相同基础支持之上。

\n
\n

提交

\n

每个 Git 提交都有编号。不过,这些数字并不是简单的连续计数:我们没有提交 #1,然后是 #2 和 #3,依此类推。相反,每次提交都会在所有存储库中获得唯一的哈希 ID \xe2\x80\x94 ,即使它们与您的存储库根本无关。 2 \xe2\x80\x94看起来是随机的,但事实并非如此。3 哈希 ID 又大又丑,而且人类无法使用:计算机可以处理它们,但我们脆弱的大脑会变得混乱。因此,下面我将使用假哈希 ID,其中我仅使用一个大写字母来代表真正的哈希 ID。请注意,为了使这些哈希 ID 发挥作用,提交的每个部分都必须是完全只读的。也就是说,一旦您进行了新的提交,该提交就会永远冻结。该特定哈希 ID,无论它获得什么哈希 ID,都是用于提交的,并且过去、现在或将来的其他提交\xe2\x80\x94 都不能使用该哈希 ID。

\n

无论如何,每个 Git 提交都会存储两件事:

\n
    \n
  1. 提交存储每个文件的完整快照(无论如何,Git 在您或任何人创建它时就知道)。为了防止存储库变得过大,这些文件经过 (a) 压缩和 (b)重复数据删除。因此,它们以只有 Git 可以读取的格式存储,任何东西(甚至 Git 本身)都无法覆盖。正如我们将看到的,这解决了一些问题,但也产生了一个大问题。

    \n
  2. \n
  3. 提交还存储一些元数据或有关提交本身的信息。例如,这包括提交者的姓名和电子邮件地址(来自他们的user.name设置user.email,他们可以随时更改,因此未经验证就不可靠,但它仍然有用)。它包括一条日志消息:当您为自己的提交提供一条日志消息时,您应该写下关于为何进行提交的解释。 你所做的\xe2\x80\x94例如将7的一个实例更改为14\xe2\x80\x94是Git可以自行显示的内容,但是你为什么将7更改为14呢?是从几周到两周,还是因为七个小矮人都被克隆了?

    \n
  4. \n
\n

在提交的元数据中,Git 出于自身目的添加了先前提交的原始哈希 ID 列表。这个列表通常只有一个元素长:对于合并提交(我们不会在这里介绍),它有两个元素长,并且任何非空存储库中至少有一个提交是第一个提交,其中没有任何先前的提交,因此该列表为空。

\n
\n

2这就是为什么哈希 ID 必须又大又丑的原因。严格来说,它们在两个永远不会相遇的存储库中不必是唯一的,但 Git 不知道两个存储库将来是否或何时会相遇,以及两个不同的提交是否具有相同的内容那时,哈希 ID就会发生不好的事情。我将这样的提交称为Doppelg\xc3\xa4nger,一种邪恶的双胞胎,是灾难的预兆。实际的灾难是\xe2\x80\x94或者至少应该是\xe2\x80\x94只是这两个Git存储库的会议失败了。在一些非常旧的 Git 版本中,由于错误,实际上确实发生了更糟糕的事情。无论如何,这种情况根本不应该发生,而哈希值的大小有助于避免这种情况。

\n

3当前哈希值是提交中所有数据的 SHA-1 校验和,其中包括有关提交之前的提交的数据,因此它是截至该点的整个历史记录的校验和。 SHA-1 不再具有加密安全性。尽管这本身并没有破坏 Git,但 Git正在转向 SHA-256。

\n
\n

提交链

\n

鉴于上述情况,我们可以在一个很小的三提交存储库中绘制三个提交,如下所示:

\n
A <-B <-C\n
Run Code Online (Sandbox Code Playgroud)\n

提交C是我们迄今为止的第三次也是最新的提交。它有一些看起来随机的哈希 ID 和所有文件的快照。中的一两个文件可能 C与早期提交中的所有文件不同B,其余文件与早期提交中的相同B,因此实际上与早期提交中共享B。所以它们不占用任何实际空间。修改后的文件确实占用一些空间,但它们被压缩\xe2\x80\x94有时非常压缩\xe2\x80\x94并且可能几乎不占用任何空间。提交元数据的空间很小(顺便说一句,元数据也被压缩),但总的来说,每个文件的完整快照可能不会占用太多空间。

\n

同时, commitC包含早期 commit 的原始哈希 ID B。我们说C 指向 B. 这意味着如果 Git 可以找到 C\xe2\x80\x94,我们很快就会看到它如何做到这一点\xe2\x80\x94Git 也可以使用哈希 ID C查找B然后,Git 可以从两次提交中提取两个快照中的所有文件,并对它们进行比较。比较文件的结果是diff:将文件 in 更改B为 files in 的说明C(反之亦然,如果您以其他顺序完成 diff)。

\n

Git 和 GitHub 等网站通常会将提交显示为差异,因为这通常比显示原始快照更有用。但如果您愿意,您可以轻松获取快照:对于 Git 来说,这有时比获取差异更容易。(由于重复数据删除技巧,git diff可以快速跳过相同的文件,但它仍然必须查看两次提交,而不仅仅是一次。因此,关于哪个更容易,这是一种混合。)

\n

CommitB是一次提交,具有快照和元数据,并向后指向更早的提交A。但提交A第一次提交,因此它的元数据不会列出任何较早的提交。这意味着根据定义,其快照中的所有文件都是新的。(它们会针对任何其他提交中的任何文件进行压缩和重复数据删除,但当时,这是第一次提交,因此它们仅针对自身进行压缩和重复数据删除。这最后意味着如果第一个提交commit 包含一个大文件的 100 个相同副本,实际上 commit 中只有一个副本A。)

\n

分公司名称及其他名称

\n

Git 需要一种快速的方法来查找某个链中的最后一次提交。Git 可以强迫我们\xe2\x80\x94 使用 Git\xe2\x80\x94 的人类记下最后一次提交的哈希 ID,在本例中为C。我们可以将其保存在纸上、白板或其他东西上。但这很愚蠢:我们有一台计算机。为什么不让计算机将这些哈希 ID 保存在文件或其他文件中?事实上,为什么不让Git我们保存最新的哈希 ID呢?

\n

这正是分支名称的含义:保存最新提交的哈希 ID 的位置。Git 只需要最新的一个,因为最新的指向第二个最新的,第二个最新的指向更早的,依此类推。这会尽可能长时间地进行,仅在没有更早的提交时结束这就是 Git 的工作方式:它从我们告诉它的提交开始 \xe2\x80\x94 通常通过分支名称 \xe2\x80\x94 和向后工作。

\n

让我们绘制一个以哈希 ID H(对于哈希)结尾的简单提交链,并将分支名称main指向(包含的哈希 ID)H

\n
...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

现在让我们添加一个新名称,例如feature1。该名称必须指向某个现有的提交。我们可以选择G、 或H或一些较早的提交,但选择它似乎很自然,H因为它是我们最新的:

\n
...--G--H   <-- feature, main\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,Git 有很多种名称\xe2\x80\x94,而不仅仅是分支名称\xe2\x80\x94,而且它们都执行此类操作,即指向提交。因此我们可以创建一个指向 commit 的标签H,例如:

\n
...--G--H   <-- feature, main, tag: v1.0\n
Run Code Online (Sandbox Code Playgroud)\n

不过,大多数情况下,我们只会使用分支名称,这就是我现在在这里显示的全部内容。

\n

在分支机构工作

\n

Git 有它自己的特殊功能来让我们工作。正如我们之前提到的,提交快照的内容会一直冻结,并且只能由 Git 本身读取。因此,我们实际上无法处理提交中包含的这些文件。我们必须让 Git 将文件提取到某个地方。这个“某处”就是我们的工作树工作树

\n

Git 还有一个非常重要的东西,Git 给出了三个名称:索引暂存区,有时还有缓存。我们不会在这里介绍这一点,但要注意的是,当您运行时git commit,Git 实际上是从 Git 索引/暂存区域中的文件而不是从工作树中的文件 进行新提交。所有要提交的文件都必须位于暂存区:这些是 Git 知道的文件。提取提交会将提交的文件复制暂存区域以及工作树,以便它们可以从那里开始。

\n

无论如何,一旦文件位于您的工作树中,它们就只是您计算机上的普通文件。它们不再存在于 Git 中。它们来自Git(来自提交),您可以稍后在新提交中将它们放回到Git 中,但是当您执行工作时,您会处理不在Git 中的文件。Git 中只有已提交的文件。

\n

您可以使用工作树文件进行工作并git add照常运行。(这会将您列出的文件的工作树版本复制回索引中,以便它们准备好提交。在这个阶段,git addGit 会进行初始压缩和重复数据删除。如图所示的文件换句话说,Git 索引中的内容是预先去重的。这意味着索引的副本大部分不占用空间,除了您更改和添加的任何文件。您可以添加未更改的文件:这只是轻微的时间浪费,因为 Git 会发现它是重复的,而只保留原始文件。这是浪费廉价的计算机时间,而不是宝贵的人类时间,所以随意浪费它!但如果您知道某些文件很大并且这也会浪费您的时间,请随意跳过它。)

\n

无论如何,现在您的新提交已准备就绪,您可以运行git commit. 这:

\n
    \n
  • 收集任何必要的元数据,例如您的姓名和电子邮件地址以及当前日期和时间;
  • \n
  • 获取当前提交\xe2\x80\x94的哈希 ID ,您之前签出该提交来填充工作树(和 Git\ 的索引);
  • \n
  • 冻结索引的快照;和
  • \n
  • 将所有这些写出作为新的提交,从而获得一个新的、唯一的哈希 ID。
  • \n
\n

如果你有:

\n
...--G--H   <-- feature, main\n
Run Code Online (Sandbox Code Playgroud)\n

就在刚才,你当前的提交是H,所以你的新提交\xe2\x80\x94(我们称之为I\xe2\x80\x94)指向H

\n
          I\n         /\n...--G--H\n
Run Code Online (Sandbox Code Playgroud)\n

然而,Git 确实需要知道您使用哪个分支名称来查找 H. 所以这两个名字之一有一个特殊的名字HEAD“附加到它”。假设这个名字过去是,现在仍然是 feature。那么我们的绘图现在看起来像这样:

\n
          I   <-- feature (HEAD)\n         /\n...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

也就是说,Git过去HEAD是查找名称feature,首先查找哈希ID H,现在将新的哈希IDI 写入 feature

\n

这样做的效果是,当前分支名称(无论它是什么)现在都指向您刚刚所做的新提交。(请注意,快照I使用了索引/暂存区域,您对其进行了更新以匹配您的工作树,因此现在所有三个都匹配,就像您从“干净”结帐或开始时一样git switch。)如果您创建另一个新的使用通常的修改文件添加并提交过程提交,您将得到:

\n
          I--J   <-- feature (HEAD)\n         /\n...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

如果你现在git switch maingit checkout main,Git 所做的是:

\n
    \n
  • 删除所有 commit-J文件并用 commit-H文件替换它们;和
  • \n
  • 将特殊名称附加HEADmain.
  • \n
\n

您现在拥有:

\n
          I--J   <-- feature\n         /\n...--G--H   <-- main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

on branch main正如会说的,您git status的工作树和暂存区域是“干净的”(与提交匹配H),您更新的文件将永久安全保存\xe2\x80\x94,或者只要提交本身持续\xe2\x80\ x94in commit J,您可以使用名称找到它feature

\n

如果您愿意,您现在可以创建一个新分支,例如feature2,并切换到它(使用git branchgit switch,或组合起来git switch -c一次完成所有操作):

\n
          I--J   <-- feature\n         /\n...--G--H   <-- feature2 (HEAD), main\n
Run Code Online (Sandbox Code Playgroud)\n

当您在此新分支上进行新提交时,分支名称会自动更新以指向最新提交:

\n
          I--J   <-- feature\n         /\n...--G--H   <-- main\n         \\\n          K--L   <-- feature2 (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

H请注意,用 Git 的术语来说,向上提交并包括提交是在所有三个分支上进行的。I-J目前,提交feature,并且提交K-L仅在 上。Commit是上的最新提交,尽管它不是有史以来的最新提交(此时是存储库的提交)。此外,提交和之间没有直接关系:可以说它们只是表兄弟。他们是共同祖父母的孩子。feature2HmainLJLH

\n

合并

\n

为了了解将会发生什么,我们现在需要看看合并的常见情况。Git 有一个简单案例的快捷方式,但由于各种原因(有些好,有些不太好),特别是 GitHub 从不使用这个快捷方式。无论如何,一旦您了解了更一般的情况,简单的情况就更容易理解。

\n

在 Git 中,使用git merge就是结合工作。让我们绘制两个特征分支,而不绘制名称 main它可能仍然存在,只是不符合我想要绘制的方式)。我们feature先切换到分支:

\n
          I--J   <-- feature (HEAD)\n         /\n...--G--H\n         \\\n          K--L   <-- feature2\n
Run Code Online (Sandbox Code Playgroud)\n

我们当前的提交是 now J,我们现在会J在工作树中找到文件。我们现在运行git merge feature2, 并且git merge

\n
    \n
  • 找到提交J(简单:只需阅读HEAD然后feature);
  • \n
  • 定位提交L(也很简单:feature2包含正确的哈希 ID);
  • \n
  • 找到最佳的公共起点提交。
  • \n
\n

最后一部分可能很难,尽管在这里很容易看出这是 commit :和H的祖父。如果 Git 现在将中的快照中的快照进行比较,Git 将生成一个配方,其中包含您在 中所做的所有工作:JLHJfeature

\n
git diff --find-renames <hash-of-H> <hash-of-J>   # what "we" did\n
Run Code Online (Sandbox Code Playgroud)\n

通过从to运行第二个diff ,Git 将生成一个配方,其中包含在 上完成的所有工作:HLfeature2

\n
git diff --find-renames <hash-of-H> <hash-of-J>   # what "they" did\n
Run Code Online (Sandbox Code Playgroud)\n

此时,谁做了哪些工作并不重要:唯一重要的是“我们”更改了哪些文件,“他们”更改了哪些文件,以及我们对每个文件进行了哪些更改。两人git diff想通了这一点。

\n

如果 Git 可以自行组合这两组更改,则它可以将组合的更改应用到. 无论您喜欢如何看待它,这要么保留我们的更改并添加他们的更改,要么将这两个更改添加在一起,或者其他什么。Git 假设最终结果是存储在新提交中的正确快照H

\n

如果 Git无法自行合并这些更改,Git 将在合并过程中因合并冲突而停止。程序员现在必须得出正确的结果。我们将直接跳过这一部分。我们假设 Git 自己得出了正确的结果。在这种情况下,git merge它会继续git commit为你奔跑。

\n

通常,生成的提交M会将 commitJ作为其父级。我们的新合并提交 实际上确实有作为J\xe2\x80\x94第一个父级\xe2\x80\x94,而且还具有 commit (L我们在命令行上命名的提交)git merge作为其第二个父级,如下所示:

\n
          I--J\n         /    \\\n...--G--H      M   <-- feature (HEAD)\n         \\    /\n          K--L   <-- feature2\n
Run Code Online (Sandbox Code Playgroud)\n

所附加的 name 像往常一样移动以指向新的提交。但由于向后指向,提交现在“在”分支上。这意味着所有提交都在,同时仍然结束并且包含提交。featureHEADMM J LK-LfeatureMfeaturefeature2LI-J

\n

如果我们愿意的话,我们现在可以删除该名称feature2:只有L直接查找才有用,如果我们觉得不需要L直接查找,则可以通过查看 的第二个父级来找到它M,无论何时我们关心。如果我们现在想添加更多提交feature2,我们应该保留该名称并执行以下操作:

\n
          I--J\n         /    \\\n...--G--H      M   <-- feature\n         \\    /\n          K--L--N--O   <-- feature2 (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们愿意的话,我们现在可以再次合并feature2feature

\n
          I--J\n         /    \\\n...--G--H      M-----P   <-- feature (HEAD)\n         \\    /     /\n          K--L--N--O   <-- feature2\n
Run Code Online (Sandbox Code Playgroud)\n

制作一种鸭子的头部图片,尽管我们也可以重新绘制它,而不要在顶行上出现肿块:

\n
...--G--H--I--J--M-----P   <-- feature (HEAD)\n         \\      /     /\n          K----L--N--O   <-- feature2\n
Run Code Online (Sandbox Code Playgroud)\n

(不确定这个是什么样子)。

\n

快进

\n

Git 的特殊快捷方式适用git merge于这样的情况:

\n
...--D--E   <-- main (HEAD)\n         \\\n          F--G   <-- bugfix\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们运行git merge bugfix,Git 将找到提交EG,然后找到和 的合并基础:两个分支上的最佳提交。但这是提交本身,即当前提交EGE

\n

Git可以继续E对自身进行比较,以发现没有变化。然后它可以E通过差异G来找到他们的变化。然后它将应用这些更改 E提出一个新的提交H,并给它两个父项:

\n
...--D--E------H   <-- main (HEAD)\n         \\    /\n          F--G   <-- bugfix\n
Run Code Online (Sandbox Code Playgroud)\n

提交H将是一个合并提交,有两个父级,就像“真正的合并”情况一样。但显然E,与自身比较是愚蠢的,添加他们的更改只会让我们得到一个提交H,其快照与他们的提交中的快照完全匹配G。因此,在这种情况下,除非我们告诉它,否则 Git 根本不会费心进行合并。

\n

相反,Git 会执行所谓的快进合并。这意味着 Git 只是G直接检查提交,同时向前拖动当前分支名称:

\n
...--D--E\n         \\\n          F--G   <-- bugfix, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

现在根本没有理由在图表中绘制扭结:

\n
...--D--E--F--G   <-- bugfix, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

删除这个名字bugfix显然是足够安全的,尽管main稍后可能会进一步推进。

\n

为了抑制快进而不是合并的事情,我们将运行git merge --no-ff. GitHub 实际上总是这样做,因此您不会看到GitHub发生快进合并;但了解它们还是有好处的。

\n

何时删除姓名

\n

何时以及是否删除其他分支名称由用户决定。请注意,删除名称并不会删除提交:它只会使查找它们变得更加困难。但还有一件事需要知道。假设我们有:

\n
...--G--H   <-- main\n         \\\n          I--J   <-- bugfix (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

where 提交IJ实际上不起作用。你将运行:

\n
git switch main\ngit branch -d --force bugfix\n
Run Code Online (Sandbox Code Playgroud)\n

放弃修复错误的尝试。这给你留下:

\n
...--G--H   <-- main\n         \\\n          I--J   ???\n
Run Code Online (Sandbox Code Playgroud)\n

提交I-J 仍然存在,但除非您记下J哈希 ID,否则您可能永远无法再次找到提交J

\n

Git 将\xe2\x80\x94 最终\xe2\x80\x94 检测到提交J无法访问(你无法找到它),并将真正删除它。I一旦J消失,提交也是如此。您将获得一个宽限期(通常至少为 30 天),在此期间 Git不会执行此操作,并且提供各种 Git 命令来帮助查找意外丢失的提交。但是,如果您不费心找到它们并重新添加名称,Git 用来跟踪“丢失”提交的“reflog 条目”最终会过期,然后当 Git 开始执行其操作时,\xe2\x80\x94维护和清洁工作\xe2\x80\x94“丢失”的提交将真正从此存储库中消失。因此,虽然提交是只读的,但它们只是“大部分是永久性的”。只要您能找到它们,它们就会保留在您的存储库中(然后会更长一点)。

\n

克隆、远程和多个存储库

\n

Git 不仅仅是一个版本控制系统(VCS);它还是一个版本控制系统(VCS)。它是分布式VCS (DVCS)。Git 进行此分发的方式是允许 \xe2\x80\x94 或者更确切地说,强烈鼓励\xe2\x80\x94 存储库的许多副本存在。因此,Git存储库是:

\n
    \n
  • 提交和其他 Git 对象的集合,其中部分或全部也可能位于其他存储库中;和
  • \n
  • 名称的集合,例如分支名称和标记名称,可帮助您(和 Git)查找提交和其他内部对象。
  • \n
\n

它们存储为两个简单的键值数据库。名称数据库中的键是分支名称(例如)refs/heads/main、标签名称(例如refs/tags/v1.2)以及许多其他类型的名称。每个名称都位于下的命名空间refs/中。每个名称仅存储一个哈希 ID。

\n

对象数据库中的键是哈希 ID。该数据库中的每个对象都有一些 Git 内部对象类型(提交、树、blob 或带注释的标签)。提交对象以及支持树和 blob 对象最终存储您的文件;您将主要只处理提交,通常不必太关心这些细节。

\n

由于提交哈希 ID 是全局唯一的,因此某个存储库的克隆的对象数据库键与同一存储库的每个其他克隆中的键相同。当您克隆存储库时,您将获得其全部或几乎全部提交和支持对象。但是您克隆中的名称数据库与他们的完全分开。

\n

这意味着存储库的克隆一开始根本没有分支名称。你跑:

\n
git clone <url>\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
git clone -b <branch> <url>\n
Run Code Online (Sandbox Code Playgroud)\n

然后你的 Git 软件会创建一个新的、完全空的 Git 存储库来启动。你的 Git 软件,使用你的 Git 存储库(我喜欢将其缩写为“你的 Git”)调用他们的Git 软件并将其指向他们的Git 存储库(“他们的 Git”)。他们的 Git 列出了所有分支和标签以及其他名称以及与之相关的哈希 ID,然后您的 Git 会询问它想要复制的对象(通常是所有对象)。对于您将要获得的每个提交,其 Git 有义务提供该提交的所有父项以及父项的父项,依此类推。所以你最终将每个提交复制到你的 Git 中。

\n

现在您已拥有所有提交(和支持对象),您的 Git 会获取每个分支名称并重命名它们。此重命名过程利用了“远程”的概念。

\n

在 Git 中,远程只是一个至少存储一个 URL 的短名称(稍后您可以让它存储各种额外功能)URL 是您输入的 URL git clone,第一个“远程”的名称origin始终是。4 因此,origin从现在开始,意味着我从 克隆的 URL,除非并且直到您更改某些内容。

\n

Git 使用这个名称\xe2\x80\x94字符串\xe2\x80\x94为其分支名称origin组成新名称。他们成为你的;他们成为你的;如果他们有一个,你会得到一个;等等。这些名称实际上并不是分支名称;而是分支名称。我喜欢称它们为远程跟踪名称5 它们的功能是记住您的 Git 存储库的分支名称是什么,以及上次您的 Git 从其 Git 获取更新时提交的每个选定名称mainorigin/maindebugorigin/debugfeature/tallorigin/feature/tall

\n

重命名完成后,您的 Git 就会为它们拥有的每个分支名称创建远程跟踪名称。您拥有他们的所有提交,并且可以找到所有这些提交,因为您的远程跟踪名称与分支名称具有相同的哈希 ID,他们使用该哈希 ID 来查找其提交。

\n

现在,在您git clone完成并将控制权返回给您以便您可以开始工作之前不久,您的 Git:

\n
    \n
  • 根据您给出的参数在您的存储库中创建一个新的分支名称-b:如果您说-b bugfix,您的 Git 会找到与它们origin/bugfix相对应的您的分支名称bugfix,并创建您自己的分支名称 bugfix,指向相同的提交。
  • \n
  • 签出(切换到)这个新分支。
  • \n
\n

所以现在你的克隆中有一个分支,与它们的分支之一相匹配。如果您不使用-b,您的 Git 会询问他们的 Git 推荐的名称。通常的标准推荐是他们的主要分支(现在通常是main;过去是master)。

\n

一旦您有了克隆,您就可以使用 来添加更多遥控器git remote add。这需要远程名称URL;它设置了遥控器但尚未运行git fetch。现在是时候讨论获取和推送了;请参阅另一个答案。

\n
\n

4您可以选择其他名称,但这样做几乎没有任何意义。用作origin“主遥控器”的名称。您可以随时重命名远程,因此即使您不打算保留起始 URL,也可以将git clone默认值设置为origin此处。

\n

5 Git 将它们称为远程跟踪分支名称,将这个可怜的重载词branch从血腥、畸形的野兽变成了几乎无法辨认的斑点。说真的,只是把“分支”这个词放在这里,没有任何帮助。

\n