TL;DR
你在正确的轨道上。你只需要使用git switch -c my_feature --track fork/my_feature。原因至少有点乱,可能还有其他几种方法可以解决这个问题,这取决于您的个人喜好,但以上应该只是工作。
如果你只是想查看那个提交,你可以告诉git switch使用分离头模式是可以的:
git switch --detach fork/my_feature
一般来说,像这样新工作并不是一个好主意,因为太容易忘记它了。
长
“没什么大不了的,”我想,“反正都在 GitLab 上,我只需要回到原来的位置”
这是正确的——毕竟这就是分布式开发的重点:存储库有不止一个副本。不过,棘手的部分是回到原来的位置。这并不难,但是 棘手,不同之处在于人们可以在不了解实际情况的情况下使用 Git。那是因为我们(人类,而不是计算机)喜欢认为 Git 是关于文件和分支的,但事实并非如此。 Git 就是关于提交。
问题是,在 Git 中,提交是通过哈希 ID 识别的:大而丑陋的随机字母和数字字符串,例如 d2ecc46c0981fb829fdfb204604ed0a2798cbe07。每个提交都获得其中一个,并且没有任何提交与任何其他提交共享它。1 这意味着这些 hsah ID 是提交,在非常真实的意义上.您只需将哈希 ID 提供给您的 Git,它就会找出提交(如果有的话);或者如果它没有,您知道您需要从拥有它的任何一个 Git 或 Git 复制该提交。
因此,尽管这些是 Git 使用的,但这些哈希 ID 对人类没有好处,也不是 我们 与 Git 交互的方式。它们对于做任何新工作也毫无用处:它们的唯一目的是定位和提取现有工作。请记住,每个提交都代表一个快照,及时冻结。也就是说,每个提交都会存储您的所有文件。每个提交都有每个文件的完整副本。这些副本被重复数据删除,这是一件好事。然后它们以只有 Git 本身可以读取和使用的冻结、压缩形式存储。总体而言,这很好,因为这意味着每个存储库(通常)都相对较小:随着我们进行更多提交,存储库不会变得非常庞大,因为每个提交实际上只是重复使用以前的文件。
但这确实意味着我们确实不能处理提交。我们必须让 Git extract 提交。这会将提交的文件复制到我们可以使用的形式。这就是git switch 和在较小程度上git restore 的意义所在。请注意,在 2.23 之前的 Git 版本中,它们被组合成一个大的git checkout 命令。
1这在完全独立的存储库中通常也是如此。上面引用的提交哈希 ID 位于 Git 本身的 Git 存储库中,因此,如果您有该存储库的克隆并且它是最新的,那么您将在该存储库的克隆中拥有该提交。不过,该哈希 ID 不会出现在您的 GitLab 存储库中。所以这些哈希 ID 是普遍唯一的。
它们不一定是——它们只需要在你将通过git remote add 等相互连接的存储库集中是唯一的——但总的来说,它们是。任何两个单独的哈希 ID 意外冲突的可能性仅为 2160 分之一。但是,birthday paradox 意味着随着提交数量的增加,机会会迅速增加,如果您的提交数量超过几万亿次左右,它就会在统计上显着。尽管Git is accidentally immune to some known collisions,恶意行为者也有可能故意制造碰撞。无论如何,SHA-1 不再被认为是加密安全的,因此 Git 最终可能会迁移到 256 位 SHA。
名字
虽然 Git 是关于 commits 的,但我们人类喜欢从 分支 的角度来思考。 branch:这个词有一个很大的问题:我们人类用它含糊不清。有时,当我们说 branch B 时,我们的意思是 一个提交,由我们的名字 B 找到。有时,当我们说branch B时,我们的意思是每次提交直到并包括一系列提交的last提交,这些提交的最后一次提交是使用我们的名字B找到的。有时我们指的是名称 B 本身,有时我们指的是 Git 提供的通用名称的特定子集。
(另见What exactly do we mean by "branch"?)
Git 有多种不同的名称。这些包括分支名称如master或feature/tall,标签名称如v1.0和v2.17.2;和 远程跟踪名称,例如 origin/master 或 fork/my_feature。所有这些名称的处理方式都非常相似,最终被视为 Git 调用 refs 或 references 的通用形式。
为了跟踪每个引用及其名称,Git 的引用存储在name spaces 中。 refs/heads/ 命名空间包含我们所有的分支名称,所以master 实际上只是拼写refs/heads/master 的一种简短方式。标签名称在 refs/tags 中:v1.0 只是 refs/tags/v1.0 的缩写。
远程跟踪名称是其中最复杂的,但遵循完全相同的模式origin/master 是 refs/remotes/origin/master 和 work/my_feature 是 refs/remotes/work/my_feature。稍微棘手的部分是refs/remotes/ 本身被拆分为refs/remotes/origin/* 和refs/remotes/work/*。这是因为 远程名称 origin 和 work。我们稍后再讨论。
这些名称中的每一个仅存储一个哈希 ID。这就是它所要做的一切,所以这就是 Git 用它所做的一切:2master 的名称意味着一些提交哈希 ID,而名称 work/my_feature 也意味着一些提交哈希 ID——可能一个不同的,但两个不同的名称可以表示相同的哈希 ID。然而,分支名称有一个非常特殊的特点。
当我们使用git switch 获得“在一个分支上”,如master 或develop,该分支名称将成为当前分支。 Git 为我们提取正确的提交:Git 在 Git 的 name-to-hash-ID 表中查找哈希 ID,并将提交的冻结文件复制到具有普通文件的工作区中。这使我们能够查看和编辑文件。但是,与此同时,Git stores name 到特殊的 Git 引用 HEAD,因此 git status,例如,现在会说 on branch master 或on branch develop.
“在树枝上”给了我们一个快乐的财产。正确描述这个属性需要我们再看一次提交的解剖结构。
2分支名称也有其他功能,但它们是通过将分支名称及其其他数据存储在您的 .git/config 文件中来处理的,而不是通过 name-to-hash-ID映射部分是每个 Git 引用的一部分。
提交存储数据和元数据,并包含哈希 ID
我们在上面说过,每次提交都会存储所有文件的完整快照,这仍然是正确的。这是提交的主要数据:一个保存的文件树,其中所有文件都是特殊的、只读的、仅 Git 的、冻结和压缩格式。我们还说过,每个提交都有一个唯一的哈希 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 来显示比我们通常看到的更多)。实际上,这只是提交中原始数据的清理版本,我们可以直接查看:
$ 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 行代表已保存的快照。 author 和 committer 行给出了提交者的姓名:作者是编写者,提交者是将其添加到 Git 存储库的人。3parent 行给出了该提交之前提交的原始哈希 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。
这些互连形成了向后看的链
当像master 这样的名称包含像d2ecc46c... 这样的哈希ID,或者像d2ecc46c... 或ea7aa4f6... 这样的提交包含某个早期提交的哈希ID,我们说那个名字,或者那个提交,指向目标。所以master这个名字指向d2ecc46c...,而ea7aa4f6...又指向ea7aa4f6...。我们可以这样画:
... <-ea7aa4f6 <-d2ecc46c <--master
事实上ea7aa4f6 指向两个不同的提交:
...--d55a4ae7--ea7aa4f6--d2ecc46c <-- master
/
...--1eb73712
一般来说,如果我们让圆点 o 或大写字母代表看起来随机且完全容易忘记的哈希 ID,我们会得到更多有用的图片:
...--o--o--o <-- master
或:
...--D--G--H <-- master
/
...--E--F
这是一种更易于理解,因此对人类来说更容易思考分支和分支名称的方式。 这里的关键要点是分支名称指向分支中的 last 提交,而该提交指向也包含在分支中的较早提交。 所以鉴于上图,通过H 的所有提交都在master 上。在某个时候,有一个名字dd/t5703-grep-a-fix,指向提交F:
...--D--G--H <-- master
/
...--E--F <-- dd/t5703-grep-a-fix
不再需要该名称,因为 Git finds 以某个名称开头(例如 master)并找到 last 提交,然后使用它来提交向后工作。从提交H,Git 工作回到G;从G,Git 可以同时返回D 和 F;所以 Git 可以在没有单独名称的情况下找到 F。
出于这些目的,任何名称都与任何其他名称一样好。 像 master 这样的分支名称,或者像 v2.17.2 这样的标签名称,或者像 dd/t5703-grep-a-fix 这样的远程跟踪名称(我猜这是一个远程跟踪名称),所有这些都只是用来定位一个特定的提交,这就是我们所需要的这些目的。
分支名称的特殊性
branch 名称的特别之处在于,我们可以使用 git switch 或在早于 2.23 的 Git 中使用 git checkout 来“进入”分支。我们无法“启用”标签或远程跟踪名称:相反,我们会收到 分离的 HEAD (git checkout) 或投诉 (git switch):4
fatal: a branch is expected, got remote branch 'fork/my_feature'
但是我们可以上一个分支:
git checkout master
之后我们可以像这样绘制我们的图表:
...--G--H <-- master (HEAD)
如果我们现在做一些工作并进行 new 提交,会发生以下情况:
-
做一些工作:我们修改工作树中的文件,然后运行
git add 将更新后的文件复制回 Git 的索引中。
-
git commit:Git 创建一个新的提交,获得一个新的唯一哈希 ID。我们将此提交称为I,使用H 之后的下一个字母。
- Git 收集适当的元数据:用户名、电子邮件、日志消息、当前日期和时间等。该元数据中包含提交
H 的原始哈希 ID,它是 当前提交,因为 master 是当前 分支名称,因为 HEAD 附加到master。所以新提交 I 的父级将是 H。
- Git 一直冻结其索引中的所有文件(也称为 暂存区)。我们不会在这里详细介绍,但请注意索引开始匹配提交
H。
- Git 写出新的提交,此时获取其哈希 ID。哈希 ID 基于数据和所有元数据,包括您运行
git commit 的确切秒数。它看起来是随机的,但它只是所有这些数据的校验和。
- 最后,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 从存储库中取出,摆弄一些字节,然后将结果放回去,那只是一个不同的提交 H' 具有不同的哈希ID。提交H 仍然存在。提交I 将指向提交H,因为现在提交I 存在,it 的任何部分也不能更改。
所以,“在一个分支上”的特殊功能是,当我们进行新的提交时,Git 会自动更新分支名称。更新的名称是我们附加的名称HEAD。我们可以做额外的名字:
...--G--H <-- master, develop
我们选择其中一个名称并将HEAD 附加到它:
...--G--H <-- master (HEAD), develop
然后我们进行新的提交并得到:
I <-- master (HEAD)
/
...--G--H <-- develop
如果我们再次提交,那会继续扩展分支:
I--J <-- master (HEAD)
/
...--G--H <-- develop
没有现有的提交 更改,也没有其他分支名称 移动。只有我们“打开”的分支名称——如git status 中的on branch master——移动。
如果我们现在切换到 develop,我们会在工作区(以及 Git 的索引/暂存区)中返回提交 H:
I--J <-- master
/
...--G--H <-- develop (HEAD)
现在,如果我们进行两次新的提交,它们会使 name develop 相应地移动:
I--J <-- master
/
...--G--H
\
K--L <-- develop (HEAD)
现在我们有了熟悉的分支结构。请注意,通过并包括H 的提交都在两个分支上。提交 I-J 当前仅在 master 上,K-L 当前仅在 develop 上。名称HEAD 附加到名称develop,告诉我们当前分支名称是develop,当前提交是提交L。
4您可以在此处看到 Git 将其称为 远程分支,而不是我的首选术语,远程跟踪名称。鉴于 branch 这个词在 Git 中的过载程度,我认为 remote-tracking name 是描述 fork/my_feature 名称的更好用语。不过,两者在这里的意思是一样的。
远程、git fetch 和远程跟踪名称
当我们有两个或多个存储库应该保存相同的提交时,我们需要让它们相互交谈。一般来说,为了实现这一点,我们给每个“其他 Git”起一个名字。这个名字是一个remote。
大多数时候,我们会自动获得第一个也是唯一一个遥控器。我们创建自己的本地 Git 存储库,不是通过运行 git init,而是通过运行 git clone:
git clone ssh://git@github.com/project/repo.git
例如。 git clone 命令实际上只是一个花哨的包装器,它为我们运行了六个命令:
-
mkdir,创建一个新的空目录,加上一个内部chdir 到新目录中,用于后续的每个命令;
-
git init,在这个空目录中创建一个仓库;
-
git remote add origin <em>url</em>,创建远程名称origin 并使用它来存储url;
-
git config if / 根据需要(主要是如果我们使用 git clone 命令指定特定的配置项);
-
git fetch origin;最后
-
git switch -c master --track origin/master,或类似的东西(见下文)。
当这一切都完成后,我们留下了一个非裸存储库——一个具有关联工作树的存储库,我们可以在其中完成我们的工作——它已将master 分支签出到其工作树中, 5 位于新目录的顶层。正确的存储库是 .git 子目录及其所有文件。6
我们有一个名为origin 的remote 是我们的origin/* remote-tracking 名称 的来源。上面的第 5 步是让我们的 Git 运行 git fetch origin。这让我们的 Git 使用步骤 3 中保存的 URL 调用他们的 Git。他们的 Git 然后为我们的 Git 列出他们的所有分支和其他名称,以及相应的提交哈希 ID。我们的 Git 主要丢弃了非分支名称,除了标签,它们以复杂的方式处理,我们不会在这里正确介绍。我们的 Git 采用分支名称并重命名它们。例如,他们的master 变成了我们的origin/master。7
每个重命名的分支名称的全名是一个远程跟踪名称,即在我们前面提到的那个远程跟踪名称空间中:它们的refs/heads/master——一个分支名称——变成了我们的refs/remotes/origin/master :远程跟踪名称。对于他们的每个refs/heads/* 名称,我们得到一个refs/remotes/origin/* 名称。
他们的分支名称持有的哈希 ID 成为我们的远程跟踪名称持有的哈希 ID。但是,为了让我们的远程跟踪名称保存这些哈希 ID,我们必须首先获取提交。所以在我们实际使用这些重命名之前,我们的 Git 会告诉他们的 Git:请发送这些提交,以及他们所有的祖先。
结果是我们得到了他们拥有的每一个提交,除了一些无法访问的提交,或者只能从非分支名称访问的提交。8所以在我们之后:
git clone <url>
我们有一个新的存储库,其中包含他们的每一次提交——或几乎每一次提交——并且已将他们的分支名称更改为我们的远程跟踪名称。最后一步,即上面的第 6 步,已在 我们的 Git 存储库中创建,我们的 是唯一的一个分支,名为 master。
我们可以在以后的任何时间运行:
git fetch origin
我们的 Git 会调用他们的 Git 并让他们列出他们所有的名字,就像以前一样。和以前一样,我们将从这些名称中找到我们想要但还没有的任何提交,并从他们的 Git 中获取这些提交。然后我们将调整我们的远程跟踪名称,origin/*,以匹配它们的分支名称。 这样做的最终结果是git fetch origin 获得origin 的新提交并更新我们对其分支名称的记忆。它根本不涉及任何我们的分支名称。
我们可以使用:
git remote add fork <url>
添加一个名为fork 的新远程存储给定的URL。完成之后,我们运行:
git fetch fork
让我们的 Git 调用他们的 Git,让他们列出他们的 branch(和其他)名称,然后让我们的 Git 将这些名称重命名为我们的远程跟踪 fork/* 名称。我们将从他们那里得到他们有的任何提交,我们没有,我们需要更新我们的 fork/* 名称,然后更新我们的 fork/* 名称以记住他们的分支名称。
5由于当前工作目录在 Linux 中的工作方式,我们仍然需要在目录中添加自己的 cd。理论上,在某些操作系统上,git clone 可以调整 shell 的工作目录,但鉴于人们不希望它这样做,它不会那样做——它只是在新目录中运行其他五个命令中的每一个.
6如果您真的愿意,您可以稍后将存储库适当地移动到其他地方。如果您执行自己的命令而不是让git clone 为您执行命令,您可以先进行设置。据我所知,没有人真正以这种方式工作,并且允许它的功能对于普通工作并不是特别有用 - 它是供内部使用的,以现代 Git 方式处理子模块,而不是旧的 Git 1.7 风格。
7你可以改变这一切。例如,使用git clone --mirror 会生成一个bare 克隆——一个没有工作树的克隆,这意味着你不能在其中做任何工作——我们的Git 会盲目地复制它们的所有名称。这里的底层机制非常灵活,但在实践中,它主要用于处理三个特别有趣的特殊情况。我们在这里介绍的唯一一个是正常的日常完整克隆与工作树案例。
8无法访问的提交是无法通过以名称开头并通过父链接向后工作来找到的提交。可以通过一些时髦的 GitHub 特定名称(例如 refs/pull/123/head)访问的提交也可能不会出现。不过,通过配置脚注 7 中提到的奇特机制,我们也可以安排引入 pull-request 提交。
他们的分支名称不是我们的分支名称
虽然上面已经介绍了这一点,但值得再次强调它。现在我们有两个遥控器,origin 和 fork,我们有两组远程跟踪名称。我们有一个origin/master 和一个fork/master,前提是origin 和fork 都有名为master 的分支。
他们的masters 可能有不同的最终提交——以及不同的早期提交——与我们的master。当然,如果我们现在运行git clone origin,很可能我们的master 匹配它们的origin/master:例如,它们都指向提交H。 fork/master 更可能与这两个不同。
不过,无论如何,如果我们想在 我们的 Git 存储库中拥有其他分支名称,我们现在可以创建它们。我们的分支名称是我们的。我们可以对它们做任何我们喜欢的事情,随时创建和删除它们。对我们的分支名称的唯一限制是每个分支名称必须指向 我们的 存储库中某个实际的、现有的、有效的提交哈希 ID。目前,我们有从其他两个存储库获得的提交,因此我们可以创建任何我们喜欢的分支名称,指向这些提交中的任何一个。
“按我的意思做”模式
git switch 命令采用分支名称:
git switch master
鉴于名称master 已经存在,这意味着选择名称master 和名称master 指向的提交。 Git 将尝试将HEAD 附加到该名称,并将该提交的冻结快照提取到我们的工作树中。
但是你可以给git switch一个还不存在的分支的名字!
假设origin/develop 存在,并进一步假设fork/develop 不存在,或者我们还没有完成git remote add fork ...。也就是说,我们有这样的东西:
...--G--H <-- master (HEAD), origin/master
\
I <-- origin/develop
然后:
git switch develop
会失败,因为我们没有develop——但在它失败之前,Git 首先检查:我是否只有一个远程跟踪名称,表明另一个Git 有一个develop? 在这种情况下,因为有一个origin/develop 而没有fork/develop,所以情况就是这样:只有另一个develop。
然后我们的 Git 说:啊哈,你的意思是你希望我使用origin/develop 标识的提交创建 develop。所以我们的 Git 做到了——它创建名称develop,指向提交I,并切换到它:
...--G--H <-- master, origin/master
\
I <-- develop (HEAD), origin/develop
这种“按我的意思做”模式是可选的,并且还具有一些更高级的功能,但它默认为“开启”并且行为如上所述。它基本上把你给的git switch——git switch develop——变成了:
git switch -c develop origin/develop
这是一个显式请求:检查由名称origin/develop标识的提交(在本例中为提交I)同时,创建一个新的分支名称develop 指向此提交。
在你的情况下,你有更复杂的事情。例如,也许你有:
...--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 严重超载。远程跟踪名称已经使用了这个动词,因为当您运行 git fetch 时,您的 Git 会从其 Git 的分支名称更新它们。 git switch -c name --track remote/name 以另一种方式使用动词,我们在这里没有介绍。
独立于这两者,您的工作树中的文件可以是已跟踪或未跟踪。 跟踪的文件 只是目前在 Git 索引中的文件。我们也没有正确介绍 Git 的索引,但它是一个非常重要的结构,很高兴了解它。
您可以从任何提交中提取任何文件或整个目录树。在 Git 2.23 或更高版本中,使用 git restore 来执行此操作。在旧版本的 Git 中,此功能也被卡在 git checkout 中。
结论
这里要记住的事情是:
Git 已分发。一些存储库有许多副本,包含提交。提交包含文件,但我们与 Git 交互的单位是整个提交:我们要么拥有它们,要么没有。
这些存储库副本大多包含相同的提交。提交实际上是通过复制共享的,并通过它们的哈希 ID 找到。一旦创建,任何提交都不能改变,一点也不能改变,所以如果你有一个副本,只使用你的副本是安全的。如果您没有副本,您只需通过哈希 ID 从任何拥有副本的人那里获得一份:它们都是一样的。不仅如此,hash-ID-as-checksum 的密码学方面还告诉您,您拥有正确的提交内容:没有人弄乱它。
这些存储库不共享它们的分支名称。最多,有时有人会来确保存储库副本 C1 的分支名称 B 与存储库副本 C2 中的分支名称匹配。如果人们对此很认真,那么分支名称似乎会保持同步,但这只是因为人们很努力地同步这些独立的名称。
您的 Git 会记住 remote 下其他 Git 的 URL: 一个短名称,例如 origin 或 fork 或 upstream 或任何您喜欢的名称。
-
您自己的 Git 会记住另一个 Git 的分支名称作为您的 remote-tracking 名称。通过远程名称向另一个 Git 运行 git fetch 将获取新的提交(但不会重新获取旧的提交,因此速度很快)并更新你的 Git 对其 Git 分支名称的记忆。
李>
要进行新工作,您需要创建或更新自己的分支名称。
请记住,在您使用 git push 将新提交发送到其他 Git 存储库之前,它们不会有您自己的提交。只有你自己的 Git 存储库会有这些。如果您向其他 Git 存储库询问这些哈希 ID,他们只会说:我没有那个哈希 ID。