【问题标题】:Git index messed upGit索引搞砸了
【发布时间】:2022-01-05 21:13:59
【问题描述】:

我不小心提交了一个大的 .psd 文件,然后卡住了我的推送过程。

因此我将 *.psd 添加到我的 gitignore 中,然后尝试删除此提交,因为它仍在尝试推送一个现在不存在的 .psd 文件。

在我做一些 git 软重置时,我弄乱了我的 git 索引,现在我的一半项目文件带有红色标记为“索引已删除”。

不管我是否执行 git git add .,这些文件都不再被索引,我该怎么办?

【问题讨论】:

  • 你可以使用git reflog 恢复良好状态吗?然后尝试从 .gitignore 中删除 .psd 文件,看看它是否出现?然后在那个文件上git rm --cached
  • git reflog 向我显示了一个我想恢复的提交,但是当我尝试恢复时,它说它失败了,因为它会覆盖我的更改。考虑到 git rm --cached,psd 文件已经被删除了,但是我还有很多文件没有被索引,我不能再推送它们了。
  • 您在使用git reset --hardgit reflog 吗?您可能对您可能不想丢失的分支的当前状态进行了实际修改,因此提交这些并标记它们,以便您可以取回它们。你想重置到这个提交有多远?
  • 这能回答你的问题吗? How to remove/delete a large file from commit history in the Git repository?(还有很多关于这个主题的其他问题,只需点击“相关问题”链接即可。)

标签: git indexing gitignore


【解决方案1】:

链接的问题 (How to remove/delete a large file from commit history in the Git repository?) 在您修复索引情况之后是合适的。不过,首先,您需要修复索引情况。

你提到:

在某些时候,我正在做一些 git soft reset ...

git reset --soft 不涉及索引(也不涉及您的工作树),但可用于更改存储在HEAD 中的提交哈希ID。如果您这样做了,您可能需要将正确的提交哈希 ID 放回 HEAD,再次使用 git reset --soft 和正确的提交哈希 ID。

这可能足以解决所有问题,因为git statusHEAD(可移动)与当前索引内容进行比较,然后将当前索引内容(可更改)与工作树内容(也是可变)。

关于HEAD、Git 的索引(或“暂存区”)和工作树您需要了解的内容

Git 真的是关于提交。这与文件无关,尽管提交包含文件。这与分支无关,尽管分支可以帮助您(和 Git)find 提交。最后,Git 就是关于commits。所以重要的是提交。但这会给您留下几个问题,包括:

  • 究竟什么是提交?
  • 我们如何找到提交?
  • 我们如何进行新的提交?
  • 我们可以摆脱旧的提交吗?
  • 这个索引是什么东西?

我不会在这里正确地介绍其中的一些内容,以使这个答案更短(或者对我来说更短)。但是让我们从这个开始,关于提交:提交是有编号的。任何提交,一旦提交,就永远无法更改。它们大多是永久性的(但请参阅链接的问题),并且完全只读。

我们(主要)通过操纵现有提交来进行 提交。您可以完全从头开始进行新的提交,但这通常对于除了第一次提交之外的任何事情都太痛苦了。因此,要进行 new 提交,我们必须采用现有的提交,并更改其中的某些内容。 根据定义,这是一个矛盾:提交不能更改,但我们需要更改某些内容才能进行新的提交。我们如何解决这个难题?

答案很简单。 我们不会更改提交。我们将提交复制到我们可以更改的地方,更改that,然后使用that进行新的提交。所以我们不要处理提交:我们处理从提交中复制的东西

几乎所有版本控制系统都做这种事情; Git 与 SVN 或 Mercurial 或这里的任何东西并没有真正的不同,因为我们首先提取一些提交,然后处理它,然后使用它来进行新的提交。

但 Git 在这里有所不同,一开始并没有明显的原因。使用其他版本控制系统,您可以将提交提取到一个工作区域,在那里您可以处理它,仅此而已。在 Git 中,您将提交提取到一个工作区域——您的工作树工作树——但到一个提议的下一个提交。由于历史原因,Git 有 三个名称 用于提议的下一次提交,称其为“索引”或“暂存区”,或者 - 这些天主要出现在 git rm --cached 等标志中的术语 - “缓存”。

