Git 里那些每次都要现查的命令
不是速查表,是我自己每周都会用错一遍的那几个。每条后面附一句「什么时候不该用」, 因为大部分事故不是不会用,而是用错了场合。
撤销:先搞清楚要撤哪一层
三层要分清:工作区(改过的文件)、暂存区(add 过的)、提交历史。撤错了层,就变成另一种事故。
# 撤工作区:放弃某个文件的修改,恢复成上次提交的样子
git restore path/to/file
# 撤暂存区:取消 add,但保留改动
git restore --staged path/to/file
# 两者一起来:取消 add 并丢弃修改
git restore --staged --worktree path/to/file
老版本 Git 里对应的是 git checkout -- <file> 和 git reset HEAD <file>,还能用,但 restore 语义更清楚,不容易手滑。
git restore丢弃的改动找不回来。如果那个文件只是还没提交,先用git stash,别直接 restore。
reset 的三个档位
这是最容易记混的一组,区别只在「动到哪一层」:
| 命令 | HEAD | 暂存区 | 工作区 |
|---|---|---|---|
--soft |
回退 | 保留 | 保留 |
--mixed(默认) |
回退 | 清空 | 保留 |
--hard |
回退 | 清空 | 清空 |
想把最近两个提交合成一个重写:git reset --soft HEAD~2,改动会全留在暂存区,重新 commit 一次即可。
这是最安全的「改写历史」方式。
--hard只在确认没有未推送的价值时用。已经推到远端的提交不要 reset 后强推——别人拉到之后再 rebase 会一团乱,那种情况应该用 revert。
revert:公开分支上唯一正确的撤销方式
# 生成一个反向提交,历史不变,安全
git revert <commit>
# 撤销一个合并提交,必须指定保留哪一边
git revert -m 1 <merge-commit>
-m 1 的意思是「以第一父提交为主线」,也就是保留合并进来的分支内容、撤销这次合并本身。这个参数不写会直接报错,很多人卡在这。
reflog:救命的那个
误 reset、误删分支、rebase 搞砸——只要提交过,就还在 reflog 里。默认保留 90 天。
git reflog
# 找到那个 SHA,然后
git reset --hard <sha>
# 或者把删掉的分支捡回来
git branch recover-branch <sha>
注意:从没 commit 过的改动 reflog 里没有。这也是为什么我习惯先在草稿分支上 commit 一次,哪怕是很丑的 WIP。
stash:不止是暂存
git stash -u # 连未跟踪的新文件一起存
git stash list
git stash show -p stash@{0}
git stash pop # 取出并删除记录
git stash apply stash@{0} # 取出但保留记录(更安全)
-u 很关键。默认 stash 不含新建文件,切分支后发现「新写的文件不见了」,多半就是漏了这个参数。
cherry-pick:把某个提交搬到当前分支
git cherry-pick <sha>
git cherry-pick <sha1>^..<sha2> # 一段连续提交
适合「修 bug 的提交要同时进两个分支」。不适合拿它做常规合并——用得多了,说明分支策略有问题。
三个我定下的使用纪律
- 共享分支只用 revert,私有分支随便 reset。判断标准是有没有别人拉过。
- 强推前先看远端有没有新提交:
git fetch && git log --oneline HEAD..@{u},非空就别推。 - rebase 只在自己分支上做,做之前先
git branch backup-xxx留个后路,出问题直接 reset 回去。
这份笔记的目的不是背命令,是减少「手比脑子快」造成的损失。真正救命的其实只有两个:
git reflog 和 git stash -u。