能否删除(或撤销)Git 提交但保留更改?

能否删除(或撤销)Git 提交但保留更改?

技术背景

在使用 Git 进行版本控制时,我们可能会因为各种原因需要撤销某次提交,但又不想丢失这次提交所做的更改。例如,临时提交了一些未完成的代码,后续想要重新组织提交,或者提交了错误的内容但希望保留修改。

实现步骤

1. 使用 git reset 命令

如果要撤销的提交是最后一次提交,且之后没有进行额外的工作,可以使用 git reset 命令。

1
git reset HEAD^

需要注意的是,某些 shell 会将 ^ 视为特殊字符,例如一些 Windows 系统的 shell 或启用了通配符的 ZSH。在这些情况下,可能需要将 HEAD^ 用引号括起来,或者使用 HEAD~1

git reset 命令如果不指定 --hard--soft,会将 HEAD 指向指定的提交,而不会更改任何文件。HEAD^ 表示当前提交的(第一个)父提交。

还可以设置一个别名来简化操作:

1
git config --global alias.uncommit 'reset HEAD^'

之后就可以使用 git uncommit 来撤销最后一次提交。

2. 合并提交(Squashing)

合并提交意味着将两个或多个提交合并为一个。可以使用 git rebase -i <ref> 命令进入交互式变基模式。

  • 运行 git log 找到要撤销的提交,复制其 SHA1 值,替换 <ref>
  • Git 会列出当前状态与 <ref> 之间的所有提交。
  • 将要撤销的提交前的 pick 改为 fixupsquash。使用 fixup 会丢弃该提交的消息并将更改合并到列表中的前一个提交;squash 则允许编辑合并后新提交的消息。

3. 使用 git stash 暂存更改

在未来,为避免此类问题,可以考虑使用 git stash 临时存储未提交的工作。

1
git stash save 'some message'

可以使用 git stash list 查看暂存列表,使用 git stash apply stash@{#} 恢复暂存的更改,其中 # 是暂存列表中的位置。如果要恢复最近的暂存,可以直接使用 git stash apply

4. 针对不同 reset 模式的操作

git reset 命令有三种模式:

  • git reset HEAD^ --soft:撤销 git commit,更改仍然存在于工作树(项目文件夹)和索引(--cached)中。
  • git reset HEAD^ --mixed:撤销 git commitgit add,更改仍然存在于工作树中。
  • git reset HEAD^ --hard:就像从未对代码库进行过这些更改一样,更改从工作树中消失。

5. 已推送提交的处理

如果提交已经推送到远程仓库,可以使用 git revert -n <sha> 命令撤销特定提交,同时将更改保留在本地文件中。

6. 针对特定文件的撤销

如果只想撤销第一次提交中特定文件的更改,并将它们添加到第二次提交中,可以使用以下命令:

1
2
3
4
5
git checkout HEAD~1 <path_to_file_to_put_in_different_commit>
git add -u
git commit --amend --no-edit
git checkout HEAD@{1} <path_to_file_to_put_in_different_commit>
git commit -m "This is the new commit"

7. 根据 reflog 进行重置

可以使用 git reflog 查看所有提交记录,找到要撤销的提交及其对应的 HEAD 编号,然后使用 git reset HEAD@{#NumberOfCommitYouWantToUndo} 进行重置。

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 撤销最后一次提交
git reset HEAD^

# 设置别名
git config --global alias.uncommit 'reset HEAD^'

# 交互式变基
git rebase -i <ref>

# 暂存更改
git stash save 'some message'
git stash list
git stash apply stash@{#}

# 不同模式的 reset
git reset HEAD^ --soft
git reset HEAD^ --mixed
git reset HEAD^ --hard

# 撤销已推送的提交
git revert -n <sha>

# 针对特定文件的撤销
git checkout HEAD~1 <path_to_file_to_put_in_different_commit>
git add -u
git commit --amend --no-edit
git checkout HEAD@{1} <path_to_file_to_put_in_different_commit>
git commit -m "This is the new commit"

# 根据 reflog 重置
git reflog
git reset HEAD@{#NumberOfCommitYouWantToUndo}

最佳实践

  • 在进行任何可能修改提交历史的操作(如变基)之前,先备份重要的分支或创建副本。
  • 使用有意义的提交消息,以便在需要撤销或合并提交时更容易识别。
  • 定期使用 git stash 暂存未完成的工作,避免不必要的临时提交。

常见问题

  • 提交已推送到远程仓库:如果提交已经推送给他人,修改提交历史可能会导致问题。可以使用 git revert 命令创建一个新的提交来撤销之前的提交。
  • git reset 提示输入:某些情况下,git reset HEAD^ 可能会提示输入。可以尝试使用 git reset HEAD~#numberOfCommits 来选择要重置的本地提交数量。
  • ZSH 特殊字符问题:在 ZSH 中,^ 是特殊字符,需要进行转义,如 git reset --soft HEAD\^,或者使用 HEAD~ 避免转义。

能否删除(或撤销)Git 提交但保留更改?
https://119291.xyz/posts/can-i-delete-or-undo-a-git-commit-but-keep-the-changes/
作者
ww
发布于
2025年5月20日
许可协议