使用 Git 涉及实验和迭代。在开发过程中会发生错误,有时需要逆转更改。Git 提供了一些功能,允许您在任何时候撤消更改,从而控制您的代码历史:Git 工作流

从意外提交中恢复,移除敏感数据,修复错误合并,并保持一个干净的存储库历史。当与他人协作时,保留新恢复提交的透明性,或在共享之前本地重置您的工作。使用的方法取决于更改是否:

  • 仅在您的本地计算机上。
  • 远程存储在一个 Git 服务器上,例如 JihuLab.com。

撤销本地更改

在您将更改推送到远程存储库之前,您在 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。您想要撤销的提交是 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. 从当前提交 D 变基到 B

    git rebase -i A
    

    在编辑器中显示提交列表。

  2. 在提交 B 前面,将 pick 替换为 drop
  3. 对所有其他提交保留默认的 pick
  4. 保存并退出编辑器。

编辑特定提交

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

  1. 从当前提交 D 变基到 B

    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 "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 的动作描述。

撤销远程更改

您可以撤销分支上的远程更改。但是,您不能撤销合并到您分支上的分支更改。在这种情况下,您必须在远程分支上恢复更改。

恢复远程更改而不改变历史

要撤销远程存储库中的更改,您可以创建一个新的提交,包含您想要撤销的更改。此过程保留历史,并提供清晰的时间线和开发结构:

%%{init: { "fontFamily": "GitLab Sans" }}%% flowchart LR 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 访问,至少在所有自动清理孤立提交完成之前,或手动运行清理之前。即使清理可能不会删除旧提交,如果仍有引用指向它们。

本地分支与未更改远程分支之间的差异

当您在公共分支或可能被其他人使用的分支上工作时,您不应该更改历史。

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

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

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

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

    git commit -m "Squash commit from 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 "New Message"
    
  • 撤销最后的更改并删除所有其他更改,如果您尚未推送:

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

    git reset --hard HEAD^^
    

示例 git reset 工作流

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

  1. 编辑文件。
  2. 检查分支的状态:

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

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

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

    git commit --amend -m "New comment added"
    
  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 的文件),请使用 rm 命令和 --cache 标志:

    git rm <filename> --cache
    

这些命令会从当前分支中删除文件,但不会从存储库历史中彻底删除它。要完全删除文件的所有痕迹,包括过去和现在,请参阅移除 blob

比较 git revertgit reset

  • git reset 命令会完全删除提交。
  • git revert 命令会删除更改,但保留提交完整。这更安全,因为您可以恢复一个恢复。
# 已更改文件
git commit -am "bug introduced"
git revert HEAD
# 创建了一个新提交来恢复更改
# 现在我们想重新应用恢复的提交
git log # 从恢复提交中获取哈希
git revert <rev commit hash>
# 恢复的提交已返回(再次创建新提交)

相关主题