能否删除(或撤销)Git 提交但保留更改?
能否删除(或撤销)Git 提交但保留更改?
技术背景
在使用 Git 进行版本控制时,我们可能会因为各种原因需要撤销某次提交,但又不想丢失这次提交所做的更改。例如,临时提交了一些未完成的代码,后续想要重新组织提交,或者提交了错误的内容但希望保留修改。
实现步骤
1. 使用 git reset
命令
如果要撤销的提交是最后一次提交,且之后没有进行额外的工作,可以使用 git reset
命令。
1 |
|
需要注意的是,某些 shell 会将 ^
视为特殊字符,例如一些 Windows 系统的 shell 或启用了通配符的 ZSH。在这些情况下,可能需要将 HEAD^
用引号括起来,或者使用 HEAD~1
。
git reset
命令如果不指定 --hard
或 --soft
,会将 HEAD
指向指定的提交,而不会更改任何文件。HEAD^
表示当前提交的(第一个)父提交。
还可以设置一个别名来简化操作:
1 |
|
之后就可以使用 git uncommit
来撤销最后一次提交。
2. 合并提交(Squashing)
合并提交意味着将两个或多个提交合并为一个。可以使用 git rebase -i <ref>
命令进入交互式变基模式。
- 运行
git log
找到要撤销的提交,复制其 SHA1 值,替换<ref>
。 - Git 会列出当前状态与
<ref>
之间的所有提交。 - 将要撤销的提交前的
pick
改为fixup
或squash
。使用fixup
会丢弃该提交的消息并将更改合并到列表中的前一个提交;squash
则允许编辑合并后新提交的消息。
3. 使用 git stash
暂存更改
在未来,为避免此类问题,可以考虑使用 git stash
临时存储未提交的工作。
1 |
|
可以使用 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 commit
和git add
,更改仍然存在于工作树中。git reset HEAD^ --hard
:就像从未对代码库进行过这些更改一样,更改从工作树中消失。
5. 已推送提交的处理
如果提交已经推送到远程仓库,可以使用 git revert -n <sha>
命令撤销特定提交,同时将更改保留在本地文件中。
6. 针对特定文件的撤销
如果只想撤销第一次提交中特定文件的更改,并将它们添加到第二次提交中,可以使用以下命令:
1 |
|
7. 根据 reflog
进行重置
可以使用 git reflog
查看所有提交记录,找到要撤销的提交及其对应的 HEAD
编号,然后使用 git reset HEAD@{#NumberOfCommitYouWantToUndo}
进行重置。
核心代码
1 |
|
最佳实践
- 在进行任何可能修改提交历史的操作(如变基)之前,先备份重要的分支或创建副本。
- 使用有意义的提交消息,以便在需要撤销或合并提交时更容易识别。
- 定期使用
git stash
暂存未完成的工作,避免不必要的临时提交。
常见问题
- 提交已推送到远程仓库:如果提交已经推送给他人,修改提交历史可能会导致问题。可以使用
git revert
命令创建一个新的提交来撤销之前的提交。 git reset
提示输入:某些情况下,git reset HEAD^
可能会提示输入。可以尝试使用git reset HEAD~#numberOfCommits
来选择要重置的本地提交数量。- ZSH 特殊字符问题:在 ZSH 中,
^
是特殊字符,需要进行转义,如git reset --soft HEAD\^
,或者使用HEAD~
避免转义。