--soft、--mixed、--hard 到底各改变了哪几个区域?哪些改动会真的丢失?--keep 和 --merge 又保护什么?这篇用同一个起点(HEAD、暂存区、工作区各不相同)逐个模式对账。
HEAD=c3 | 暂存区 x='版本 3 + 暂存的改动' | 工作区 x='版本 3 + 暂存的改动 + 工作区的改动' | 未跟踪 z 还在
git 里一份文件有三个版本:HEAD(最近提交)、暂存区、工作区。reset 的三种模式决定向这三个区域里的几个同步到目标提交。
git reset --soft: HEAD=c2 | 暂存区 x='版本 3 + 暂存的改动' | 工作区 x='版本 3 + 暂存的改动 + 工作区的改动' | 未跟踪 z 还在 git reset --mixed: HEAD=c2 | 暂存区 x='版本 2' | 工作区 x='版本 3 + 暂存的改动 + 工作区的改动' | 未跟踪 z 还在 git reset --hard: HEAD=c2 | 暂存区 x='版本 2' | 工作区 x='版本 2' | 未跟踪 z 还在 规律:--soft 只动 HEAD;--mixed(默认)动 HEAD 和暂存区;--hard 三个都动(未跟踪文件不碰)
--soft 只移动 HEAD(及当前分支);--mixed(默认)再让暂存区与目标一致,工作区不动;--hard 连工作区也改成目标的内容。未跟踪文件三种模式都不碰。
c3 还在对象库里: True;暂存过的 blob 内容: '版本 3 + 暂存的改动'(从没 add 过的“工作区的改动”则没有对象,真的丢了)
$ git reflog -3 --format='%gd: %gs'
HEAD@{0}: reset: moving to HEAD~1
HEAD@{1}: commit: c3
HEAD@{2}: commit: c2
ORIG_HEAD 指向被 reset 掉的 c3: True--hard 覆盖掉的工作区改动里,只有从来没 git add 过的部分是真的丢了;add 过的内容作为 blob 仍在对象库里,被 reset 掉的提交可以通过 reflog 和 ORIG_HEAD 找回。
git reset --keep HEAD~1 → error: Entry 'x' would be overwritten by merge. Cannot merge. / fatal: Could not reset index file to revision 'HEAD~1'. | HEAD=c3 | 暂存区 x='版本 3 + 暂存的改动' | 工作区 x='版本 3 + 暂存的改动 + 工作区的改动' | 未跟踪 z 还在 git reset --merge HEAD~1 → error: Entry 'x' not uptodate. Cannot merge. / fatal: Could not reset index file to revision 'HEAD~1'. | HEAD=c3 | 暂存区 x='版本 3 + 暂存的改动' | 工作区 x='版本 3 + 暂存的改动 + 工作区的改动' | 未跟踪 z 还在 对比:本地改的是 y(c1 到 c2 没碰 y)时 --keep 可以成功: HEAD=c1,x='1',y='y 的本地改动'(本地改动保留)
两者在会覆盖有本地改动的文件时拒绝执行,不像 --hard 无条件覆盖;只改了目标无关的文件时则正常成功并保留本地改动。
$ git reset HEAD~1 -- x; git status --short Unstaged changes after reset: M x MM x ?? z HEAD=c3 | 暂存区 x='版本 2' | 工作区 x='版本 3 + 暂存的改动 + 工作区的改动' | 未跟踪 z 还在 HEAD 没动: True $ git reset -- x; git status --short Unstaged changes after reset: M x M x ?? z
git reset <提交> -- 路径 只替换暂存区里这些路径的内容,HEAD 和工作区都不动;不写提交就是用 HEAD,即“取消暂存”。
$ git log --oneline --all --decorate | sed -E 's/^[0-9a-f]{7} //'
(keep) c3
c2
(HEAD -> main) c1reset 只是移动当前分支的指针,别的分支不受影响。