首页 > 解决方案 > Git 分支未显示所有分支

问题描述

我是使用 Git 的新手,我从 GitHub 克隆了一个分支,当我输入git branch. 完成我的工作后,我成功地将它推送到了一个新的分支。之后,我将文件夹复制到另一个目录(因为我想备份以避免冲突),输入它,然后键入git branch. 只显示了 3 个分支,知道我在 Github 上有 4 个。

我试图通过克隆一个新文件夹(键入git clone -b <branch-name> <repo-link-https>)中的分支来解决这个问题,现在只有我克隆的分支出现了..

请问有什么建议吗?

标签: gitgithubgit-branch

解决方案


当您克隆现有存储库时,您的 Git 会创建一个新的和不同的存储库,并将原始存储库中的所有1提交任何分支都复制到这个新存储库中。最后一步创建一个分支。这个分支名称是的,不是他们的;它的拼写与他们的名字之一相同。git clone

当您使用您的克隆(一个不同的存储库)时,您可以向它添加越来越多的分支。如果您将原始存储库中的所有相同分支添加到其中,那么您现在拥有它们的所有提交及其所有分支名称(作为您自己的分支,请注意)。但在那之前,你只是拥有他们所有的提交。没关系,因为 Git 与分支无关。Git 是关于提交的。


1确切的描述比这要复杂得多,但将其视为“复制他们的所有提交而不复制他们的任何分支”会让你开始。


我试图通过克隆一个新文件夹(键入git clone -b)中的分支来解决这个问题,现在只出现了我克隆的分支..

当你创建一个新的克隆时——这又是一个的存储库,你可以在其中获得以前存储库的所有提交,但还没有它的分支——命令的最后一步git clone是运行创建一个分支git checkoutgit switch命令2。该-b标志的存在是为了让您可以告诉您的 Git要复制它们的哪个分支名称,作为最后一步。如果你省略了这个-b标志,你的 Git 会询问他们的 Git 存储库——你正在克隆的那个——他们推荐哪个分支。但无论哪种方式,您都只会得到一个分支。

您实际上不需要任何分支名称来在 Git 中工作。但是,您确实需要某种名称,而分支名称是这里最好的名称。这就是为什么您的 Git 在git clone流程结束时会独树一帜的原因。您命名的每个名称都会为您提供更多的工作机会。

要了解发生了什么,请继续阅读。如果您对您的直接问题已得到解答感到满意,您可以在此停止。


2git switch命令最初是在 Git 2.23 版本中添加的,用于将过于复杂的git checkout命令拆分为两个单独的命令,git switch以及git restore. 现存git checkout遗迹;您可以使用它来代替两个新的、更简单的命令。不过,新的简化命令在某种意义上更安全:该git switch命令试图非常安全,就像git checkout它复制的一半一样。然而,这个git restore命令是故意不安全的,因为它会不可挽回地破坏工作;它复制git checkout. 因此,如果您使用git checkout,当您认为您正在调用“安全地做事”一半时,您可能会意外地调用“破坏我的工作”一半。


Git 是关于提交的

要了解 Git 在这里做什么以及为什么这样做,首先要了解 Git 本身就是关于提交的事实。这与分支无关,尽管分支名称可以帮助您(和 Git)找到提交。它与文件无关,尽管提交包含文件。这真的是关于提交:Git 所做的一切都是为了保留和添加提交。提交是事情的开始,也是其他一切的目的。

这意味着了解什么是提交如何命名特定提交以及如何进行提交至关重要。让我们从名字开始。

提交的真实名称是其哈希 ID

你可能认为一个分支名称会命名一个提交——它确实是这样,但是是间接的。事实上,每个提交都由它的编号命名。每个提交都有一个唯一的编号。没有其他提交可以拥有该编号:一旦做出该提交,该编号将分配给提交。因为该提交永远占用了这个数字,所以这个数字必须非常大,而且确实如此。目前,每个 Git 提交都会获得 2 160个可能的数字中的一个。3 这个数字以十六进制表示为一个大而难看的字符串e31aba42fb12bdeb0f850829e008e1e3f43af500(这是 Git 本身的 Git 存储库中的实际提交)。

这个数字总是有效的:如果你有这个提交,那就是它的数字,并且git show e31aba42fb12bdeb0f850829e008e1e3f43af500会显示它,例如。您通常可以将数字缩写为前四个字符(如果这是明确的话),因此如果您有 Git 的 Git 存储库的克隆,git show e31aba42fb12bdeb0f850829e008几乎可以保证工作。但git show e31a不是因为它可能是这个提交的缩写,或者是 commit e31a17f741...,例如。虽然e31ab今天有效,但随着更多提交的添加,它可能会停止工作。