然后,您可以像在任何版本控制系统中一样处理工作树中的文件。但是当您对工作树文件感到满意时,您必须在其上运行git add。您不必在 Mercurial 或 SVN 中执行此操作,1 因为在这些系统中,工作树文件文件的建议下一个提交版本。在 Git 中,您必须这样做:git add 命令将文件复制回 Git 的索引,为下一次提交做好准备。


1例外,即对于全新文件。这是因为,例如,Mercurial 有称为“dircache”和“manifest”的东西,它们的作用类似于 Git 的索引,但 Mercurial 将这些隐藏起来,这样您就不必了解它们。相比之下,Git 时不时地抽出它的索引和slaps you in the face with it (Monty Python fish-slapping dance)。您不能允许忽略它。 git commit -a 快捷方式有时几乎可以让您到达那里,但这还不够:您必须了解 Git 的索引。


分支名称查找提交,提交查找提交

正如我所说,提交是有编号的。这些数字看起来是随机的(尽管它们实际上并不是随机的)并且是巨大而丑陋的hexadecimal 字符串。这些通常是人类无法使用的,所以我们不(即使用它们)。这些是 hash IDsobject IDs (OIDs); Git 在任何地方都使用 OID,包括内部。

提交也是由两部分组成的单元。一个部分包含每个文件的快照,以一种特殊的、只读的、仅限 Git 的、压缩和去重的方式存储。重复数据删除处理了大多数提交主要重用早期提交的文件这一事实:这可以防止提交占用大量空间。 (事实上​​,如果您进行新的提交来撤消以前的提交所做的操作,则新提交的存储文件可能根本不占用空间,因为它们现在全部重复了。)你不'不必担心 Git 是如何做到这一点的:这部分效果很好,不会像索引那样让你头疼。

每个提交的另一部分是它的元数据,或关于提交本身的信息。其中包含提交人的姓名和电子邮件地址、一些日期和时间戳以及日志消息等内容。当您进行新的提交时,您提供日志消息,您的 user.nameuser.email 设置提供名称和电子邮件地址。这一切都非常简单,但这里有一部分不是:Git 在这个元数据中添加了一个 父提交 哈希 ID 的列表。对于大多数提交,只有一个父级。

当您进行 new 提交时,您是在处理一些现有的提交。 Git 在您的 new 提交中存储您之前选择的提交的哈希 ID。因此,您的新提交将该提交的哈希 ID 作为其父级。然后 Git 将 new 提交的哈希 ID 写入 当前分支名称

这值得一提。假设我们有以下提交链:

... <-F <-G <-H   <--main (HEAD)

H 代表最近提交的哈希 ID,H 是我们检查的提交。 main 是我们的分支名称名称 main 持有H 的哈希ID,这是Git 找到 @ 987654344@,当我们说git checkout maingit switch main时。

提交H 存储在H 的元数据中,之前G 的哈希ID。我们说H 指向 G,因此图中的箭头来自H,指向G。提交G 因此是提交HGH 都有每个文件的完整快照(具有重复数据删除功能),因此 Git 可以比较这两个快照以查看 GH 之间的更改。而且,G 是一个提交,G 在其元数据中具有 父提交 F 的哈希 ID。 F 指向另一个更早的提交,依此类推。

无论如何,我们现在操作工作树和 Git 索引中的文件,并进行新的提交,它会获得一个新的、唯一的、看起来随机的哈希 ID,我们将称之为 I。新提交 I 指向现有提交 H

... <-F <-G <-H   <--main (HEAD)
               \
                I

git commit 的最最后 步骤是Git 将I 的哈希ID 写入名称 main

... <-F <-G <-H
               \
                I   <--main (HEAD)

所以现在main 指向提交I 而不是提交H

git reset--hard--mixed--soft

git reset --soft 所做的是允许您移动分支名称git reset 所做的通常是......非常复杂。

让我们画一个更复杂更有用的 Git 图:

          I--J   <-- br1
         /
...--G--H   <-- main (HEAD)
         \
          K--L   <-- br2

在这里,我们有一个包含三个分支名称的存储库,mainbr1br2。名称HEAD 当前附加名称main,它选择提交Hbr1br2 名称分别选择提交 JL

如果我们运行git merge --ff-only br1,我们最终会得到:

          I--J   <-- br1, main (HEAD)
         /
