1

我的 Git 中有一个分离的头的问题。我对上一次提交进行了结帐。在那之后,我们做出了承诺。所以在我为此提交创建分支之后。

git checkout -b detached-head-after-gitlab-crush

所以在那之后,我进行了更改并提交了更改。

但是现在,当我写git branch

* (detached from a71c5ea)
  detached-head-after-gitlab-crush
  master

所以我想了解当前分支是什么,以及它是如何创建的。

我如何对最后一个分支进行更改,并且我不能将这个当前分支推送到原点。

git log --all --decorate --oneline --graph

显示以下结果:

* 548af67 (HEAD) Images were changed, and issue with Printing Gate entry records
* be89a73 (origin/detached-head-after-gitlab-crush, detached-head-after-gitlab-c
* 6979cba Files before solving HEAD detached problem
* fb89a62 Rules added, made some changes which I don't remember
| *   d4183f3 (origin/master, origin/HEAD, master) Merged files
| |\
|/ /
| *   3c3cadc Merge branch 'master' of http://gitlab.sdu.edu.kz/sdu/portal

4

2 回答 2

4

让我们先快速回顾一下您已经知道的事情:

  • 任何 Git 存储库中的基本存储单元都是commit。提交中有更小的单元——例如,提交包含文件,有点类似于原子如何保存质子、中子和电子——但提交本身就是你应该使用的容器。(在这个类比中,我们想做化学,而不是核物理。)

  • 每个提交都有自己唯一的哈希 ID。这些哈希 ID 又大又丑,很难让人使用,所以 Git 有时会缩短它们以显示:例如,548af67(它是更长的东西的缩写),be89a73(又是 40 个字符长的东西的缩写) , 等等。我从你的git log --all --decorate --oneline --graph输出中得到了这些。各地的每个 Git 存储库都同意这些哈希 ID 是为这些特定提交保留的,即使该存储库没有这些提交也是如此。

    始终可以使用原始哈希 ID 来引用您在自己的存储库中的任何提交。

  • 提交本身包含:

    • 数据:所有文件的快照。这与之前的提交没有区别。它是每个文件的完整副本。(这些文件以压缩、冻结、只读、Git-only 格式存储。因为它们只读的,所以可以共享。例如,如果您的大多数提交都有一个README文件,并且只有三个版本README90 次提交中,每 30 次提交更改一次,然后一个内部 Git 格式的 README 冻结副本服务于前 30 次提交,另一台服务器服务于接下来的 30 次,依此类推。)

    • 元数据:有关提交的信息例如提交人(姓名和电子邮件地址)、提交时间(日期和时间戳)以及提交原因(提交日志消息)。在此元数据中,每个提交都可以列出一些先前提交的原始哈希 ID。


    大多数提交准确地列出了一个先前的提交哈希 ID。列出的提交是这个提交的提交,即在这个提交之前的提交。一些提交列出了多个先前的提交,即,有多个父级。每个非空存储库中的一次提交是该存储库中的第一次提交,因此不列出任何父级。

每当 Git 有权访问一个提交时,Git 都可以查看该提交的父级(或父级),因此可以向后工作到前一个提交。这使 Git 可以访问父提交。所以现在 Git 可以找到另一个父级——这个父级的父级,即我们刚才的提交的祖父级——当然那个提交有一个父级。因此,Git只需从最后一次提交开始并向后工作,就可以找到整个历史记录。

分支名称找到特定的提交

但是提交哈希 ID看起来是随机的,并且是不可预测的。您和 Git 如何快速轻松地知道哪个提交是最后一个?这就是分支名称的来源。分支名称类似于masterdetached-head-after-gitlab-crush存储一个提交哈希 ID。根据定义,该哈希 ID 是该分支中的最后一次提交。

让我们使用大写字母来代表实际的提交哈希 ID。我们会很快用完,这是 Git 不使用简单大写字母的原因之一,但它对我们的绘图来说是可以的。假设我们的存储库非常新,并且只有三个提交。第一个是 commit A,因为它第一个,它没有父级:

A

我们将调用第二次提交B。它将第一次提交的哈希 ID 记住为其父级。所以我们会说 commitB 指向commit A,然后画成这样:

A <-B

当然, commitC包含 commit 的哈希 ID B,因此C向后指向B

A <-B <-C

为了C快速查找,Git 将其哈希 ID 存储在name master中:

A--B--C   <-- master

(此时我们有点累了,变得懒惰了,将 commit 到 commit 的连接画成线,而不是箭头。请记住,它们仍然是箭头,它们来自 child 并指向 parent,永远不会从父级到子级。 每次提交的所有部分都被永久冻结,包括从其中出来的箭头,所以我们不能稍后返回并添加一个向前指向的箭头:我们进行了提交,它有给它的父母一两个向后的箭头,从那时起我们就被困住了。孩子们知道他们的父母是谁,但父母永远不知道他们的孩子是谁。)

现在我们有了这个,让我们在这张图片中添加另一个分支名称。而不是拼写crashcrush我将称之为develop

A--B--C   <-- master, develop

现在让我们向我们的集合添加一个新的提交。我们使用 Git 中的常规流程来实现这一点。D无论 Git 提供什么哈希 ID,我们都将调用新的 commit 。新提交D将指向现有提交,因为我们通过签出 commitC开始工作。因此,一旦制作完成,它将如下所示: DCD

A--B--C
       \
        D

D向上和向左指向CC指向B,等等。

这是HEAD进来的地方

我们现在有一个问题。我们有两个分支名称。 哪一个应该记住新的提交D

为了告诉 Git 是哪一个,我们会将特殊名称(全部大写)附加到两个现有分支名称之一。HEAD假设我们在进行新提交之前有这样的安排D

A--B--C   <-- master (HEAD), develop

然后我们会得到这个:

A--B--C   <-- develop
       \
        D   <-- master (HEAD)

但如果这不是我们想要的,我们应该git checkout develop首先。然后我们将有:

A--B--C   <-- master, develop (HEAD)

当我们进行新的提交时D,我们会得到:

A--B--C   <-- master
       \
        D   <-- develop (HEAD)

无论哪种方式,我们都会得到相同的提交集。不同之处在于,当 Git进行新提交时,它将新提交哈希 ID 写入该名称HEAD附加到的任何分支名称。然后该分支名称会自动指向新的提交。

事实上,新提交的HEAD提交是之前指向的提交的分支名称。根据定义,这就是我们的提交。我们使用了git checkout masteror git checkout develop,但无论哪种方式,我们都选择了现有的 commit C

未附加到分支名称时会发生分离的HEADHEAD

现在我们有了:

A--B--C   <-- master
       \
        D   <-- develop (HEAD)

我们可以继续进行更多新的提交:

A--B--C   <-- master
       \
        D--E--F   <-- develop (HEAD)

例如。但如果我们愿意,我们可以把我们的头拿掉。Git 有一种模式,我们可以直接HEAD指向任何现有的提交。例如,假设出于某种原因,我们想直接提出我们的观点 commit :HEADE

A--B--C   <-- master
       \
        D--E   <-- HEAD
            \
             F  <-- develop

我们现在可以进行一个的提交——我们称之为它G——它将指向现有的提交E。Git 会将新提交的哈希 ID(无论它可能是什么)写入分离的 HEAD 中,从而为我们提供:

A--B--C   <-- master
       \
        D--E--G   <-- HEAD
            \
             F  <-- develop

这种模式本质上没有任何问题,但它使以后的事情变得更加困难。假设我们想C再次查看提交。我们可能会跑git checkout master。这会将HEAD名称master再次附加到名称:

A--B--C   <-- master (HEAD)
       \
        D--E--G
            \
             F  <-- develop

你将如何找到commit G?我们可以C很容易地找到:这是我们当前的提交和名称HEAD,并且master都找到了它。我们可以B通过C返回一个找到。我们无法D从中找到C,但我们可以F名称 develop中找到。从F,我们可以后退到E,从那里到D。但是我们不能前进。Git 的所有箭头都指向后面。不再有一种简单的方法可以找到 commit G

解决方案是我们从G. 这就是您之前在创建 name 时所做的detached-head-after-gitlab-crush。如果我们知道G(例如,如果它仍在屏幕上)的哈希 ID,我们可以用另一种方式做同样的事情:

git branch save-it <hash-of-G>

会成功的:

A--B--C   <-- master (HEAD)
       \
        D--E--G   <-- save-it
            \
             F  <-- develop

现在我们可以使用 commitC一段时间,甚至可以进行一个新的提交H,使master更改指向H

A--B--C--H   <-- master (HEAD)
       \
        D--E--G   <-- save-it
            \
             F  <-- develop

我们所要做的就是返回Ggit checkout save-it它附加HEAD到名称save-it(仍然指向G):

A--B--C--H   <-- master
       \
        D--E--G   <-- save-it (HEAD)
            \
             F  <-- develop

你需要做的是找出为什么你总是让你的 HEAD 脱离

虽然 Git 中的分离 HEAD 模式根本没有问题,但它很难使用。您必须手动创建和/或更新分支名称以记住您的提交。

每当您告诉 Git 时,Git 都会进入此分离的 HEAD 模式:

git checkout --detach master

例如说“我想使用由 标识的提交master,但我想在分离的 HEAD 模式下执行此操作”。

每当您要求它通过原始哈希 ID 或任何分支名称的名称签出(或切换到,使用新的 Git 2.23 及更高版本)提交时, Git 也会分离HEAD。这包括远程跟踪名称(如)和标签名称(如果您已创建标签)。git switchorigin/masterv1.2

某些命令,包括特别是git rebase,将在运行时暂时分离 HEAD。如果他们无法完成,以至于您处于 rebase 的中间,他们将停止并让您处于这种分离的 HEAD 模式。然后,您必须选择是完成变基还是使用 完全终止它git rebase --abort。(如果你不想做其中任何一个,你会有点卡住:你真的必须做其中一个。)

所以:找出你为什么一直进入这种分离的 HEAD 模式。你在做什么导致它?当你处于分离HEAD模式时git branch,或者如果你不需要记住你在哪里现在——如果你故意查看一个历史提交,你可以并且可能确实找到了使用,例如,只需使用或将你的 HEAD 重新附加到现有的分支名称。但是除了那些你确实想要一个分离的 HEAD(使用标记的提交或查看历史提交)的特殊情况,或者在你完成之前处于分离的 HEAD 模式的基础上工作的情况,你可能不'吨git checkout -bgit switch -ccgit loggit checkoutgit switch在分离的 HEAD 模式下工作。所以,不要那样做!

于 2020-01-22T17:56:23.577 回答
1

从您的 git branch 命令的结果来看,您处于提交的分离头部,并且您只创建一个没有分离提交的新分支。

要从以前的提交创建分支,您有 3 种方法:

  • 您可以通过哈希创建分支:

git 分支分支名称 sha1-of-commit

  • 或者通过使用符号引用:

git branch 分支名 HEAD~5

  • 要在创建分支时签出分支,请使用

git checkout -b 分支名称 sha1-of-commit 或 HEAD~3

于 2020-01-22T12:17:15.740 回答