如何正确克隆和切换到 fork 的现有分支

How to properly clone and switch to existing branch of fork

我一直在处理与合并请求关联的分叉的分支,但我愚蠢到不小心删除了我的本地 git 文件夹。幸运的是,所有代码更改都已推送,但我不知道如何在删除时正确地重新创建文件夹的状态。

我最初使用

克隆了一个项目
git clone [origin_URL]

之后我做了一些本地更改,在 GitLab 上创建了一个分支,并使用

添加了它
git remote add fork [fork_URL]

然后创建了一个分支,推送到我的fork

git checkout -b new_feature
git add [files]
git commit -m [message]
git push fork new_feature

并创建了一个从 my_user_name/project:new_featureother_user_name/project:master 的合并请求。合并请求已经进行了一些讨论,并且已经进行了一些提交,我准备在继续其他工作之前完成最后的润色,但那是在我意识到我不小心删除了我的本地文件夹之前。

“没什么大不了的,” 我想,“反正都在 GitLab 上,我只需要回去到我原来的位置” 但现在我花了最后 2 个小时试图找出克隆存储库和正确配置分支的正确方法,但都无济于事。我一直在阅读 Google 和 SO 搜索中关于 SO 的其他几个 Git 问题,其中 none 似乎为这个特定问题提供了答案,所以我认为这应该不会是重复的,但当然我不能太确定这里 Git 的问题数量。

我尝试了 git clone 的几种变体,首先克隆原始存储库并使用 git remote add fork,克隆分支,重命名它并将原始存储库添加为 origin,使用 --branch 克隆...我尝试回到我在使用 git checkout 之前正在处理的分支,但到目前为止所有尝试都以我不知道的“分离头”状态结束当我只想回到我正在工作的分支时,如何退出或 Git 迫使我创建一个新分支。我尝试使用 git switch fork/my_feature 但得到了

fatal: a branch is expected, got remote branch 'fork/my_feature'

由于请求已经打开,master 分支上有一些(不相关的)activity,所以源分支是目标分支后面的一些提交,这意味着我需要对源进行变基分支到目标分支——我对 Git 的了解还不够,不知道这是否与问题相关,所以我想我会提到它。任何对 Git 有足够经验的人的任何见解告诉我为什么以前的方法失败将不胜感激。

TL;DR

你走在正确的轨道上。你只需要使用 git switch -c my_feature --track fork/my_feature。原因至少有点乱,可能还有其他几种方法可以解决这个问题,具体取决于您的个人喜好,但以上应该 Just Work.

如果你只想查看那个提交,你可以告诉git switch可以使用detached-HEAD模式:

git switch --detach fork/my_feature

一般来说,像这样新工作并不是一个好主意,因为它太容易忘记了。

“Not a big deal,” I thought, “it's all on GitLab anyway, I'll just have to get back to where I was”

这是对的——毕竟这就是分布式开发的重点:存储库有 多个副本。不过,棘手的部分是回到原来的位置。它不是 hard,而是 tricky,区别在于人们可以使用 Git 不了解到底发生了什么。那是因为我们(人类,而不是计算机)喜欢将 Git 视为关于文件和 b运行ches,但事实并非如此。 Git 是关于 提交 .

问题是,在 Git 中,提交是通过哈希 ID 识别的:丑陋的大 random-looking 字母串和 digit,例如 d2ecc46c0981fb829fdfb204604ed0a2798cbe07 .每个提交都会得到其中一个,并且没有任何提交会与任何其他提交共享它。1 这意味着这些 hsah ID 提交,在非常真实的意义上。您只需将哈希 ID 提供给您的 Git,它就会取出提交(如果有的话);或者如果它没有它,你知道你需要从任何 Git 或 Gits do 复制那个提交。

所以即使这些是 Git 使用的,这些哈希 ID 对人类没有好处,也不是 我们 与 Git 交互的方式。它们对做任何 new 工作也毫无用处:它们的唯一目的是定位和提取 existing 工作。请记住,每次提交都代表一个快照,及时冻结。也就是说,每次提交都会存储 all 的文件。每个提交都有每个文件的完整副本。这些副本是 de-duplicated,这是一件好事。然后将它们以冻结、压缩的形式存储,只有 Git 本身可以读取和使用。总的来说,这很好,因为这意味着每个存储库(通常)相对较小:随着我们进行更多提交,存储库不会变得非常胖,因为每次提交实际上只保留 re-using 以前的文件。

