使用 Git 涉及实验和迭代。在开发过程中会发生错误,有时需要逆转更改。Git 提供了一些功能,允许您在任何时候撤消更改,从而控制您的代码历史:Git 工作流。
从意外提交中恢复,移除敏感数据,修复错误合并,并保持一个干净的存储库历史。当与他人协作时,保留新恢复提交的透明性,或在共享之前本地重置您的工作。使用的方法取决于更改是否:
- 仅在您的本地计算机上。
- 远程存储在一个 Git 服务器上,例如 JihuLab.com。
撤销本地更改
在您将更改推送到远程存储库之前,您在 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
。您想要撤销的提交是 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 "Undo commits C and D" git push --set-upstream origin new-path-of-feature
提交现在是 A-B-C-D-E
。
或者,选择性取用该提交到一个新的合并请求。
{{< alert type=”note” >}}
另一个解决方案是重置到 B
并提交 E
。但是,此解决方案会导致 A-B-E
,这与其他人本地的版本冲突。如果您的分支是共享的,请不要使用此解决方案。
{{< /alert >}}
恢复撤销的提交
您可以回忆以前的本地提交。但是,并非所有以前的提交都可用,因为 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
的动作描述。
撤销远程更改
您可以撤销分支上的远程更改。但是,您不能撤销合并到您分支上的分支更改。在这种情况下,您必须在远程分支上恢复更改。
恢复远程更改而不改变历史
要撤销远程存储库中的更改,您可以创建一个新的提交,包含您想要撤销的更改。此过程保留历史,并提供清晰的时间线和开发结构:
要恢复特定提交 B
引入的更改:
git revert B
恢复远程更改并修改历史
您可以撤销远程更改并更改历史。
即使历史已更新,旧提交仍可以通过提交 SHA 访问,至少在所有自动清理孤立提交完成之前,或手动运行清理之前。即使清理可能不会删除旧提交,如果仍有引用指向它们。
当您在公共分支或可能被其他人使用的分支上工作时,您不应该更改历史。
{{< alert type=”note” >}}
切勿修改默认分支或共享分支的提交历史。
{{< /alert >}}
使用 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
{{< alert type=”note” >}}
如果您决定停止变基,请不要关闭编辑器。相反,删除所有未注释的行并保存。
{{< /alert >}}
在共享和远程分支上小心使用 git rebase
。在推送到远程存储库之前先在本地试验。
# 从 commit-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 "Squash commit from 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' HEAD
git filter-branch
命令在大型存储库上可能较慢。可以使用工具来更快地执行 Git 命令。这些工具速度更快,因为它们不提供与 git filter-branch
相同的功能集,而是专注于特定用例。
有关从存储库历史和极狐GitLab 存储中清除文件的更多信息,请参阅减少存储库大小。
撤销和删除提交
-
撤销您的最后一次提交并将所有内容放回暂存区:
git reset --soft HEAD^
-
添加文件并更改提交消息:
git commit --amend -m "New Message"
-
撤销最后的更改并删除所有其他更改,如果您尚未推送:
git reset --hard HEAD^
-
撤销最后的更改并删除最后两个提交,如果您尚未推送:
git reset --hard HEAD^^
示例 git reset
工作流
以下是一个常见的 Git 重置工作流:
- 编辑文件。
-
检查分支的状态:
git status
-
使用错误的提交消息将更改提交到分支:
git commit -am "kjkfjkg"
-
检查 Git 日志:
git log
-
使用正确的提交消息修改提交:
git commit --amend -m "New comment added"
-
再次检查 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
的文件),请使用rm
命令和--cache
标志:git rm <filename> --cache
这些命令会从当前分支中删除文件,但不会从存储库历史中彻底删除它。要完全删除文件的所有痕迹,包括过去和现在,请参阅移除 blob。
比较 git revert
和 git reset
-
git reset
命令会完全删除提交。 -
git revert
命令会删除更改,但保留提交完整。这更安全,因为您可以恢复一个恢复。
# 已更改文件
git commit -am "bug introduced"
git revert HEAD
# 创建了一个新提交来恢复更改
# 现在我们想重新应用恢复的提交
git log # 从恢复提交中获取哈希
git revert <rev commit hash>
# 恢复的提交已返回(再次创建新提交)