繁体   English   中英

Git 索引搞砸了

[英]Git index messed up

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

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

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

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

修复索引情况,链接问题(如何从 Git 存储库中的提交历史记录中删除/删除大文件? )是合适的。 但是,首先,您需要修复索引情况。

你提到:

在某些时候,我正在做一些 git 软复位......

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

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

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

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

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

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

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

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

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

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

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


1全新文件除外。 这是因为,例如,Mercurial 具有称为“dircache”和“manifest”的东西,它们的作用与 Git 的索引相似,但 Mercurial 将这些隐藏起来,这样您就不必了解它们。 相比之下,Git 不时抽出它的索引并用它打你的脸(Monty Python 拍鱼舞) 不能忽视它。 git commit -a快捷方式有时几乎可以让你到达那里,但这还不够:你必须了解 Git 的索引。


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

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

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

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

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

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

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

其中H代表最近一次提交的 hash ID, H是我们检查过的提交。 main is our branch name , and the name main holds H 's hash ID, which is how Git found H , when we said git checkout main or git switch main .

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

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

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

git commit最后一步是 Git 将I的 hash ID 写入名称main

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

所以现在要提交I而不是提交Hmain

git reset ,带有--hard--mixed--soft

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

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

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

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

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

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

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

git reset --hard HEAD~2

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

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

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

以下是实际发生的事情:

  • 首先, git reset执行--soft步骤。 我们给它一个提交 hash ID,例如提交H的原始 hash ID,或者像HEAD~2这样的相对提交指令。 git rev-parse命令将在此处使用的任何内容。 Git找到该提交,例如提交H 然后它使HEAD附加到的分支名称指向该提交。 所以现在main指向H

  • 然后,如果我们允许它——如果我们使用--mixed--hard —— git reset重置 Git 的index 它通过删除来自我们所在的提交的所有文件( 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的标志。

(The hash IDs of the various commits you've reset to get stored in the HEAD reflog, so git reflog will show them. This is a way to find which commit you want to go back to, if you accidentally reset away the hash ID you can't now find. Use the reflogs to find hash IDs that you have lost. Note that the hash IDs are really difficult to remember: you might want to run git show hash or git log -1 hash or similar, using cut-并粘贴 hash ID,在使用git reset --soft之前,找出哪个 hash ID 持有哪个感兴趣的提交。)

git status等类似比较器

git status命令部分通过运行两个git diff来工作。

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

git diff --staged --name-status

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM