【问题标题】:how to un-do git add --all followed by multiple commits如何撤消 git add --all 后跟多个提交
【发布时间】:2017-09-03 14:08:17
【问题描述】:

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

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

【问题讨论】:

  • 这样的问题有很多。例如herehere。看看其中之一是否是您需要的。
  • 我试过 git --reset,没用。当我添加一个新文件时,它仍然会尝试推送所有内容,并显示诸如“在 Delta 压缩中使用多达 4 个线程”之类的消息。压缩对象:75% (45/60)
  • 您是否使用 -a 标志提交?
  • @thesecretmaster 我过去做了 git add all ,这导致了现在的问题。当我现在 git add/git commit 时,它一直在尝试上传所有内容,即使在我运行 git --reset 之后也是如此。
  • 只是做了git add --all,你做了git commit。这很重要。

标签: git github git-add


【解决方案1】:

TL;DR:

How to undo last commit(s) in Git? 上查看各种答案,或者,跳到最后一部分,“最终示例”。

说明

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

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

提交本身实际上是由哈希 ID 存储的,Git 向您展示的那些丑陋的东西(有时是缩写),例如 a9b307c 等等。这些哈希 ID 是每个提交的“真实姓名”。它们看起来是随机的,而且人们基本上不可能记住,所以我们要做的是让 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 是第一次提交,它不能指向任何更早的提交。所以它只是没有。我们将 A 称为 root 提交。任何 Git 存储库都必须有第一次提交——嗯,任何有任何提交的 Git 存储库——所以通常总是有一个根提交。该根提交是 Git 可以停止向后遍历的方式。

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

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

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

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

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

坏消息

这听起来像是个坏消息:你已经提交这些文件,所以你现在 和他们在一起。而且,这是个坏消息,但也不是致命坏消息。

好消息

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

因此,您需要在这里做的是“忘记”您的一些提交。你可以通过告诉 Git re-point 分支名称来做到这一点。例如,假设提交D 本身就是问题,我们只想摆脱它。假设我们可以告诉 Git:“嘿,让 master 再次指向 C”——就像这样:

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

提交D仍然存在(它是一种永久性的!)但它不再可从名称master 访问,因为 Git 从提交哈希开始master 命名的 ID,并向后工作。这意味着 Git 不会“看到”提交 D:它不再位于分支 master


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

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


关于git reset的知识

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

Git 有另外一对重要的特性:当你makegit commit 提交时,你可以 东西中做出它们。 “某物”本身实际上是 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 时,您必须选择是否保留索引和/或工作树。如果您使用git reset --hard,Git 将完成所有三件事。这没关系只要您准备好丢失索引和工作树中的临时内容。

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


2全部,即,除了 (a) 未 在索引中的文件和 (b) 列在 .gitignore 或类似“don “不自动添加”文件。

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


最后一个例子

假设您在master 上进行了许多 次提交,而不仅仅是一个,其中包含太多文件。你想记住(保存)你的工作,但也扔掉索引和工作树,让 master 重新与你的“上游”存储库同步,你克隆的那个,你正在给你的 Git 打电话origin

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

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

你在你的 master 分支上,它上面有所有底行的提交,加上你和origin 的其他 Git 共享的所有顶行的提交。

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

所以,您可以简单地创建一个 new 分支,指向提交 Y:

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 相同的提交,并重新设置您的索引和工作树以匹配该提交:

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

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

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


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

无论如何,所有这些都是由git gc --auto 驱动的,当其他 Git 命令认为有充分的理由时,它们会运行这些命令。因此,在 那些 Git 命令之一运行 git gc --auto 之前,这些过期的对象可能会一直存在。这与 Git 的非正式名称“版本控制的 Borg”非常吻合:它将所有东西都粘在它的集合中,但它可能会摧毁你的世界。 :-)

【讨论】:

  • 你是救星!
【解决方案2】:

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

git stash -u

【讨论】:

    【解决方案3】:

    您可以通过运行 git reset HEAD~2 --hardgit reset 退回 2 个提交

    【讨论】:

      【解决方案4】:

      如果您已经提交,一种选择是删除您的文件夹并再次克隆它。新的变化将会丢失!仅当您独自处理此 repo 并且没有太大变化时才这样做。

      【讨论】:

        猜你喜欢
        • 2015-03-16
        • 2010-09-25
        • 2012-08-21
        • 2012-12-14
        • 2021-08-14
        • 1970-01-01
        相关资源
        最近更新 更多