如何正确克隆并切换到现有的分叉分支



我一直在研究与合并请求关联的分支,我很愚蠢,不小心删除了我的本地 git 文件夹。幸运的是,所有代码更改都已被推送,但我无法弄清楚如何在删除时正确重新创建文件夹的状态。

我最初使用

git clone [origin_URL]

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

git remote add fork [fork_URL]

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

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

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

">

没什么大不了的,">我想,"反正都在 GitLab 上,我只需要回到原来的位置",但现在我花了最后 2 个小时试图找出克隆存储库的正确方法并以正确的方式配置分支,但都无济于事。我一直在阅读在谷歌和 SO 搜索上找到的其他几个关于 SO 的 Git 问题,似乎没有一个能为这个特定问题提供答案,所以我认为这不应该是重复的,但当然我不能太确定这里的 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'

由于请求已打开,主分支上有一些(不相关的)活动,因此源分支是目标分支后面的一些提交,这意味着我需要将源分支重新定位到目标分支上 - 我对 Git 的了解不够,不知道这是否与问题相关,所以我想我会提到它。任何来自对 Git 有足够经验的人的见解,告诉我为什么以前的方法失败了,我们将不胜感激。

TL;博士

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

如果你只想查看该提交,你可以告诉git switch可以使用分离的 HEAD 模式:

git switch --detach fork/my_feature

一般来说,做这样的新工作不是一个好主意,因为很容易忘记它。

">

没什么大不了的,"我想,"反正都在GitLab上,我只需要回到原来的位置。

这是正确的 — 毕竟,这就是分布式开发的意义所在:存储库有多个副本。 不过,棘手的部分是回到原来的位置。 这并不,但棘手,不同之处在于人们可以在不了解真正发生的事情的情况下使用 Git。 这是因为我们(人类,而不是计算机)喜欢将 Git 视为与文件和分支有关,但事实并非如此。 Git 是关于提交的

问题是,在 Git 中,提交是通过哈希 ID 标识的:大而丑陋的随机字母和数字字符串,例如d2ecc46c0981fb829fdfb204604ed0a2798cbe07。 每个提交都会获得其中之一,并且没有提交会与任何其他提交共享它。1这意味着这些 hsah ID真正意义上的提交。 您只需将哈希 ID 提供给您的 Git,它就会捞出提交(如果有的话);或者,如果它没有它,你知道你需要从任何 Git 或 Gits 有它的地方复制该提交。

因此,即使这些是 Git 使用的,这些哈希 ID 对人类也没有好处,也不是我们与Git 交互的方式。 它们对于做任何工作也毫无用处:它们的唯一目的是定位和提取现有工作。 请记住,每次提交都代表一个快照,冻结在时间中。 也就是说,每次提交都会存储您的所有文件。 每次提交都有每个文件的完整副本。 这些副本已消除重复,这是一件好事。 然后,它们以只有 Git 本身才能读取和使用的冻结压缩形式存储。 总的来说,这很好,因为这意味着每个存储库(通常)相对较小:随着我们进行更多的提交,存储库不会变得非常胖,因为每次提交实际上只是不断重用以前的文件。

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


1在完全独立的存储库中,这通常也是如此。 上面引用的提交哈希 ID 位于 Git 本身的 Git 存储库中,因此,如果您有该存储库的克隆并且它是合理的最新,那么您将在该存储库的克隆中拥有该提交。 但是,该哈希 ID 不会在您的 GitLab 存储库中。 因此,这些哈希 ID 是普遍唯一的。

它们不一定是 - 它们只需要在您将通过git remote add等相互连接的存储库集中是唯一的 - 但总的来说,它们是。 任何两个单独的哈希ID意外碰撞的几率仅为2160分之一。 然而,生日悖论意味着随着提交数量的增加,机会上升得非常快,如果你有超过几万亿次左右的提交,则在统计上是显着的。 恶意行为者也可能故意制造冲突,尽管 Git 意外地不受某些已知冲突的影响。 无论如何,SHA-1 不再被认为是加密安全的,因此 Git 最终可能会迁移到 256 位 SHA。


名字

虽然 Git 是关于提交的,但我们人类喜欢从分支的角度思考。 这个分支这个词有一个很大的问题我们人类使用它模棱两可。 有时,当我们说分支B 时,我们指的是一个提交,由我们的名字 B 找到。 有时,当我们说分支 B 时,我们指的是一系列提交之前的每次提交,包括最后一次提交,这些提交是使用我们的名字 B 找到的有时我们指的是名称 B 本身,有时我们指的是Git 提供的通用名称的特定子集

(参见 我们所说的"分支"到底是什么意思?

Git 有多种不同类型的名称。 这些包括分支名称,如masterfeature/tall标签名称v1.0v2.17.2;以及远程跟踪名称,如origin/masterfork/my_feature。 所有这些名称的处理方式都非常相似,最终被处理为 Git 称为refs引用的一般形式。

为了跟踪每个引用及其名称类型,Git 的引用存储在命名空间中。refs/heads/命名空间包含我们所有的分支名称,因此master实际上只是拼写refs/heads/master的简短方法。 标签名称在refs/tagsv1.0只是refs/tags/v1.0的缩写。

远程跟踪名称是其中最复杂的,但遵循完全相同的模式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。但是,分支名称有一个非常特殊的功能。

当我们使用git switch来获取"在分支上"时,例如masterdevelop,该分支名称将成为当前分支。 Git 为我们提取正确的提交:Git 在 Git 的名称到哈希 ID 表中查找哈希 ID,并将提交的冻结文件复制到包含普通文件的工作区域中。 这使我们能够查看和编辑文件。 但与此同时,Git 将名称存储到特殊的 Git 引用HEAD中,因此git status,例如,现在会说on branch masteron branch develop

"在树枝上"给了我们一个快乐的财产。 正确描述此属性需要我们重新审视提交的剖析。

2 分支名称

也有其他功能,但它们是通过将分支名称及其其他数据存储在.git/config文件中来处理的,而不是通过作为每个 Git 引用一部分的名称到哈希 ID 映射部分来处理的。


提交存储数据和元数据,并包含哈希 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:请参阅 commite83c5163316f89bfbde7d9ab23ca2e25604af290


这些互连形成向后看的链条

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

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

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

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

一般来说,如果我们让圆点o或大写字母代替看起来随机且完全容易忘记的哈希 ID,我们会得到更有用的图片:

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

或:

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

这是一种更容易消化,因此对人类来说更容易思考分支和分支名称的方法。这里的关键要点是分支名称指向分支中的最后一个提交,并且提交指向分支中也包含的早期提交。因此,给定上面的绘图,通过H的所有提交都在master上。 在某个时候,有一个名字,dd/t5703-grep-a-fix,指向提交F

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

不再需要该名称,因为 Git 通过从某个名称(例如master)开始并找到最后一个提交,然后使用它向后工作来查找提交。 从提交H,Git 工作回G;从G开始,Git 可以回到DF;所以 Git 可以找到没有单独名称的F

出于这些目的,任何名称都与其他名称一样好。master这样的分支名称,或像v2.17.2这样的标签名称,或像dd/t5703-grep-a-fix这样的远程跟踪名称(我猜这是一个远程跟踪名称),所有这些都只是用于定位一个特定的提交,这就是我们实现这些目的所需要的

分支名称的特殊功能

分支名称的特别之处在于我们可以使用git switch或在 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)

如果我们现在做一些工作并进行新的提交,会发生什么:

  1. 做一些工作:我们修改工作树中的文件,然后运行git add将更新的文件复制回 Git 的索引中。
  2. 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

但是曾经包含H哈希IDmaster这个名字呢? 好吧,因为HEAD附加到master,Git 将I的哈希 ID 写入名称master中。 所以现在master指向I,而不是H

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

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

因此,"在分支上"的特殊功能是,当我们进行新提交时,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)

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

I--J   <-- master
/
...--G--H

K--L   <-- develop (HEAD)

现在我们有了我们熟悉的分支 y 结构。 请注意,提交到并包括H都在两个分支上。 提交I-J目前只在master上,K-L目前只在develop上。 名称HEAD附加到名称develop,告诉我们当前分支名称develop,当前提交是提交L


