如何取消 git 添加 --all 后跟多个提交

how to un-do git add --all followed by multiple commits

我之前不小心使用了git add --all,然后做了几次提交,它试图添加几个应该被忽略的大文件。

现在,任何提交都会显示“这超出了 GitHub 的文件大小限制 100.00 MB”。我试过 git --reset 但它显示你的分支领先 'origin/master' 2 次提交。如何让 git 再次恢复正常?非常感谢。

如果您已经提交,一种选择是删除您的文件夹并重新克隆它。新的更改将丢失!只有在您独自处理此回购并且没有做太多更改的情况下才这样做。

您可以 git reset 返回 运行 git reset HEAD~2 --hard

的 2 次提交

如果您还没有提交,运行:

git stash -u

长话短说:

查看 How to undo last commit(s) in Git? 中的各种答案,或者,跳到最后一部分 "final example"。

说明

你的问题中的错误是你没有正确描述你的情况。您不仅添加了各种文件,而且还 提交 它们。这意味着它们现在永久存储在您的存储库中。

永久这个词在Git中有点奇怪。确实,无论您做什么,提交都是固定的、不变的,并且会在您的存储库中保留很长时间。默认情况下,它们会永远留在那里,成为任何人所做的每一次提交历史的一部分。 Git 永远不会删除任何提交:每个 new 提交都保留其先前提交的身份——称为其 parent——以及这个向后的提交链从新到旧的提交 存储在您的存储库中的历史记录。

提交本身实际上是由哈希 ID 存储的,那些丑陋的大东西 Git 向您展示(有时缩写)如 a9b307c 等等。这些哈希 ID 是每个提交的 "true names"。它们随机出现,人们基本上不可能记住,所以我们所做的是 Git 将 name 附加到某个特定的提交上,例如 master。我们称此提交为分支的 tip。该提交本身具有 previous 分支提示的哈希 ID。

考虑这张只有三个提交的存储库图,全部在 master 上。我将为每个提交使用一个字母的名称而不是哈希 ID:

A <--B <--C   <--master

名称 master 存储哈希 ID badf00d 或其他任何内容,这是提交 C 的实际哈希 ID。我们说master指向C。同时 C 存储 B 的哈希 ID;我们说 C 指向 B。提交 B 存储 A 的 ID,因此 B 指向 A.

因为 A 是第一个提交,它不能指向任何更早的提交。所以它只是没有。我们称 Aroot 提交。任何 Git 存储库都必须有第一次提交——好吧,任何 Git 存储库都有任何提交——所以通常总是有根提交。该根提交是 Git 可以 停止 向后遍历的方式。

要添加一个 new 提交,Git 只需写出提交及其所有文件——每个提交都会存储与该提交相关的每个文件——并使它指向当前分支的提示:

A <--B <--C <--D

Commit D 得到一个新的、唯一的散列,它基于 everything in D(包括 C 的散列 ID ,以及您的姓名和电子邮件地址以及当前时间)。但是——这里是 Git 中分支名称的秘密——Git 现在 将新提交的哈希 ID 写入分支名称 ,因此名称 master 现在指向提交 D:

A <--B <--C <--D   <--master

树枝是这样生长的。提交后,名称指向它,并且提交本身永久存储在您的存储库中。

坏消息

这听起来像是个坏消息:您已经提交了 那些文件,所以您现在 坚持他们。而且,这是个坏消息,但不是致命的坏消息。

好消息

然而,如果您努力工作,提交最终可能会被遗忘。此外,当您将您的提交转移到其他人的存储库时 - 即,git push 您的新提交 - Git 只会推送那些 可从 访问的提交您正在推送的一个或多个分支1

因此,您在这里需要做的是 "forget" 您的一些提交。你通过告诉 Git 到 重新指向 分支名称来做到这一点。例如,假设 commit D 本身就是问题,我们只想摆脱它。假设我们可以告诉 Git:"hey, make master point to C again"——像这样:

A--B--C   <-- master
       \
        D

提交 D 仍然存在(它是永久性的!)但它不再可以从 名称master,因为 Git 从 master 命名的提交哈希 ID 开始,并向后工作。这意味着 Git 不会 "see" 提交 D:它不再在分支 master.


1您可以一次推送多个分支名称。这曾经是 git push 的默认操作,事实上,尽管事实证明这很容易出错,所以现在默认只推送 current 分支名称。

在 Git 中,仔细区分 名称 ,例如 masterdevelop,以及我所说的有时很重要"DAGlets":提交图的部分,就像我们上面画的那些。提交图是一个D直接A循环Graph或DAG。 git fetchgit push 都采用 分支名称 或任何其他类型的名称。他们在 Internet-phone-呼叫的另一端呼叫另一个 Git,并与其交谈:他们互相提供其中一些名称,然后将这些名称转换为适当的哈希 ID。然后他们根据哈希 ID 和我之前提到的那些父链接决定发送或接收哪些提交(和其他 Git 对象)。因为哈希 ID 仅基于每个提交的 内容 ,如果您的 Git 和他们的 Git 具有相同的 commit,这两个提交具有相同的哈希 ID。


关于 git reset

的知识