这些数字看起来是随机的,但并非如此。事实上,每一个都是提交完整内容的加密校验和。4 Git 在提取其任何内部对象(包括提交)时会进行双重检查,校验和是否仍然匹配,以检测存储故障:您告诉 Git 通过哈希 ID 查找提交(或其他对象),它会检查哈希 ID 仍然匹配。因此,这反过来意味着任何提交的任何部分——或 Git 的任何其他内部对象——都不能更改。您可以创建的,每个都有一个新的和不同的 ID,但这不会影响现有的,它们保留在存储库中。


3有计划重做编号系统以使用 2 256个号码,并进行某种丑陋的过渡。

4事实上,Git 的所有内部对象都使用这种方案。这意味着所有保存的对象都会一直冻结。例如,这就是 Git 冻结和删除重复文件内容的方式。


提交中有什么

既然我们知道了一种——而且是最深入的——通过哈希 ID 来查找提交的方法,现在是时候查看每个提交中的内容了。每个提交有两个部分:

  • 提交包含所有文件的完整快照。这是大多数提交的主要数据(通常也是存储库的大部分)。每个文件都存储为一个内部blob 对象,使用相同的哈希名称编码技巧。这会自动对文件进行重复数据删除,因此如果您连续进行一百次提交,主要是重复使用他们的大部分文件,它们实际上并不会占用任何额外的空间。

  • 每个提交还包含一些元数据,或有关提交本身的信息:例如,谁提交、何时提交以及为什么提交。“为什么”部分是您的日志消息:您自己稍后对自己和/或其他人的解释。为什么这个提交比上一个更好?或者至少,如果它不一定更好,为什么它会有所不同。这个特定提交的目标可能是修复一些错误,或添加一些新功能,或准备好添加新功能或其他任何东西。提交本身有更新的源代码,但不一定有关于更新应该修复的错误的任何内容。这是你解释的机会。

Git 为您生成并稍后使用的元数据,您很少直接看到,那就是:每个提交都包含其前一个提交的原始哈希 ID。此字符串一起向后提交,形成以最新提交结尾的提交链。

我们可以画这个。想象一下,我们有一个只有三个提交的存储库。我们将使用单个大写字母代替真正的哈希 ID,而不是代表提交。第一个提交是A,下一个是B,第三个提交是 commit C

A <-B <-C

由于 commitC是最后一个,它B的元数据中有较早的 commit 的哈希 ID。我们说那C 指向 B。同样,commitB指向A. 由于A是第一次提交,它缺少这个向后的箭头:它没有指向任何地方。Git 将此称为(或)根提交。这是我们停止向后工作的地方。

我刚才提到,每个提交都有每个文件的完整快照。但是,如果您让 Git显示提交,Git 会向您显示更改的内容。Git 如何以及为什么这样做?

为什么可能是最容易解释的。如果您想查看提交中的所有文件,只需提交即可。Git 会将所有这些文件从提交中复制出来——记住,它们以特殊的冻结 Git 格式存储,经过去重(和压缩)——到普通的普通计算机文件中。您可能拥有一堆比 Git 更胜任的文件查看器:它们可以将图像显示图像,在文本编辑器中打开文本文档,使用 PDF 查看器打开 PDF,等等。但是您的文件查看器可能无法将整个快照与之前的整个快照进行比较。吉特可以

Git 可以很容易地将快照C与快照进行比较B,因为 commitC持有 commitB的哈希 ID。所以 Git 可以提取两个提交。此外,由于 Git 对文件进行重复数据删除的方式,Git 可以立即知道——甚至不必费心提取——重复文件。Git 只需要提取和比较不同的文件。Git 会这样做,并将构建一组更改,将旧文件转换为新文件。这就是 Git 将向您展示的内容:这组指令。

(请注意,Git按需创建指令集。在您要求 Git 比较任何两个提交之前,Git 所拥有的只是两个快照。您可以根据传递给比较命令的选项获得不同的指令集。例如, Git 可以根据单词进行差异检查,或者忽略某些类型的空白更改。Git 在这里的能力并不总是像我们希望的那样好,但是我们可以使用一些技巧。它们超出了范围不过,对于这个特定的答案。)

按分支名称查找提交

我们已经知道,如果我们记住大而丑陋的哈希 ID(或将它们写下来),我们可以使用它们来查找提交。但这是荒谬的。我们有一台电脑。为什么我们不让计算机为我们写下哈希 ID?

这就是分支名称的作用。但这有点偷偷摸摸。分支名称的真正作用是仅存储最后一次提交的哈希 ID。让我们再次绘制那个三提交存储库,并添加一个名称,main来标识最后一次提交:

