在 Git 中撤消

Git 中的任何内容都不会被删除,所以当您使用 Git 工作时,您只能撤消提交的内容。

所有版本控制系统都有撤销的选项。然而,由于 Git 的去中心化性质,撤销操作会成倍增加。您采取的行动应基于您所处环境的开发阶段

当您撤销改动

在标准的 Git 工作流中:

  1. 您创建和修改一个文件。它开始处于 unstaged 状态。如果是新文件, Git 则尚未追踪此文件。
  2. 您把此文件添加(git add)到本地的仓库,它就变成了暂存状态。
  3. 您提交(git commit)此文件到本地的仓库。
  4. 最后您可以通过推送(git push)到远端仓库与其他开发人员共享该文件。

您可以在此工作流程中的任何时候撤消更改:

撤销本地的改动

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

撤销本地 unstaged 的改动

当您进行更改但处于 unstaged 状态时,您可以撤消您的工作。

  1. 执行命令 git status 来确认您的文件处于 unstaged 状态(您没执行过 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 stash
      
    • 永久的丢弃所有的本地改动:

      git reset --hard
      

撤销本地 staged 的改动

如果您将文件添加到暂存,仍可以撤消它。

  1. 执行 git status 确保您的文件处于 staged 状态(在您已经执行过 git add <file> 后):

    $ 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 stash
      
    • 永久的丢弃所有改动:

      git reset --hard
      

快速保存本地更改

如果您想改变到另一个分支,您可以使用 git stash

  1. 从您要保存工作的分支,键入 git stash
  2. 切换到另外一个分支 (git checkout <branchname>)。
  3. 提交,推送,然后测试。
  4. 返回要恢复更改的分支。
  5. 使用 git stash list 列出所有以前隐藏的提交。

    stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
    stash@{1}: On master: 9cc0589... Add git-stash
    
  6. 运行一个版本的 git stash

    • 使用 git stash pop 重做以前隐藏的更改并将它们从隐藏列表中删除。
    • 使用 git stash apply 来重做以前隐藏的更改,但将它们保留在隐藏列表中。

撤消已提交的本地更改

当您提交(git commit)到本地仓库时,Git 记录您的改变。因为您还没有推送到远端仓库,所以您的更改是非公开(不与其他开发人员共享)的。此时您可以撤消更改。

撤消暂存的本地更改而不修改历史记录

您可以在保留提交历史记录的同时还原提交。

这个例子使用了五个提交 ABCDE,它们按顺序提交:A-B-C-D-E。 您要撤消的提交是 B

  1. 通过输入 git log 查看提交日志,找到要还原到的提交的 SHA。
  2. 选择一个选项并撤消您的更改:

    • 交换由提交 B 引入的添加和删除的更改:

      git revert <commit-B-SHA>
      
    • 从提交 B 撤消对单个文件或目录的更改,但将它们保留在暂存状态:

      git checkout <commit-B-SHA> <file>
      
    • 从提交 B 撤消对单个文件或目录的更改,但将它们保留在未暂存状态:

      git reset <commit-B-SHA> <file>
      

撤消多个已提交的更改

您可以从多次提交中恢复。例如,如果您已经完成提交 A-B-C-D 在您的功能分支上,然后意识到 CD 是错误的。

要从多个不正确的提交中恢复:

  1. 查看最后一次正确的提交。 在这个例子中,B

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

    git checkout -b new-path-of-feature
    
  3. 添加,推送并且提交您的修改。

提交现在是 A-B-C-D-E

或者,使用极狐GitLab,您可以 cherry-pick 提交到一个新的合并请求中。

note 另一种解决方案是重置为 B 并提交 E。但是,此解决方案会导致 A-B-E,这与其他开发人员在本地拥有的内容相冲突。

使用历史修改撤消暂存的本地更改

以下任务重写 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
    

重做撤销

您可以找回以前的本地提交。但是,并非所有以前的提交都可用,因为 Git 定期清理分支或标签无法访问的提交

要查看仓库历史记录并跟踪先前的提交,请运行 git reflog show。例如:

$ git reflog show

# Example output:
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 的动作的描述。

撤消远程更改而不更改历史记录

要撤消远程存储库中的更改,您可以使用要撤消的更改创建新提交。您应该遵循这个过程,它保留了历史并提供了清晰的时间表和开发结构。但是,仅当您的工作合并到其他开发人员用作其工作基础的分支时,才需要此过程。

Use revert to keep branch flowing

要恢复在特定提交 B 中引入的更改:

git revert B

更改历史时撤消远程更改

您可以撤消远程更改和更改历史记录。

即使有更新的历史记录,提交 SHA 仍然可以访问旧的提交。至少在执行分离提交的所有自动清理或手动运行清理之前都是这种情况。如果仍然有 refs 指向它们,即使清理也可能不会删除旧的提交。

修改历史导致远程分支出现问题

当改变历史是可以接受的

当您在公共分支或其他开发人员可能使用的分支中工作时,不应更改历史记录。

当您为大型开源存储库做出贡献时,您可以将提交压缩为一个。

要将功能分支上的提交压缩到合并时目标分支上的单个提交,请使用 git merge --squash

note 永远不要修改您的默认分支或共享分支的提交历史。

如何修改历史

合并请求的功能分支是公共分支,可能会被其他开发人员使用。但是,项目规则可能要求您使用 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
note 如果您决定停止变基,请不要关闭编辑器。相反,删除所有未注释的行并保存。

在共享和远端分支上小心使用 git rebase。在推送到远端仓库之前在本地进行试验。

# Modify history from commit-id to HEAD (current commit)
git rebase -i commit-id

从提交中删除敏感信息

您可以使用 Git 从过去的提交中删除敏感信息。然而,历史在此过程中被修改。

使用 特定过滤器 改写历史,运行 git filter-branch

要从历史记录中完全删除文件,请使用:

git filter-branch --tree-filter 'rm filename' HEAD

git filter-branch 命令在大型仓库上可能会很慢。可以使用工具更快地执行 Git 命令。这些工具速度更快,因为它们不提供与 git filter-branch 相同的功能集,而是专注于特定用例。