在 Git 中移动分支指针的主要方式是使用 git reset。不幸的是,git reset 是一个复杂的命令。

Git 有另外一对重要的特征:当你 make 使用 git commit 提交时,你让它们 from 东西。 "something" 本身实际上是 Git 的 index。索引主要是你构建下一个提交的地方.

当您 运行 git add、Git 从您的 工作树 获取文件时——您工作的地方,以您可以实际使用的形式拥有文件,并将它们复制到索引中。当您 运行 git add --all、Git 获取 所有 工作树文件 2 并将它们添加到索引。

此时,它们在索引中,但未提交,因此它们不是永久性的。您可以 git reset 返回 out 索引。那是 git reset 的工作之一:重新设置索引。

但这些文件也在您的工作树中。工作树版本也未提交,因此它们不是永久性的。您也可以 git reset 他们,那是 git reset 另一个

当然,git reset 可以 移动分支名称 ,这是我们针对这种特殊情况所需要的,因为您 做了 提交文件,所以现在它们作为新提交的一部分永久存储。移动分支名称是 git reset 三个主要工作中的第三个。3 这三个工作按重要性排序:

  1. git reset 始终 移动(重新设置)分支名称;
  2. git reset 有时 会重置索引;和
  3. git reset 偶尔 重置工作树。

您可以使用 --soft(仅执行作业 #1)、--mixed(执行作业 #1 和 #2)和 --hard(执行所有三个作业)来控制这些职位)。

当您执行移动分支的 git reset 时,您必须选择是否保留索引 and/or 工作树。如果您使用 git reset --hard,Git 将完成 所有这三个 事情。这没关系只要您准备好丢失索引和工作树中的临时内容。

提交 是永久性的,尽管一旦您忘记了它们的哈希 ID,将很难找到它们。因此,可以将它们重置掉:您可以再次取回它们。此外,您可以使用新名称保存哈希 ID,例如 new 分支名称,然后您可以很容易地将它们全部取回。让我们看最后一个例子。


2所有,即除了 (a) 未 already 在索引中且 (b) 在中列出的文件.gitignore 或类似的 "don't automatically add" 文件。

3git reset命令还可以做一些更具体的事情,在这种情况下它可以停止做一些主要工作,但我们不会在这里提及它们。


最终示例

假设您在 master 上并做出了 多次 次提交,而不仅仅是一次,其中包含太多文件。您想要 记住 (保存)您的工作,但也扔掉索引和工作树并使 master 恢复与您的 "upstream" 存储库同步,您从中克隆的那个,您正在 Git 调用 origin

你的提交图中有一些看起来像这样的东西:

...--o--o--o   <-- origin/master
            \
             X--o--o--o--Y   <-- master (HEAD)

你在你的 master 分支上,它有所有底行提交,加上你和其他 Git 在 [=80] 的所有顶行提交=]分享。

您在第一次提交时犯了一个错误,标记为 X。因此,您希望一直重置 master 以指向与 origin/master 相同的提交,并丢弃您当前的索引和工作树,因为它们是 "clean"(提交和匹配您 master 的提示,即提交 Y)。您可以 运行 git reset --hard 来执行此操作,但随后您将 忘记 XY 的所有哈希 ID。

因此,您可以简单地创建一个指向提交 Y:

new 分支
git branch save-my-mistake

现在你的照片是这样的:

...--o--o--o   <-- origin/master
            \
             X--o--o--o--Y   <-- master (HEAD), save-my-mistake

现在是时候运行:

git reset --hard origin/master

移动当前分支——仍然是master——指向origin/master相同的提交,并且还重新设置你的索引和工作-tree 以匹配该提交:

...--o--o--o   <-- master (HEAD), origin/master
            \
             X--o--o--o--Y   <-- save-my-mistake

现在您可以随时从 save-my-mistake 分支中获取任何您想要的东西,因为 那个 分支名称会为您记住您的提交。

最后,当你完成它时,你可以简单地删除那个分支。这些提交迟早会 4 成为 "garbage collected" 并真正从您的存储库中消失。在那之前,您将不会再看到它们,因为您(和Git)用来找到难以理解的哈希 ID 的名称已经消失。


4过期时间比较棘手。它部分取决于 reflog 条目, "reachable" 提交在 90 天后到期,"unreachable" 提交在 30 天后到期,根据参考的当前值。一旦 reflog 条目本身过期,如果底层 Git 对象 globally 无法访问,即无法从 any[=265 访问,则它们将有资格进行此垃圾收集=] 名字。从创建之日起,它们仍然有 14 天的宽限期,尽管在大多数情况下,如果 30 或 90 天的时间段已经到期,那么 14 天已经是很久以前的事了。但是,删除分支名称会删除其所有 reflog 条目,这可能会暴露较年轻的提交,然后这些提交将获得其 14 天期限内剩余的任何内容。

无论如何,所有这些都是由 git gc --auto 驱动的,其他 Git 在他们认为有充分理由这样做时命令 运行。因此,直到 those Git 命令之一 运行s git gc --auto,这些过期的对象可能会一直存在。这与 Git 的非正式名称 "the Borg of version control" 非常吻合:它把一切都粘在它的集体中,但它可能会摧毁你的世界。 :-)