A--B--C   <-- main

在这里,我们无需尝试记住 C的哈希 ID,而是只知道该名称main为我们做了这些。所以git checkout main(2.23 之前的 Git)或git switch main(2.23 及更高版本)为我们提供了最新的提交——当前C——不管它有什么哈希 ID。

我们现在可以添加一个也指向 commit的新名称C

A--B--C   <-- main, dev

现在我们还需要一件事:我们使用这些名称中的哪一个?现在,这并不重要,因为两个名称都选择了 commit C。但是让我们将特殊名称附加HEAD到两个分支名称之一,如下所示:

A--B--C   <-- main (HEAD), dev

如果我们git switch dev,我们将特殊名称重新附加HEAD到 name dev,如下所示:

A--B--C   <-- main, dev (HEAD)

现在让我们做一个的提交。不用担心我们如何进行新的提交,让我们假设一切都完成了。这个新的提交D必然会指向现有的提交C,因为我们是D C. 所以看起来像这样:

A--B--C
       \
        D

但是D现在是最新的提交,所以 Git 必须更新一个name。它应该更新哪个名称?答案很明确:它应该更新HEAD附加到的那个:

A--B--C   <-- main
       \
        D   <-- dev (HEAD)

我们现在有两个分支名称,这两个名称指定了两个不同的“最新”提交。最新提交mainC,最新提交devD。commitD点回commit C,哪个回指向B,哪个回指向A;所以所有四个提交都分支上dev,而其中三个在上main

如果我们切换回名称main并在那里进行新的提交,我们会得到:

        E   <-- main (HEAD)
       /
A--B--C
       \
        D   <-- dev

这意味着我们现在有三个在两个分支上共享的提交,一个仅 on 的main提交和一个仅 on 的提交dev。现在我们需要 两个名称来查找所有五个提交;一个名字会找到一个提交,它会找到三个共享的提交,但是我们需要另一个名字来找到最后一个剩余的提交。

请注意,分支名称move。事实上,当我们进行新的提交时,它们会自动移动:任何HEAD附加到它的分支名称都会自动移动以包含新的提交。那时所有其他分支名称都保留在原位,但是因为它们是我们的分支名称,所以我们可以控制。我们可以让我们的 Git 随时移动这些名称。唯一的限制是我们必须有一个提交才能将名称移动到。

克隆创建远程跟踪名称

当我们克隆其他人的存储库时,我们得到了他们的所有提交,但没有得到他们的分支。这是如何运作的?好吧,假设我们有上面的内容,有两个实际的分支名称main并分别dev选择提交ED。我们现在创建一个的存储库,我们复制所有五个提交,给我们:

        E
       /
A--B--C
       \
        D

我们确实需要两个名称来查找所有提交。但是我们不需要分支名称。另一个 Git 与另一个存储库一起工作,具有分支名称,因为这些是在进行新提交时会移动的分支。所以我们的 Git 所做的就是复制他们的名字改变他们。我们让 Git 获取他们的分支名称并创建我们的远程跟踪名称,方法是在名称中添加一些东西——通常是origin/——。5 所以我们得到:

        E   <-- origin/main
       /
A--B--C
       \
        D   <-- origin/dev

Git 将拒绝将特殊名称附加HEAD到这些远程跟踪名称之一。 HEAD只允许附加到分支名称。所以我们的最后一步git clone是使用-b选项或他们的建议,从这两个名称中选择一个,并从中创建一个分支名称,如下所示:

        E   <-- main (HEAD), origin/main
       /
A--B--C
       \
        D   <-- origin/dev

请注意,我们的分支名称选择与我们根据分支名称创建的远程跟踪名称相同的提交。但是我们现在只有一个分支名称,而不是两个。如果我们运行:git clone

git switch dev

这使用了 Git 提供的特殊功能,可以找到它们origin/dev并创建我们自己的新名称dev

        E   <-- main, origin/main
       /
A--B--C
       \
        D   <-- dev (HEAD), origin/dev

现在我们有两个分支名称。但我们一开始没有。请注意,我们现在还D签出了提交,而不是 commit E,因为git switch(或者git checkout,如果我们使用它)不仅切换分支,而且选择分支名称标识的提交,作为要签出的提交,因此可供我们使用。


5从技术上讲,远程跟踪名称位于单独的命名空间中。我们的 Git 不只是origin/在前面,它替换refs/heads/refs/remotes/origin/. 这个名字origin实际上是一个遥控器,我们可以在我们的 Git 存储库中拥有任意数量的遥控器。但这是另一个问题的话题。


推荐阅读