在 Git 中撤销更改和提交


总有些东西需要撤销。你输入了一个无意中的编辑,尝试的修复使情况更糟,或者提交了一些你希望没人看到的内容。Git 保存了它曾见过的项目几乎每个版本的记录,所以大多数错误的可恢复性比当时感觉到的要强。你需要哪个工具取决于错误的传播程度:仍然在编辑器中、已经暂存、已经提交,或者已经推送到团队成员可以看到的地方。本章按这个顺序逐一介绍每个阶段。
什么是推送和拉取?
推送将你的提交发送到团队成员也在工作的存储库的共享副本,拉取将他们的提交下载到你的机器。远程和 GitHub 完整覆盖了这两者;在本章中,"推送"意味着提交已离开你的机器。
在提交前丢弃更改
假设你仍在 提交循环 中的 weather-app 里重新设计 styles.css,但新布局被证明是个坏主意。你还没有暂存它,也没有提交它。你想让文件恢复到之前的样子,仅此而已。恰好完成这项工作的命令是 git restore:
$ git status
On branch main
Changes not staged for commit:
modified: styles.css
$ git restore styles.css
$ git status
On branch main
nothing to commit, working tree cleangit restore <file> 丢弃未提交的编辑,并将文件恢复到上次保存的状态。如果你已经用 git add 暂存了更改,但后来又改变了主意,那么普通的 git restore 不会触及它,因为文件现在位于暂存区而不仅仅在你的工作目录中。先将其从暂存区移出,然后恢复:
$ git add styles.css
$ git restore --staged styles.css
$ git restore styles.css两个分离的操作用于两个分离的区域:git restore --staged 将文件从暂存区移出,单独使用 git restore 丢弃工作目录中的编辑。git restore 仅适用于未提交的工作,一旦提交了更改,就需要使用不同的命令。
git restore <file> 丢弃你还未提交的编辑,并将文件恢复到之前的样子。如果你已经用 git add 暂存了更改,先用 git restore --staged <file> 将其从暂存区移出,然后如果你想让编辑消失,再恢复文件。这仅适用于你还未提交的更改,所以它无法抢救你已保存的错误。 用 git stash 暂存工作
有时没有错误要修复,只是时机不好。你正在编辑 styles.css,一个团队成员现在需要你转向另一个分支,但你还没有准备好提交半成品的样式。git stash 暂存你的更改,给你一个干净的工作目录,这样你可以离开并稍后返回。
$ git status
On branch main
Changes not staged for commit:
modified: styles.css
$ git stash
Saved working directory and index state WIP on main: 8f3c7d1 Cache forecast responses in localStorage
$ git status
On branch main
nothing to commit, working tree clean对 styles.css 的编辑没有消失,它被停靠了。一旦你准备好就用 git stash pop 把它恢复:
$ git stash pop
On branch main
Changes not staged for commit:
modified: styles.cssgit stash 用于你还未提交的更改,它从不在你的分支上创建提交。
git stash 暂存你还未提交的更改,所以你的工作目录变干净,git stash pop 稍后把它们恢复回来。没有东西被提交,也没有东西丢失,编辑只是停靠了一会。我每当团队成员现在需要我转向另一个分支时就频繁使用这个。 用 git revert 撤销提交
假设提交 8f3c7d1,"Cache forecast responses in localStorage",被证明是错误的,并且它已经被推送,所以一个团队成员已经拉取了它。现在重写该提交会改变别人已经拥有的东西,他们的副本和你的副本在下一次拉取时会不一致。git revert 解决了这个问题而不改变任何已经存在的东西:它创建一个全新的提交,应用变化的反面。
$ git revert 8f3c7d1
[main 2b6f0d1] Revert "Cache forecast responses in localStorage"历史仍然显示两个提交,原始的和撤销它的,按顺序。没有人的副本需要重新塑造,团队成员像拉取任何其他提交一样拉取新的恢复提交。
git revert 在别人已经拥有的提交上总是安全的,因为它添加新提交而不是改变旧的。
git revert <commit> 通过添加一个全新的提交来撤销一个提交,该提交反转它,而不是删除原始的。这使其成为一旦提交已经与任何其他人共享时的安全选择,因为没有已保存的东西被改变。历史记录保留了错误和修复的记录,我使用 Git 的时间越长就越喜欢这样。 用 git reset 回卷:soft、mixed 和 hard
有第三种方式来撤销提交,git reset,它的工作方式与恢复不同:reset 不是添加一个撤销旧提交的新提交,而是移动你当前分支以指向完全不同的提交。这使它对没有人看到的提交快速而干净,对他们已看到的提交则有风险。
Reset 是恢复和恢复上面的更宽泛的、更容易误用的表亲。大多数日常修复中,你不需要它,恢复、恢复和暂存涵盖了绝大多数"帮助,撤销这个"的时刻。你将在其他人的说明中看到它,所以这里是概括:reset 在历史中向后移动你的分支,它的一个模式 --hard,毫无确认地丢弃未提交的工作。如果你从某处复制的命令包括 git reset --hard,在运行前读一下它做什么。
git reset,恢复、恢复和暂存已经覆盖了几乎所有东西。仍然,知道它的形状:reset 将你的分支向后移动到较早的提交,它的 --hard 模式毫无确认地擦除你的工作目录以匹配。对你复制包括 reset --hard 的任何命令要真正谨慎。 选择恢复或重置
决定使用哪个工具的规则:别人已经拉取这个提交了吗?如果是,使用恢复。如果提交只存在于你的机器上,在一个你还未推送的分支上,reset 是可以的,通常是更整洁的修复。当你不确定时,恢复总是安全选择,因为它适用于两种情况。
用 git reflog 恢复提交
reset --hard、凌乱的变基或误删分支看起来像提交完全消失了。它通常没有。Git 保持了一个本地日志,记录你的 HEAD 和分支所指向的所有位置,称为 reflog,而且该日志几乎总是你回到看起来丢失的提交的方式。
你不太可能经常需要这个,但这是概括:Git 很少在看起来有东西的时候就立即丢弃。如果你曾运行过 reset --hard 然后意识到你之后需要那个提交,很可能有办法让它回来,当你准备更深入去时这里有覆盖。
reset --hard 然后太迟意识到你需要那个提交,它可能仍然可恢复。Git 保持一个称为 reflog 的本地记录,记录你的工作所有指向的地方,这通常足以让一个看起来消失的提交回来。这是深入领地,但知道它在那里是令人放心的。 
