如何取消 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
是第一个提交,它不能指向任何更早的提交。所以它只是没有。我们称 A
为 root 提交。任何 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 中,仔细区分 名称 ,例如 master
和 develop
,以及我所说的有时很重要"DAGlets":提交图的部分,就像我们上面画的那些。提交图是一个D直接A循环Graph或DAG。 git fetch
和 git 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 这三个工作按重要性排序:
git reset
始终 移动(重新设置)分支名称;
git reset
有时 会重置索引;和
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
来执行此操作,但随后您将 忘记 从 X
到 Y
的所有哈希 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" 非常吻合:它把一切都粘在它的集体中,但它可能会摧毁你的世界。 :-)
我之前不小心使用了git add --all
,然后做了几次提交,它试图添加几个应该被忽略的大文件。
现在,任何提交都会显示“这超出了 GitHub 的文件大小限制 100.00 MB”。我试过 git --reset 但它显示你的分支领先 'origin/master' 2 次提交。如何让 git 再次恢复正常?非常感谢。
如果您已经提交,一种选择是删除您的文件夹并重新克隆它。新的更改将丢失!只有在您独自处理此回购并且没有做太多更改的情况下才这样做。
您可以 git reset
返回 运行 git reset HEAD~2 --hard
如果您还没有提交,运行:
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
是第一个提交,它不能指向任何更早的提交。所以它只是没有。我们称 A
为 root 提交。任何 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 中,仔细区分 名称 ,例如 master
和 develop
,以及我所说的有时很重要"DAGlets":提交图的部分,就像我们上面画的那些。提交图是一个D直接A循环Graph或DAG。 git fetch
和 git 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 这三个工作按重要性排序:
git reset
始终 移动(重新设置)分支名称;git reset
有时 会重置索引;和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
来执行此操作,但随后您将 忘记 从 X
到 Y
的所有哈希 ID。
因此,您可以简单地创建一个指向提交 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
相同的提交,并且还重新设置你的索引和工作-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" 非常吻合:它把一切都粘在它的集体中,但它可能会摧毁你的世界。 :-)