但这确实意味着我们实际上不能处理提交。我们必须 Git extract 提交。这会将提交的文件复制到我们可以使用的格式。这就是 git switch,在较小程度上,git restore 的意义所在。请注意,在 2.23 之前的 Git 版本中,这些被组合成一个大的 git checkout 命令。


1这通常甚至在 entirely-independent 存储库中也是如此。上面引用的提交哈希 ID 位于 Git 存储库中 Git 本身,因此如果您有该存储库的克隆并且它是最新的,您将在您的克隆中拥有该提交那个存储库。不过,该哈希 ID 不会出现在您的 GitLab 存储库中。所以这些哈希 ID 是普遍唯一的。

它们不一定是——它们只需要在你将通过 git remote add 等相互连接的一组存储库中是唯一的——但总的来说,它们是。任何两个单独的散列 ID 意外碰撞的几率仅为 2160 分之一。然而,birthday paradox means that the chance rises very fast as the number of commits increases, to the point where it's statistically significant if you have more than a few trillion commits or so. It's also possible for a malicious actor to craft a collision on purpose, though Git is accidentally immune to some known collisions。无论如何,SHA-1 不再被认为是加密安全的,因此 Git 最终可能会转向 256 位 SHA。


姓名

虽然 Git 完全是关于 提交 ,但我们人类喜欢根据 b运行ches[=654= 来思考]. b运行ch:这个词有很大的问题,我们人类使用起来含糊不清。有时,当我们说 b运行ch B 时,我们的意思是 一次提交,由我们的名字 B 找到。有时,当我们说 b运行ch B 时,我们指的是 每次提交,包括 最后一次 提交在一系列提交中,最后一次提交是使用我们的名字 B 找到的。有时我们指的是 名称 B 本身,有时我们指的是 Git 提供的通用名称的特定子集.

