0

我正在尝试做一个git rebase掌握。我有28变基。所以,在某些阶段,我会遇到冲突。我做了调整,然后我做git status了,修改后的文件出现了。但是,当我这样做时,有时文件会从列表中git add {filename}消失。modifiedchanges to be committed

是因为一些git错误还是因为我无意中使代码与master分支相同?

4

1 回答 1

2

[消失状态] 是不是因为我无意中让代码与master分支相同?

可能——尽管“无意”可能是错误的;也许你是故意这样做的,却没有意识到这你的目的。master但是,说“与分支相同”并不完全正确。正如j6t 在评论中所说,这意味着该文件现在与HEAD提交相同。

在我们讨论细节之前,让我回到这个:

但是,当我这样做时,有时文件会从列表中git add {filename}消失。modifiedchanges to be committed

让我们来看看git status实际做了什么。首先,让我们定义工作树索引和一般的提交,特别是HEAD提交。然后,让我们看看 Git diff 是什么。然后我们可以进入git status并查看 的过程git rebase

为此,请记住文件树(或只是)是文件的集合,从顶级目录(或“文件夹”,如果您更喜欢该术语)开始,其中可能包含其他子目录(“子文件夹") 以及包含文件。是具有所有内容的顶级目录:所有它自己的文件,加上任何子树及其文件,以及任何子子树等等。

工作树、索引、提交和HEAD

你的工作树就是这样:你工作的树(目录)。它具有您的编辑器和计算机其余部分可以使用的正常格式的所有文件。(它也可以有不参与 Git 的文件:这些被称为未跟踪文件。如果您将源代码构建为目标代码,或者将 Python 转换为字节编译*.pyc文件,例如,这些文件将保留为仅工作树,即,未跟踪,故意的。)

索引——也称为暂存区,有时也称为缓存——只是你构建下一次提交的地方。Using将工作树中的给定内容git add <path> 复制<path>到索引中,替换之前存在的文件版本。当您最终运行 时git commit,Git 会将索引中的任何内容(包括任何子目录及其文件以及所有顶级文件)转换为新的提交。1

提交是 Git 存在的主要原因。每个提交存储一棵树。该树是您提交时索引中的内容的快照。每个提交还存储一些元数据。我不会在这里完全定义这个术语,而只是使用每个提交的实际元数据示例。这些是:

  • 树本身。(快照是一个独立于提交的实体。我们在这里并不真正需要关心它,但稍后会很重要,我们不妨适当地描述一下。)
  • 提交 ID列表,通常只有一个 ID。这是您进行新提交之前的提交。
  • 作者:姓名、电子邮件地址和时间戳。与之前的提交相比,这是编写新代码、新文本或任何关于此提交的“新”内容的
  • 提交者:与作者相同的想法,只是第二个人,以防写新提交的人不是运行的人git commit。例如,通过电子邮件发送的补丁会发生这种情况。
  • 一条日志消息。这是自由格式的文本,旨在为做出提交的人提供一个很好的描述,说明他们做出此提交的原因。

因为每个提交都存储了之前提交的 ID,所以一系列或一系列提交让我们可以查看开发历史:

A <- B <- C   <-- master

这里 commitC是最新的master. (它的实际 ID 是一些大而丑陋的 SHA-1 哈希,badf00daddc0ffee...或其他什么。) CommitC的哈希 ID 为 commit B,它让 Git找到commit B,并且BID 为A. 这个名字master是 Git 如何找到 commitC的。

总是有一个HEAD提交。2 这是您当前的提交。通常,这也是某个分支的尖端:例如,通常你可能是on branch master,正如git status所说的那样,然后HEAD会决定提交C。但是您可以HEAD指向其他一些提交,在这种情况下,HEAD它只是“当前提交”。

进行提交会将索引转换为快照(树)并使用该树进行新提交。新提交的父级是 old HEAD,然后 Git 更新HEAD以使其指向提交。如果你在一个分支上,Git 通过使分支名称指向新的提交来进行更新:

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

如果您不在分支上,则HEAD实际上包含原始提交 ID。在这种情况下,git commit将新的提交 ID 直接写入HEAD. (这就是在你的冲突期间发生的事情git rebase,这就是我提到它的原因。)但无论如何,看看D这里的 commit 是如何指向 commitC的:新的快照总是引用前一个快照。

同样,HEAD提交始终是当前提交。当我们进入 rebase 操作时,我们将需要这个。


1这不是很精确。如果递归地展平一棵树,就会得到索引。这使得将索引转换为树变得容易(ish)——所以这就是 Git 在这里所做的:它将索引转换为树,使用git write-tree. 这使 Git 成为那些丑陋的 SHA-1 哈希 ID 之一。然后 Git 使用这个哈希 ID 进行新的提交。通过将索引复制到树,然后将树 ID 放入提交中,Git 最终将索引的内容保存为新提交的快照。

2这条规则有一个例外。由于初始的空存储库没有提交,因此需要此异常。显然,如果没有提交,就不可能解析HEAD为提交哈希 ID。但是,出于我们的目的,我们不需要关心“孤儿”或“未出生”分支的这种特殊情况。


git diff, 和两对三棵树

虽然git diff很多选项和使用模式,但最简单和最直接的方法是比较两棵树。一棵树被标记a,另一棵被标记b。diff 本身由一组指令组成,这些指令主要是这样的:“要更改a/README.txtb/README.txt,请删除现在存在的第 12 行,并在第 12 行插入另一行。这也是第 12 行周围的一些上下文。” 这意味着有问题的文件被命名README.txt并且位于树的顶层——如果它在某个子树中,例如,输出将显示a/subdir/README.txtand b/subdir/README.txt

两棵树中的一棵通常是您的工作树。您也可以像使用树一样使用索引。或者,您可以将任何提交(例如 HEAD(当前)提交)用作树;Git 只是找到与该提交相关的快照树。

我们通常只想要一个文件名列表,而不是获得一组指令,“这是如何更改 README.txt”、“这是如何更改 main.py”等等。git diff我们可以通过使用--name-onlyor得到这个--name-status。该--name-only标志告诉它只打印 name:README.txtmain.py. 使用也会--name-status添加一个状态M对于已修改、A对于新添加等等。

请注意,给定任何普通的快照提交,通过一个父提交,我们可以git diff针对其(单个)父提交。这将向我们展示该提交中发生了什么变化。就是这样git show做的git log -p:他们打印一些关于提交的信息,然后git diff针对提交的父级运行。

但是,无论如何,git diff一次只比较棵树。3 但是现在你已经准备好运行git commit了,实际上你已经拥有了三棵树

  1. 您的 HEAD(当前)提交;
  2. 你的索引;和
  3. 你的工作树。

能够比较所有三个将是很好的。输入git status


3实际上,git diff 可以比较两棵以上的树,产生所谓的组合 diff。该git show命令为合并提交执行此操作(git log -p通常只是跳过它们,差异)。但这很棘手,更重要的是,它不会做我们想要的git status


git status

什么git status是运行两个 git diffs。每个都有一个轻微的--name-status应用变体。

第一个差异是HEADvs 索引。当前提交和您的索引之间的这个差异是“要提交的更改”。请记住,这git commit会将索引写入新提交。如果我们现在这样做——如果我们将当前索引变成一个新的提交——然后将那个提交与当前提交进行比较,我们会看到git log -pgit show将显示什么。这些将是我们提交的更改。这就是这部分所git status展示的。

它不打印实际的差异,只打印文件名和详细状态(例如,modified而不是仅仅M)。如果我们想要实际的差异,我们必须运行git diff --cached. 这 - 它使用旧的“缓存”名称作为索引 -HEAD与索引进行比较。

向我们展示了这一点,git status现在运行第二个 git diff。这将索引与工作树进行比较。如果有我们尚未git add编辑的文件,这将向我们显示这些文件是哪些文件。同样,我们看不到实际的差异,只有文件名和状态。如果我们想要实际的差异,我们必须运行git diff,它比较索引与工作树。由于这些是我们尚未修改的更改git add,因此第二种--name-status风格的差异git status显示了我们可以做到 git add的。一旦我们这样做git add了,它们就会在索引中,所以这个 diff fromgit status将停止提及文件。

如果我们改变一些东西,然后把它改回来呢?

请注意,在所有这些过程中,我们仍然得到两个单独的差异:HEAD-vs-index 和 index-vs-work-tree。如果我们直接使用HEAD-vs-work-tree 会怎样?

好吧,git status不会那样做,但我们可以:我们可以运行git diff HEAD(没有--cached这个时间)。与往常一样,我们可以使用它--name-status来获取文件名和状态,或者将其保留以获取完整的差异。

现在,假设git statusREADME.txt有要提交的更改,并且README.txt没有为提交暂存的更改。这意味着HEAD-vs-index 不同,index-vs-work-tree 不同。但是如果第一个变化——<code>HEAD vs index——是,比如说:

-the color purple
+the colour purple

(即,我们去了英式拼写)。如果从索引到工作树的第二个更改是:

-the colour purple
+the color purple

(即,我们改回美式拼写)。如果我们比较HEAD与工作树,使用git diff HEAD,我们根本看不到任何变化!

如果在这一点上,我们git add README.txt将从“要提交的更改”“未暂存以提交的更改”变为没有更改。这就是你所看到的。

变基是重复的樱桃采摘

git rebase命令非常类似于重复许多单独的git cherry-pick命令。记住我们在上面绘制的那些图,在master. 让我们画一个更大的图,带有一个侧枝:

...--D--E--F       <-- master
         \
          G--H--I--J--K   <-- sidebr

注意master点提交F,而sidebr点提交K。有五个sidebr未提交的提交master。(提交E和之前的提交都在 sidebr master。这对 Git 来说有点特殊。)要重新定位 sidebrmaster,我们需要让 Git复制这五个提交中的每一个。

复制一个提交的 Git 命令是git cherry-pick. 它复制一个提交的方式是将其转换为差异,通过将其与其父提交进行比较,然后将该差异应用到您希望将其复制到的位置。我们想要复制G并让副本紧跟在 之后F,如下所示:

             G'  <-- HEAD
            /
...--D--E--F       <-- master
         \
          G--H--I--J--K   <-- sidebr

新副本——新提交——“相似G但略有不同”,所以我们称之为G'. 一旦我们有了G',我们接下来要复制,然后H有新的副本: G'

             G'-H'  <-- HEAD
            /
...--D--E--F       <-- master
         \
          G--H--I--J--K   <-- sidebr

我们想重复这个序列,直到我们复制KK'

             G'-H'-I'-J'-K'  <-- HEAD
            /
...--D--E--F       <-- master
         \
          G--H--I--J--K   <-- sidebr

一旦它们都被复制,我们想要的最后一件事——最后一步git rebase——是移动分支标签sidebr以指向我们复制的最后一个提交,放弃旧链:

             G'-H'-I'-J'-K'  <-- sidebr (HEAD)
            /
...--D--E--F       <-- master
         \
          G--H--I--J--K   [abandoned]

现在,在所有这些挑选过程中,有可能其中一个提交(甚至其中许多提交)中的某些内容已经在 commit 中完成F。在这种情况下,由于我们将通过扫描链获得的更改应用于从 start 派生的快照F,因此我们将遇到精心挑选的提交无法正确应用的情况。

解决冲突可能会导致删除更改:它不需要作为更改,因为它已经在新的base中。HEAD在这种情况下,我们将停止从我们成功复制的最后一次提交到我们的索引的任何更改。

如果我们最终从其中一个提交中删除所有更改,我们将拥有 Git 喜欢称之为“空”提交的东西。(这些实际上不是空的,它们与之前的提交相同。不是空的提交,而是空的git log -p 补丁。)Git 默认情况下不会进行空提交,所以对于这些情况,我们必须使用git rebase --skip而不是git rebase --continue. Git 试图提前弄清楚是否会有这样的“空副本”,如果有,就提前跳过它们。但有时它无法弄清楚——当我们到达那里并解决冲突时,我们才发现跳过是正确的。

我总是觉得有点可疑:我真的正确解决了这个问题吗?变化真的在新基地吗?值得查看git log新基地的结果,以确保您确实正确解决了冲突。但它可能是正确的;毕竟这可能是故意的。

于 2017-02-14T21:06:47.017 回答