...--G--H
         \
          K--L   <-- br2

如果这是一个错误,我们可以运行:

git reset --hard HEAD~2

~2 表示倒数两个第一父链接;我不会在这里详细介绍,也不会涵盖--ff-only 的含义)和我们会回到这个:

          I--J   <-- br1
         /
...--G--H   <-- main (HEAD)
         \
          K--L   <-- br2

好像什么都没发生。这里的--hard 影响了Git 的索引我们的工作树

这是实际发生的事情:

  • 首先,git reset 执行--soft 步骤。我们给它一个提交哈希 ID,例如提交 H 的原始哈希 ID,或者像 HEAD~2 这样的相对提交指令。 the git rev-parse command 将采取的任何东西都可以在这里使用。 Git 找到该提交,例如提交 H。然后,它使 HEAD 附加到的分支名称 指向该提交。所以现在main 指向H

  • 然后,如果我们允许它——如果我们使用--mixed--hard——git reset 会重置 Git 的 索引。它通过删除来自我们正在进行的提交 (J) 的所有文件并安装来自我们移动到的提交 (H') 的所有文件来做到这一点。

  • 然后,如果我们告诉它——如果我们使用--hard——git reset 会重置我们的工作树。对于它从 Git 索引中提取并替换为来自 H 的文件的所有文件,它会将这些文件从我们的工作树中提取出来并替换为从提交 H 中提取的文件。

这就是git reset --hard 让我们回到git merge --ff-only 之前的方式:它:

  • 移动分支名称(--soft);那么
  • 更新 Git 的隐藏索引 / 提议的下一个提交 (--mixed);那么
  • 更新我们的工作树 (--hard)。

使用--mixed--soft 标志只会使git reset 在执行第二步或第一步之后提前停止。

(请注意,git reset 有其他操作模式。如果这是全部,它就不会那么复杂。)

请注意,如果您现在使用git reset 指向提交L,您将:

          I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2, main (HEAD)

Git 的索引和工作树会发生什么(如果有的话)取决于您提供给 git reset 的标志。

(您已重置以存储在 HEAD reflog 中的各种提交的哈希 ID,因此 git reflog 将显示它们。这是一种查找要返回哪个提交的方法返回 到,如果你不小心重置了你现在找不到的哈希 ID。使用 reflogs 找到你丢失的哈希 ID。注意哈希 ID 真的很难记住:你可能想运行 @987654432 @ 或 git log -1 <em>hash</em> 或类似的,在使用 git reset --soft 之前使用剪切和粘贴作为哈希 ID,以找出哪个哈希 ID 包含哪个感兴趣的提交。)

git status 和其他类似的比较器

git status 命令的部分工作原理是运行两个git diffs。

这两个差异中的第一个是:

git diff --staged --name-status

它将任何提交 HEAD 名称(即存储在该提交中的所有文件)与 Git 索引中的文件进行比较。由于这些文件通常是从该提交复制out,因此我们此后未更新的任何文件都将匹配。 Git 不会说任何关于匹配文件的内容。

如果我们确实更新了某个文件(例如,使用git add,我没有在这里介绍),该文件可能不匹配。然后git status 会说文件的索引副本是要提交的更改

如果我们在不改变 index 内容的情况下移动 HEAD(和当前分支名称),我们将使两者不同步,并且许多文件可能会被更改,甚至被删除.例如,如果我们将mainJ 向后移动到H,但不考虑索引,那么所有在HJ 之间不同的文件都会显示出来。

second 比较 git status 确实将 Git 索引中的文件与工作树中的文件进行比较。这很像在没有选项的情况下运行git diff --name-status。对于每个匹配的文件,Git 什么都不说。文件不同的地方——你修改了工作树文件,但还没有在上面运行git add——Git 会将文件列为未暂存的更改

(这里有一个很大的复杂部分,由于篇幅原因,我将省略,讨论文件如何在您的工作树中,但在 Git 的索引中 不在 , 是未跟踪的文件。Gi​​t 会抱怨这些,除非它们在 .gitignore 中列出。.gitignore 条目实际上并没有让 Git 忽略这些文件,所以.gitignore 用词不当。但由于篇幅原因,我在这里省略了所有这些。)

【讨论】:

    猜你喜欢
    • 2021-01-25
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 1970-01-01
    • 2014-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多