(另见 What exactly do we mean by "branch"?

Git 有多种不同的名称。这些包括 b运行ch names like master or feature/tall, tag names like v1.0v2.17.2;和 remote-tracking 名称 origin/masterfork/my_feature。所有这些名称的处理方式都非常相似,最终被处理为 Git 调用 refsreferences 的一般形式。

为了跟踪每个引用及其名称类型,Git 的引用存储在name spacesrefs/heads/ 命名空间包含我们所有的 b运行ch 名称,因此 master 实际上只是拼写 refs/heads/master 的缩写。标签名称在 refs/tags 中:v1.0 只是 refs/tags/v1.0 的缩写。

Remote-tracking 名称是其中最复杂的名称,但遵循完全相同的模式 origin/masterrefs/remotes/origin/masterwork/my_featurerefs/remotes/work/my_feature。稍微棘手的部分是 refs/remotes/ 本身被拆分为 refs/remotes/origin/*refs/remotes/work/*。这是因为 远程名称 originwork。我们稍后再谈这个。

这些名称中的每一个都只存储一个哈希 ID。这就是它必须做的所有事情,所以这就是 Git 对它所做的一切:2 名称 master 表示一些提交哈希 ID,名称 work/my_feature 也表示一些提交哈希 ID——可能是不同的,但是两个不同的名称 可以 表示相同的哈希 ID。然而,B运行ch 名称有一个非常特殊的特征。

当我们使用 git switch 来获得 "on a branch" 就像 masterdevelop 时,b运行ch 名称成为 current b运行ch。 Git 为我们提取正确的提交:Git 在 Git 的 name-to-hash-ID table 中查找哈希 ID,并复制出提交的冻结文件,进入带有普通文件的工作区。这允许我们查看和编辑文件。但与此同时,Git name 存储 到特殊的 Git ref HEAD 中,例如 git status 现在会说 on branch masteron branch develop.

成为"on a branch"给了我们快乐属性。正确描述此 属性 需要我们重新审视一次提交的剖析。


2B运行ch 名称还有其他功能,但它们是通过存储 b运行ch 名称及其其他数据来处理的在你的 .git/config 文件中,而不是每个 Git 引用中的 name-to-hash-ID 映射部分。


提交存储数据和元数据,并包含哈希 ID

我们在上面说过,每次提交都会存储所有文件的完整快照,现在仍然如此。这是提交数据的主要部分:保存的 file-tree,其中所有文件都采用特殊的 read-only、Git-only、冻结和压缩格式。我们还说过,每个提交都有一个唯一的哈希 ID,这也是事实。我们遗漏的是每个提交都包含一些 元数据: 一些关于提交本身的信息。

当我们 运行 git log 时,我们会看到部分或大部分元数据,例如:

$ git log --format=fuller -1 | sed 's/@/ /'
commit d2ecc46c0981fb829fdfb204604ed0a2798cbe07
Author:     Junio C Hamano <gitster pobox.com>
AuthorDate: Sun May 24 18:13:53 2020 -0700
Commit:     Junio C Hamano <gitster pobox.com>
CommitDate: Sun May 24 19:39:40 2020 -0700

    Hopefully final batch before 2.27-rc2

    Signed-off-by: Junio C Hamano <gitster pobox.com>

(我在这里使用 --format=fuller 来显示比我们通常看到的更多)。实际上,这只是提交中的原始数据的cleaned-up版本,我们可以直接查看:

$ git cat-file -p HEAD | sed 's/@/ /'
tree e83aacc68752967a710fc32e3cf49356959545eb
parent ea7aa4f612ef33ecfb7fd6d488d949da3a51a377
author Junio C Hamano <gitster pobox.com> 1590369233 -0700
committer Junio C Hamano <gitster pobox.com> 1590374380 -0700

Hopefully final batch before 2.27-rc2

Signed-off-by: Junio C Hamano <gitster pobox.com>

tree 行代表保存的快照。 authorcommitter 行给出了提交人的姓名:作者是编写它的人,提交者是将它添加到 Git 存储库的人。3 parent 行给出了 此提交之前提交的原始哈希 ID。

每个提交都有一些 parent 行。事实上,如果我们查看 HEAD 之前的提交:

$ git cat-file -p ea7aa4f612ef33ecfb7fd6d488d949da3a51a377 | sed 's/@/ /'
tree 0342252fde5f2b5721299d321d57ce12542b2957
parent d55a4ae71d515e788e5afb355a20c4b262049cac
parent 1eb73712360744b552f30a6961c03d05bc44bef2
author Junio C Hamano <gitster pobox.com> 1590374380 -0700
committer Junio C Hamano <gitster pobox.com> 1590374380 -0700

Merge branch 'dd/t5703-grep-a-fix'

Update an unconditional use of "grep -a" with a perl script in a test.

* dd/t5703-grep-a-fix:
  t5703: replace "grep -a" usage by perl

我们看到它有 两行 parent 行。这将此提交标记为 合并提交,结合了两个不同系列的提交——两行工作。


3这个特定的拆分允许通过电子邮件发送补丁,这在 2005 年左右的时间框架中更为重要,当时 Linus Torvalds 首次编写 Git:参见 commit e83c5163316f89bfbde7d9ab23ca2e25604af290.


这些互连形成 backwards-looking 链

当像 master 这样的名称包含像 d2ecc46c... 这样的哈希 ID,或者像 d2ecc46c...ea7aa4f6... 这样的提交包含一些早期提交的哈希 ID,我们说那个名字,或者那个提交,指向目标。所以名称 master 指向 d2ecc46c...,后者又指向 ea7aa4f6...。我们可以这样画:

... <-ea7aa4f6 <-d2ecc46c   <--master

事实上 ea7aa4f6 指向两个不同的提交:

...--d55a4ae7--ea7aa4f6--d2ecc46c   <-- master
              /
 ...--1eb73712

一般来说,如果我们用圆点o或大写字母代替random-looking,完全忘记table哈希ID,我们会得到更多有用的图片:

...--o--o--o   <-- master

或:

  ...--D--G--H   <-- master
         /
...--E--F

这是一种更文艺table,因此easier-for-humans,思考b运行ches和b运行ch名字的方式。 这里的关键是 b运行ch 名称指向 b运行ch 中的 last 提交,并且该提交指向后面到 b运行ch. 中也包含的较早的提交所以鉴于上面的绘图,所有通过 H 的提交都在 master 上。在某个时候有一个名称,dd/t5703-grep-a-fix,指向提交 F:

  ...--D--G--H   <-- master
         /
...--E--F   <-- dd/t5703-grep-a-fix

不再需要该名称,因为 Git 查找 以某个名称开始提交——例如 master——并找到 last 提交,然后使用它向后工作。从提交 H,Git 回到 G;从G、Git 可以返回到 D F;所以 Git 可以找到没有单独名称的 F

出于这些目的,任何名称都和其他名称一样好。 b运行ch 名称如 master,或标签名称如 v2.17.2,或者像 dd/t5703-grep-a-fix 这样的 remote-tracking 名称(我猜这是一个 remote-tracking 名称),所有这些都只是用来定位一个特定的提交,这就是我们所需要的出于这些目的

a b的特点运行ch name

使 b运行ch 名称特别的是我们可以得到 "on" b运行ch,使用 git switch 或者,在 2.23 之前的 Git 中,git checkout。我们无法获得 "on" 标签或 remote-tracking 名称:相反,我们得到 分离的 HEAD (git checkout) 或投诉 (git switch):4

fatal: a branch is expected, got remote branch 'fork/my_feature'

但是我们可以上b运行ch:

git checkout master

之后我们可以像这样绘制图形:

...--G--H   <-- master (HEAD)

如果我们现在做一些工作并进行 new 提交,会发生以下情况:

  1. 做一些工作: 我们修改 work-tree 中的文件,然后 运行 git add 将更新后的文件复制回 Git的索引。
  2. git commit: Git 创建一个新的提交,它获得一个新的且唯一的哈希 ID。我们将调用此提交 I,使用 H 之后的下一个字母。

    • Git 收集适当的元数据:用户名、电子邮件、日志消息、当前 date-and-time 等。该元数据中包含提交 H 的原始哈希 ID,这是 当前提交 因为 master 是当前 b运行ch name 因为 HEAD 附加到 master。所以新提交 I 的父级将是 H.
    • Git 始终冻结其索引中的所有文件(又名 暂存区)。我们不会在这里详细介绍,但请注意索引开始匹配提交 H.
    • Git写出新的commit,此时获取其hash ID。哈希 ID 基于数据和所有元数据,包括您 运行 git commit 的确切秒数。它 看起来 运行dom,但它只是所有这些数据的校​​验和。
    • 最后,Git 施展绝招。在我描述它之前,让我们看一下图表。

由于新提交 I 的父项是现有提交 H,提交 I 指向 H:

...--G--H
         \
          I

但是 name master 用来包含 H 的哈希 ID 呢?好吧,因为 HEAD 附加到 master,Git 将 I 的哈希 ID 写入 name master .所以现在 master 指向 I,而不是 H:

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

现有提交根本没有改变。物理上不可能更改任何现有提交的任何部分,因为 H 的实际哈希 ID 是 in 提交 H 中所有字节的校验和。那些不允许改变!如果我们从存储库中取出 H,fiddle 一些字节,然后将结果放回去,那只是一个 不同的提交 H'不同的哈希 ID。提交 H 仍将存在。提交 I 将指向提交 H,因为现在提交 I 存在, 的任何部分都不能更改。

所以,作为 "on a branch" 的特殊功能是当我们 进行新提交时 ,Git 自动更新 b运行通道名称。更新的名称是我们的名称 HEAD attached-to。我们可以做额外的名字:

...--G--H   <-- master, develop

我们选择其中一个名称并附加 HEAD 到它:

...--G--H   <-- master (HEAD), develop

然后我们进行新的提交并得到:

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

如果我们再次提交,那将继续扩展 b运行ch:

          I--J   <-- master (HEAD)
         /
...--G--H   <-- develop

没有 现有提交 更改,并且没有其他 b运行ch name 移动。只有我们是 "on" 的 b运行ch 名称——如 git status 所说 on branch master——移动。

如果我们现在切换到 develop,我们会在我们的工作区(以及 Git 的索引 / staging-area 中返回提交 H

          I--J   <-- master
         /
...--G--H   <-- develop (HEAD)

现在,如果我们进行两次新提交,它们会使 name develop 相应地移动:

          I--J   <-- master
         /
...--G--H
         \
          K--L   <-- develop (HEAD)

现在我们有了熟悉的 branch-y 结构。请注意,通过并包括 H 的提交都在 both b运行ches 上。提交 I-J 目前仅在 master 上,而 K-L 当前仅在 develop 上。名字HEAD附加到名字develop,告诉我们当前的b运行ch namedevelop和当前的commit 是提交 L.


4你可以看到这里 Git 称这个为 remote b运行ch 而不是我的首选术语,remote-tracking 名称。考虑到单词 b运行ch 在 Git 中的严重超载,我认为remote-tracking name 是描述名字 fork/my_feature 更好的短语。不过,两者在这里的意思相同。


远程、git fetch 和 remote-tracking 名称

当我们有两个或多个应该保存相同提交的存储库时,我们需要让它们相互交谈。一般来说,为了实现这一点,我们给每个 "other Git" 一个名字。此名称是 远程.

大多数时候,我们会自动获得第一个也是唯一一个遥控器。我们创建自己的本地 Git 存储库,不是通过 运行ning git init,而是通过 运行ning git clone:

git clone ssh://git@github.com/project/repo.git

例如。 git clone 命令实际上只是一个奇特的包装器,运行 为我们提供了六个命令:

  1. mkdir,创建一个新的空目录,为后续的每个命令添加一个内部chdir进入新目录;
  2. git init,在此空目录中创建存储库;
  3. git 远程添加源 <em>url</em>,创建远程名称 origin 和用它来存储 url;
  4. git config 如果/根据需要(主要是如果我们使用 git clone 命令指定特定的配置项);
  5. git fetch origin;最后
  6. git switch -c master --track origin/master,或非常相似的东西(见下文)。

当这一切都完成后,我们剩下一个 non-bare 存储库——一个具有关联 work-tree 的存储库,我们可以在其中完成我们的工作——它具有 master b 运行ch 检出到位于新目录顶层的 work-tree,5。适当的存储库是 .git sub-directory 及其所有文件。6

我们有一个名为 originremote 事实上我们的 origin/* remote-tracking names 来自。上面的第 5 步是针对我们的 Git 到 运行 git fetch origin。这让我们的 Git 调用他们的 Git,使用在步骤 3 中保存的 URL。他们的 Git 然后列出,对于我们的 Git,他们所有的b运行ch 等名称,以及相应的提交哈希 ID。我们的 Git 大部分丢弃了 non-branch 名称,但标签除外,这些标签的处理方式很复杂,我们不会在这里适当介绍。我们的 Git 采用 b运行ch 名称并 重命名 它们。例如,他们的master变成了我们的origin/master7

这些改名后的每个branch-names的全名都是一个remote-tracking的名字,也就是我们前面提到的那个remote-trackingname-space:他们的refs/heads/master——a b运行ch name——成为我们的 refs/remotes/origin/master: a remote-tracking names。对于他们的每个 refs/heads/* 个名字,我们得到一个 refs/remotes/origin/* 个名字。

他们的 b运行ch 名称持有的哈希 ID 成为我们的 remote-tracking 名称持有的哈希 ID。但是,对于我们的 remote-tracking 名称来保存这些哈希 ID,我们 必须首先获得提交 。因此,在我们实际使用这些重命名的名称之前,我们的 Git 告诉他们 Git:请发送这些提交,以及他们所有的祖先。

结果是我们得到了他们拥有的每个提交,除了一些 无法访问的 提交,或者只能通过 non-branch 名称访问的提交。8 所以在我们之后:

git clone <url>

我们有一个新的存储库,其中包含他们的每一次提交——或者几乎每一次提交——已经改变了他们的b运行ch 名称转换为我们的 remote-tracking 名称。最后一步,即上面的第 6 步,已经在 our Git 存储库中创建了 our 一个且唯一的 b运行ch , 名为 master.

我们可以随时运行:

git fetch origin

和我们的 Git 将调用他们的 Git 并让他们列出所有的名字,就像以前一样。和以前一样,我们会从这些名字中找到我们想要但还没有的任何提交,并从他们的 Git 中获取这些提交。然后我们将调整我们的 remote-tracking names, origin/* 以匹配它们的 b运行ch names . 最终结果是 git fetch origin 获得 origin 的新提交并更新我们对它们 b运行ch 名称的记忆。它根本不涉及任何 our b运行ch 名称。

我们可以使用:

git remote add fork <url>

添加一个名为 forknew 远程存储给定的 URL。完成后,我们 运行:

git fetch fork

让我们的 Git 调用他们的 Git,让他们列出他们的 b运行ch(和其他)名字,并让我们的 Git 将这些名称重命名为我们的 remote-tracking fork/* 名称。我们将从他们那里得到他们有的任何提交,我们没有,我们需要更新我们的 fork/* 名称,然后更新我们的 fork/* 名称以记住他们的 b运行ch名字。


5由于 current-working-directory 在 Linux 中的工作方式,我们仍然需要将自己的 cd 放入目录中。理论上,在某些操作系统上,git clone 可以调整 shell 的工作目录,但考虑到人们不希望它这样做,它不会那样做——它只是 运行 在新目录中添加其他五个命令中的每一个。

6如果您确实需要,您可以稍后将存储库移动到其他地方。如果您执行自己的命令而不是让 git clone 为您执行它们,您可以在最初进行设置。据我所知,没有人真正以这种方式工作,并且允许它的功能对于普通工作并不是特别有用——它是供内部使用的,以现代 Git 方式处理子模块,而不是旧方式Git 1.7 风格。

7你可以改变这一切。例如,使用 git clone --mirror 会生成一个 bare 克隆——一个没有 work-tree 的克隆,这意味着你不能在其中做任何工作——我们的 Git 盲目地复制他们所有的名字。这里的底层机制非常灵活,但在实践中,它主要用于处理三种特别有趣的特殊情况。我们在这里介绍的唯一一个是正常的日常完整克隆-work-tree 案例。

8无法访问的提交 是无法通过从名称开始并通过父链接向后查找的提交。可以通过一些时髦的 GitHub-specific 名称访问的提交,例如 refs/pull/123/head,也可能不会过来。不过,我们可以通过配置脚注 7 中提到的奇特机制,ar运行ge 也带来 pull-request 提交。


他们的b运行ch名字不是我们的b运行ch名字

虽然上面已经介绍了这一点,但值得再次强调。现在我们有两个遥控器,originfork,我们有两组 remote-tracking 名称。我们有一个 origin/master 和一个 fork/master,前提是 originfork 都有一个名为 master.

的 运行 棋子

他们的 master 可能与我们的 master 有不同的最终提交——以及不同的早期提交。当然,如果我们现在 运行 git clone origin,很可能我们的 master 匹配他们的 origin/master:例如,两者都指向提交 H。与这两个不同的可能性更大的是fork/master

无论如何,如果我们想在我们的 Git 存储库中有其他b运行ch 名称,我们现在可以创建它们。我们的 b运行ch 名字是 我们的。我们可以随心所欲地使用它们,随时创建和删除它们。我们的 b运行ch 名称的唯一限制是每个 b运行ch 名称必须指向 our 存储库中一些实际的、现有的、有效的提交哈希 ID。目前,我们有从其他两个存储库获得的提交,因此我们可以使用我们喜欢的任何 b运行ch 名称,指向这些提交中的任何一个。

"Do What I Mean"模式

git switch 命令取一个 b运行ch 名称:

git switch master

鉴于名称 master 已经存在,这意味着 select 名称 master 和名称 master 指向的提交。 Git 将尝试将 HEAD 附加到该名称,并将该提交的冻结快照提取到我们的 work-tree.

但是你可以给git switch一个还不存在的b运行ch的名字!

假设origin/develop存在,进一步假设fork/develop存在,或者我们没有做git remote add fork ...。也就是说,我们有这样的东西:

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

然后:

git switch develop

失败,因为我们没有develop——但在失败之前,Git首先检查:Do我只有一个 remote-tracking 名称表明另一个 Git 有一个 develop? 在这种情况下,因为有一个 origin/develop 而没有 fork/develop,就是这样:恰好还有一个develop.

我们的 Git 然后说:啊哈,你的意思是你想让我 create develop 使用 [=] 标识的提交224=]. 所以我们的 Git 这样做了——它创建名称 develop,指向提交 I,然后切换到它:

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

此 "do what I mean" 模式是可选的,也有一些更高级的功能,但它默认为 "on" 并且行为如上所述。它基本上把你给的git switch——git switch develop——变成了:

git switch -c develop origin/develop

这是一个明确的请求:检查由名称 origin/develop 标识的提交(在本例中为提交 I同时,创建一个新的 b运行ch name develop 指向这个 commit.

在你的情况下,你有更复杂的事情。例如,也许你有:

...--G--H   <-- master (HEAD), origin/master
      \
       I   <-- origin/my_feature, fork/my_feature

如果你现在运行:

git switch my_feature

你的 Git 会抱怨,因为有 两个 名字可以用来创建 my_feature.

请注意,在这种情况下,任何一个名称都可以正常工作 — 无论如何,就我所展示的而言。但是 git switch,或者更早的 git checkout,不会做你想做的,只是因为有不止一个匹配的名字。9 所以一个明确的 git checkout -c <em>name</em> --track <em>remote-tracking-name</em> 正常你要走的路。


9有一个配置设置你可以改变来调整这个,但是这个答案已经很长了,所以我也不在这里介绍。


进一步阅读

Git 中的动词 track 严重超载。 remote-tracking 的名字已经使用了这个动词,因为当你 运行 git fetch 时,你的 Git 会从他们的 Git 的 b运行ch 更新它们名字。 git switch -c name --track remote/name 以另一种方式使用动词,这里我们没有介绍。

独立于这两者,您 work-tree 中的文件可以是 trackeduntracked跟踪的文件 只是现在在Git 的索引中的文件。我们也没有正确地介绍 Git 的索引,但它是一个非常重要的结构,了解它是件好事。

您可以从任何提交中提取任何文件或整个 directory-tree。在 Git 2.23 或更高版本中,使用 git restore 来执行此操作。在旧版本 Git 中,此功能也被塞入 git checkout

结论

这里要记住的事情是:

  • Git已分发。一些存储库有很多副本,持有提交。提交保存文件,但我们与 Git 交互的单元是整个提交:我们要么有,要么没有。

  • 这些存储库副本大多包含相同的提交。提交实际上是通过复制共享的,并通过它们的哈希 ID 找到。一旦创建,任何提交都不能更改,一点也不能更改,因此如果您有副本,则只使用您的副本是安全的。如果您没有副本,您只需通过哈希 ID 从任何拥有副本的人那里得到一份:它们都是一样的。不仅如此,hash-ID-as-checksum 的密码学方面告诉您您拥有 正确的 提交内容:没有人弄乱它。

  • 这些存储库共享它们的 b运行ch 名称。最多,有时有人会出现并确保存储库副本 C1 的 b运行ch 名称 B 与存储库副本 C2 中的匹配。如果人们对此很努力,b运行ch 名称 似乎 保持同步,但这只是因为人们很努力地同步这些独立名称。

  • 您的 Git 会记住 URL 在 远程下的其他 Git 的 URL: 一个简称originforkupstream 或任何你喜欢的。

  • 你自己的 Git 会记住另一个 Git 的 b运行ch 名字作为你的 remote-tracking 名字。 运行 git fetch 到另一个 Git,通过它的远程名称,将获取新的提交(但不是 re-obtain 旧的,所以速度很快)并更新你的 Git对他们Git的b运行ch名字的记忆。

  • 要完成 工作,您需要创建或更新自己的 b运行ch 名称。

  • 请记住,在您使用 git push 发送 新提交到其他 Git 存储库之前,他们不会拥有您的自己的承诺。只有您自己的 Git 存储库才会有这些。如果您向其他 Git 存储库询问这些哈希 ID,他们只会说:我没有那个哈希 ID。