4你可以在这里看到,Git 称这是一个远程分支,而不是我的首选术语远程跟踪名称。 鉴于Git中单词分支的过载程度,我认为远程跟踪名称是描述名称的更好短语fork/my_feature. 不过,两者在这里的意思是一样的。


远程、git fetch和远程跟踪名称

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

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

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

例如。git clone命令实际上只是一个花哨的包装器,为我们运行六个命令:

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

当这一切完成后,我们只剩下一个非裸存储库 — 一个具有关联工作树的存储库,我们可以在其中执行我们的工作 — 它将master分支签入到其工作树,5 该工作树位于新目录的顶层。 存储库本身是.git子目录及其所有文件。6

事实上,我们有一个名为origin遥控器,这是我们origin/*远程跟踪名称的来源。 上面的第 5 步是让我们的 Git 运行git fetch origin. 这让我们的 Git 使用步骤 3 中保存的 URL 调用他们的 Git。 然后,他们的 Git 会为我们的 Git 列出他们所有的分支和其他名称,以及相应的提交哈希 ID。 我们的 Git 主要丢弃非分支名称,除了标签,这些名称以复杂的方式处理,我们不会在这里正确介绍。 我们的 Git 采用分支名称并重命名它们。 例如,他们的master成为我们的origin/master7

这些重命名的分支名称中的每一个的全名都是远程跟踪名称,即,在我们前面提到的远程跟踪名称空间中:它们的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,让他们列出他们的分支(和其他)名称,并让我们的 Git 将这些名称重命名为我们的远程跟踪fork/*名称。 我们将从他们那里获得他们拥有的任何提交,我们没有,我们需要更新我们的fork/*名称,然后更新我们的fork/*名称以记住他们的分支名称。


5我们仍然需要自己cd目录,因为当前工作目录在 Linux 中的工作方式。 从理论上讲,在某些操作系统上,git clone可以调整 shell 的工作目录,但鉴于人们不希望它这样做,它不会这样做——它只是在新目录中运行其他五个命令中的每一个。

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

7你可以改变这一切。 例如,使用git clone --mirror创建一个克隆——一个没有工作树的克隆,这意味着你不能在其中做任何工作——我们的 Git 奴性地复制它们的所有名称。 这里的底层机制非常灵活,但在实践中,它主要用于处理三种特别有趣的特殊情况。 我们在这里介绍的唯一一个是正常的日常全克隆与工作树案例。

8无法访问的提交是指通过从名称开始并通过父链接向后工作而无法找到的提交。 可以通过一些时髦的 GitHub 特定名称(例如refs/pull/123/head)访问的提交也可能不会过来。 不过,我们可以通过配置脚注 7 中提到的花哨机制来安排引入拉取请求提交。

他们的分支名称

不是我们的分支名称

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

他们的master可能有不同的最终提交 - 以及不同的早期提交 - 与我们的master。 当然,如果我们刚才运行git clone origin,很可能我们的master符合他们的origin/master:例如,两者都指向提交Hfork/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

这种"按照我的意思做"模式是可选的,并且还具有一些更高级的功能,但它默认为"on"并如上所述运行。 它基本上将你给出的git switch——git switch develop——变成:

git switch -c develop origin/develop

这是一个显式请求:签出由名称origin/develop标识的提交(在本例中为 commitI),同时创建一个指向此提交的新分支名称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 -cname--trackremote-tracking-name通常是要走的路。


9您可以更改配置设置来调整此设置,但是这个答案已经很长了,所以我也不会在这里介绍。


延伸阅读

Git 中的动词轨道严重过载。 远程跟踪名称已经使用此谓词,因为当您运行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 会记住遥控器下其他 Git 的 URL:一个短名称,如originforkupstream或任何你喜欢的名字。

  • 你自己的 Git 会记住另一个 Git 的分支名称作为你的远程跟踪名称。 通过其远程名称运行git fetch到另一个 Git 将获取新提交(但不会重新获取旧提交,因此速度很快)并更新 Git 对其 Git 分支名称的内存。

  • 要执行工作,您需要创建或更新自己的分支名称。

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

最新更新