回退和撤销更改
使用 Git 时需要进行实验和迭代。开发过程中难免出错,有时需要回退更改。Git 提供了控制代码历史的功能,可以在 Git 工作流 的任何节点撤销更改。
从意外的提交中恢复,删除敏感数据,修复错误的合并,并保持仓库历史的整洁。与他人协作时,可以通过新的回退提交保持透明度,或者在分享前本地重置你的工作。使用哪种方法取决于更改是:
- 仅在你的本地计算机上。
- 已存储在 GitLab.com 等 Git 服务器上。
撤销本地更改
在将更改推送到远程仓库之前,你在 Git 中所做的更改仅存在于本地开发环境中。
当你在 Git 中 暂存 一个文件时,你是在指示 Git 跟踪该文件的更改,为提交做准备。要忽略对文件的更改,并且不将其包含在下次提交中,请 取消暂存 该文件。
回退未暂存的本地更改
要撤销尚未暂存的本地更改:
-
运行
git status确认文件未暂存(即你没有使用git add <file>):git status示例输出:
On branch main Your branch is up-to-date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: <file> no changes added to commit (use "git add" and/or "git commit -a") -
选择一个选项并撤销你的更改:
-
要覆盖本地更改:
git checkout -- <file> -
要永久丢弃所有文件的本地更改:
git reset --hard
-
回退已暂存的本地更改
你可以撤销已暂存的本地更改。在以下示例中,一个文件已被添加到暂存区,但尚未提交:
-
使用
git status确认文件已暂存:git status示例输出:
On branch main Your branch is up-to-date with 'origin/main'. Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: <file> -
选择一个选项并撤销你的更改:
-
要取消暂存文件但保留你的更改:
git restore --staged <file> -
要取消暂存所有内容但保留你的更改:
git reset -
要将文件重置到当前提交(HEAD):
git reset HEAD <file> -
要永久丢弃所有内容:
git reset --hard
-
撤销本地提交
当你使用 git commit 向本地仓库提交时,Git 会记录你的更改。由于你尚未推送到远程仓库,你的更改尚未公开或与他人共享。此时,你可以撤销你的更改。
回退提交而不修改历史
你可以在保留提交历史的同时回退提交。
此示例使用五个提交 A、B、C、D、E,按顺序提交:A-B-C-D-E。你想要回退的提交是 B。
-
找到你想要回退到的提交的 SHA。要查看提交日志,使用命令
git log。 -
选择一个选项并撤销你的更改:
-
要回退提交
B引入的更改:git revert <commit-B-SHA> -
要回退提交
B中单个文件或目录的更改,但保留它们在暂存状态:git checkout <commit-B-SHA> <file> -
要回退提交
B中单个文件或目录的更改,但保留它们在未暂存状态:git reset <commit-B-SHA> <file>
-
回退提交并修改历史
以下部分记录了重写 Git 历史的任务。有关更多信息,请参阅 变基和解决冲突。
删除特定提交
你可以删除特定提交。例如,如果你有提交 A-B-C-D 并且想要删除提交 B。
-
将当前提交
D到B的范围进行变基:git rebase -i A你的编辑器中会显示一个提交列表。
-
在提交
B前面,将pick替换为drop。 -
为所有其他提交保留默认的
pick。 -
保存并退出编辑器。
编辑特定提交
你可以修改特定提交。例如,如果你有提交 A-B-C-D 并且想要修改提交 B 中引入的内容。
-
将当前提交
D到B的范围进行变基:git rebase -i A你的编辑器中会显示一个提交列表。
-
在提交
B前面,将pick替换为edit。 -
为所有其他提交保留默认的
pick。 -
保存并退出编辑器。
-
在编辑器中打开文件,进行编辑,然后提交更改:
git commit -a
撤销多个提交
如果你在分支上创建了多个提交(A-B-C-D),然后意识到提交 C 和 D 是错误的,撤销这两个错误的提交:
-
检出最后一个正确的提交。在此示例中,是
B。git checkout <commit-B-SHA> -
创建一个新分支。
git checkout -b new-path-of-feature -
添加、推送并提交你的更改。
git add . git commit -m "撤销提交 C 和 D" git push --set-upstream origin new-path-of-feature
现在提交变为 A-B-C-D-E。
或者,挑选 该提交到新的合并请求中。
另一种解决方案是重置到 B 并提交 E。但是,这个解决方案会导致 A-B-E,与本地其他人的内容冲突。如果你的分支是共享的,请不要使用此解决方案。
恢复已撤销的提交
你可以恢复之前的本地提交。但是,并非所有之前的提交都可用,因为 Git 会定期清理无法被分支或标签访问的提交。
要查看仓库历史并跟踪之前的提交,运行 git reflog show。例如:
$ git reflog show
# 示例输出:
b673187 HEAD@{4}: merge 6e43d5987921bde189640cc1e37661f7f75c9c0b: Merge made by the 'recursive' strategy.
eb37e74 HEAD@{5}: rebase -i (finish): returning to refs/heads/master
eb37e74 HEAD@{6}: rebase -i (pick): Commit C
97436c6 HEAD@{7}: rebase -i (start): checkout 97436c6eec6396c63856c19b6a96372705b08b1b
...
88f1867 HEAD@{12}: commit: Commit D
97436c6 HEAD@{13}: checkout: moving from 97436c6eec6396c63856c19b6a96372705b08b1b to test
97436c6 HEAD@{14}: checkout: moving from master to 97436c6
05cc326 HEAD@{15}: commit: Commit C
6e43d59 HEAD@{16}: commit: Commit B此输出显示了仓库历史,包括:
- 提交 SHA。
- 提交是在多少次
HEAD变更操作之前创建的(HEAD@{12}是 12 次HEAD变更操作之前)。 - 执行的操作,例如:提交、变基、合并。
- 改变
HEAD的操作的描述。
撤销远程更改
你可以撤销分支上的远程更改。但是,你不能撤销已合并到你的分支中的更改。在这种情况下,你必须回退远程分支上的更改。
回退远程更改而不修改历史
要撤销远程仓库中的更改,你可以创建一个新提交来包含你想要撤销的更改。这个过程保留了历史,并提供了清晰的时间线和开发结构:
%%{init: { "fontFamily": "GitLab Sans" }}%%
flowchart LR
accTitle: Git revert operation workflow diagram
accDescr: Shows commits A, B, C in sequence, then commit -B that reverses B's changes, followed by D. Commit B remains in history.
REMOTE["REMOTE"] --> A(A)
A --> B(B)
B --> C(C)
C --> negB("-B")
negB --> D(D)
B:::crossed
classDef crossed stroke:#000,stroke-width:3px,color:#000,stroke-dasharray: 5 5
negB -.->|reverts| B
要回退特定提交 B 引入的更改:
git revert B回退远程更改并修改历史
你可以撤销远程更改并修改历史。
即使更新了历史,旧的提交仍然可以通过提交 SHA 访问,至少直到所有分离提交的自动清理完成,或者手动运行清理。即使清理可能不会删除仍有引用指向的旧提交。
当你在公共分支或可能被他人使用的分支上工作时,你不应该修改历史。
永远不要修改你的 默认分支 或共享分支的提交历史。
使用 git rebase 修改历史
合并请求的分支是公共分支,可能被其他开发者使用。但是,项目规则可能要求你在审查完成后使用 git rebase 来减少目标分支上显示的提交数量。
你可以使用 git rebase -i 修改历史。使用此命令来修改、压缩和删除提交。
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Empty commits are commented out如果你决定停止变基,不要关闭你的编辑器。相反,删除所有未注释的行并保存。
在共享和远程分支上谨慎使用 git rebase。在推送到远程仓库之前先本地测试。
# 从提交-id 到 HEAD(当前提交)修改历史
git rebase -i commit-id使用 git merge --squash 修改历史
在为大型开源仓库做贡献时,考虑将你的提交压缩成一个提交。这种做法:
- 有助于保持干净和线性的项目历史。
- 简化了回退更改的过程,因为所有更改都压缩在一个提交中。
要在合并时将你分支上的提交压缩到目标分支上的单个提交,使用 git merge --squash。例如:
-
检出基础分支。在此示例中,基础分支是
main:git checkout main -
使用
--squash合并你的目标分支:git merge --squash <target-branch> -
提交更改:
git commit -m "从 feature-branch 压缩提交"
有关如何从 GitLab UI 压缩提交的信息,请参阅 压缩和合并。
回退合并提交到不同的父提交
当你回退合并提交时,你合并到的分支总是第一个父提交。例如,默认分支 或 main。要回退合并提交到不同的父提交,你必须从命令行回退提交:
-
识别你想要回退到的父提交的 SHA。
-
识别你想要回退到的提交的父编号。(默认为
1,表示第一个父提交。) -
运行此命令,将
2替换为父编号,将7a39eb0替换为提交 SHA:git revert -m 2 7a39eb0
有关如何从 GitLab UI 回退更改的信息,请参阅 回退更改。
处理敏感信息
敏感信息,如密码和 API 密钥,可能会意外提交到 Git 仓库。本节介绍处理这种情况的方法。
删除信息
永久删除意外提交的敏感或机密信息,并确保它在你的仓库历史中不再可访问。此过程将字符串列表替换为 ***REMOVED***。
或者,要从仓库中完全删除特定文件,请参阅 删除 blob。
要从你的仓库中删除文本,请参阅 从仓库中删除文本。
从提交中删除信息
你可以使用 Git 从过去的提交中删除敏感信息。但是,在此过程中会修改历史。
要使用特定过滤器重写历史,运行 git filter-branch。
要从历史中完全删除文件,使用:
git filter-branch --tree-filter 'rm filename' HEADgit filter-branch 命令在大型仓库上可能很慢。有工具可以更快地执行 Git 命令。这些工具更快,因为它们不提供与 git filter-branch 相同的功能集,而是专注于特定用例。
有关从仓库历史和 GitLab 存储中清除文件的更多信息,请参阅 减少仓库大小。
撤销和删除提交
-
撤销你的最后一次提交并将所有内容放回暂存区:
git reset --soft HEAD^ -
添加文件并更改提交消息:
git commit --amend -m "新消息" -
撤销最后一次更改并删除所有其他更改, 如果你尚未推送:
git reset --hard HEAD^ -
撤销最后一次更改并删除最后两个提交, 如果你尚未推送:
git reset --hard HEAD^^
git reset 工作流示例
以下是一个常见的 Git reset 工作流:
-
编辑一个文件。
-
检查分支状态:
git status -
用错误的提交消息将更改提交到分支:
git commit -am "kjkfjkg" -
检查 Git 日志:
git log -
用正确的提交消息修改提交:
git commit --amend -m "添加新注释" -
再次检查 Git 日志:
git log -
软重置分支:
git reset --soft HEAD^ -
再次检查 Git 日志:
git log -
从远程拉取分支的更新:
git pull origin <branch> -
将分支的更改推送到远程:
git push origin <branch>
使用新提交撤销提交
如果一个文件在提交中被更改,并且你希望将其恢复到之前提交中的状态,但保留提交历史,你可以使用 git revert。该命令创建一个新提交,反转原始提交中执行的所有操作。
例如,要删除提交 B 中文件的更改,并从提交 A 恢复其内容,运行:
git revert <commit-sha>从仓库中删除文件
-
要从磁盘和仓库中删除文件,使用
git rm。要删除目录,使用-r标志:git rm '*.txt' git rm -r <dirname> -
要在磁盘上保留文件但从仓库中删除(例如你想要添加到
.gitignore的文件),使用带有--cache标志的rm命令:git rm <filename> --cache
这些命令从当前分支中删除文件,但不会从你的仓库历史中彻底删除它。要从仓库中完全删除该文件的所有痕迹,过去和现在,请参阅 删除 blob。
比较 git revert 和 git reset
git reset命令完全删除提交。git revert命令删除更改,但保留提交。它更安全,因为你可以回退一个回退。
# 更改的文件
git commit -am "引入了 bug"
git revert HEAD
# 创建了回退更改的新提交
# 现在我们想要重新应用被回退的提交
git log # 从回退提交中获取 hash
git revert <rev commit hash>
# 被回退的提交回来了(再次创建了新提交)