Help us learn about your current experience with the documentation. Take the survey.

回退和撤销更改

使用 Git 时需要进行实验和迭代。开发过程中难免出错,有时需要回退更改。Git 提供了控制代码历史的功能,可以在 Git 工作流 的任何节点撤销更改。

从意外的提交中恢复,删除敏感数据,修复错误的合并,并保持仓库历史的整洁。与他人协作时,可以通过新的回退提交保持透明度,或者在分享前本地重置你的工作。使用哪种方法取决于更改是:

  • 仅在你的本地计算机上。
  • 已存储在 GitLab.com 等 Git 服务器上。

撤销本地更改

在将更改推送到远程仓库之前,你在 Git 中所做的更改仅存在于本地开发环境中。

当你在 Git 中 暂存 一个文件时,你是在指示 Git 跟踪该文件的更改,为提交做准备。要忽略对文件的更改,并且不将其包含在下次提交中,请 取消暂存 该文件。

回退未暂存的本地更改

要撤销尚未暂存的本地更改:

  1. 运行 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")
  2. 选择一个选项并撤销你的更改:

    • 要覆盖本地更改:

      git checkout -- <file>
    • 要永久丢弃所有文件的本地更改:

      git reset --hard

回退已暂存的本地更改

你可以撤销已暂存的本地更改。在以下示例中,一个文件已被添加到暂存区,但尚未提交:

  1. 使用 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>
  2. 选择一个选项并撤销你的更改:

    • 要取消暂存文件但保留你的更改:

      git restore --staged <file>
    • 要取消暂存所有内容但保留你的更改:

      git reset
    • 要将文件重置到当前提交(HEAD):

      git reset HEAD <file>
    • 要永久丢弃所有内容:

      git reset --hard

撤销本地提交

当你使用 git commit 向本地仓库提交时,Git 会记录你的更改。由于你尚未推送到远程仓库,你的更改尚未公开或与他人共享。此时,你可以撤销你的更改。

回退提交而不修改历史

你可以在保留提交历史的同时回退提交。

此示例使用五个提交 ABCDE,按顺序提交:A-B-C-D-E。你想要回退的提交是 B

  1. 找到你想要回退到的提交的 SHA。要查看提交日志,使用命令 git log

  2. 选择一个选项并撤销你的更改:

    • 要回退提交 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

  1. 将当前提交 DB 的范围进行变基:

    git rebase -i A

    你的编辑器中会显示一个提交列表。

  2. 在提交 B 前面,将 pick 替换为 drop

  3. 为所有其他提交保留默认的 pick

  4. 保存并退出编辑器。

编辑特定提交

你可以修改特定提交。例如,如果你有提交 A-B-C-D 并且想要修改提交 B 中引入的内容。

  1. 将当前提交 DB 的范围进行变基:

    git rebase -i A

    你的编辑器中会显示一个提交列表。

  2. 在提交 B 前面,将 pick 替换为 edit

  3. 为所有其他提交保留默认的 pick

  4. 保存并退出编辑器。

  5. 在编辑器中打开文件,进行编辑,然后提交更改:

    git commit -a

撤销多个提交

如果你在分支上创建了多个提交(A-B-C-D),然后意识到提交 CD 是错误的,撤销这两个错误的提交:

  1. 检出最后一个正确的提交。在此示例中,是 B

    git checkout <commit-B-SHA>
  2. 创建一个新分支。

    git checkout -b new-path-of-feature
  3. 添加、推送并提交你的更改。

    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。例如:

  1. 检出基础分支。在此示例中,基础分支是 main

    git checkout main
  2. 使用 --squash 合并你的目标分支:

    git merge --squash <target-branch>
  3. 提交更改:

    git commit -m "从 feature-branch 压缩提交"

有关如何从 GitLab UI 压缩提交的信息,请参阅 压缩和合并

回退合并提交到不同的父提交

当你回退合并提交时,你合并到的分支总是第一个父提交。例如,默认分支main。要回退合并提交到不同的父提交,你必须从命令行回退提交:

  1. 识别你想要回退到的父提交的 SHA。

  2. 识别你想要回退到的提交的父编号。(默认为 1,表示第一个父提交。)

  3. 运行此命令,将 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 "新消息"
  • 撤销最后一次更改并删除所有其他更改, 如果你尚未推送:

    git reset --hard HEAD^
  • 撤销最后一次更改并删除最后两个提交, 如果你尚未推送:

    git reset --hard HEAD^^

git reset 工作流示例

以下是一个常见的 Git reset 工作流:

  1. 编辑一个文件。

  2. 检查分支状态:

    git status
  3. 用错误的提交消息将更改提交到分支:

    git commit -am "kjkfjkg"
  4. 检查 Git 日志:

    git log
  5. 用正确的提交消息修改提交:

    git commit --amend -m "添加新注释"
  6. 再次检查 Git 日志:

    git log
  7. 软重置分支:

    git reset --soft HEAD^
  8. 再次检查 Git 日志:

    git log
  9. 从远程拉取分支的更新:

    git pull origin <branch>
  10. 将分支的更改推送到远程:

    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 revertgit reset

  • git reset 命令完全删除提交。
  • git revert 命令删除更改,但保留提交。它更安全,因为你可以回退一个回退。
# 更改的文件
git commit -am "引入了 bug"
git revert HEAD
# 创建了回退更改的新提交
# 现在我们想要重新应用被回退的提交
git log # 从回退提交中获取 hash
git revert <rev commit hash>
# 被回退的提交回来了(再次创建了新提交)

相关主题