这一篇在干嘛?
《Pro Git》第 7 章有 16 节,全讲完要一本书。这一篇挑日常回报率最高的几件武器讲透:修订版本怎么指、stash、reset 三棵树、cherry-pick、bisect、子模块和凭证存储。对应第 7 章精选。
指定修订版本的多种写法
git log、git diff、checkout 都要指定「某个版本」,写法越丰富越省事:
git log HEAD~2 # HEAD 往前数 2 代(~ 只走第一父提交)
git log HEAD~2..HEAD # 区间:HEAD 最近 2 条
git log feature..main # main 有而 feature 没有的提交(看要 pull 什么)
git log v1.0 ^v1.2 # 在 v1.0 中、不在 v1.2 中的
git log --author="Zhang" --grep="fix" -- README.md # 组合过滤临时切到某个历史版本看看(不建分支也能逛):
git checkout <hash> # 进入 detached HEAD 逛历史
git checkout main # 看完切回来stash:手头的活没干完,急事来了
场景:正改到一半,突然要切分支修线上 bug,直接 checkout 会被拒(有未提交修改)。把工作现场存起来:
git stash # 工作区和暂存区全部收进储藏堆栈,工作区变干净
git stash list # 查看所有储藏
git stash apply # 取回最近的储藏(堆栈保留)
git stash pop # 取回并从堆栈删除(最常用)
git stash drop # 扔掉某个储藏修完急事回来 git stash pop,现场原样恢复。stash 是堆栈结构,可以叠多层。
reset 揭密:三棵树
理解 git reset 的钥匙:Git 内部有三棵「树」——HEAD(上次提交快照)、暂存区、工作目录。reset 的本质是移动 HEAD 所指的分支指针,再按参数决定同步几棵树:
git reset --soft <commit> # 只动 HEAD:提交退回,改动全留在暂存区
git reset --mixed <commit> # 动 HEAD+暂存区(默认):改动留在工作区
git reset --hard <commit> # 三棵全动:改动彻底丢弃(危险!)| 用途 | 命令 |
|---|---|
| 最后一次提交不想要了,改动保留 | git reset --soft HEAD~1 |
| 暂存区全部清空(反操作 add) | git reset(即 --mixed HEAD) |
| 彻底回到某提交,抹掉之后一切 | git reset --hard HEAD~3 ⚠️ |
hard 的危险性
--hard丢弃的未提交工作区改动无法恢复。已提交过的不怕(见下)。另外git checkout <commit> -- <file>可以只把某个文件恢复到指定版本。
误删的提交还能救吗?git reflog 记录了 HEAD 的每一次移动,找到丢失提交的哈希,git reset --hard <hash> 就能回去——只要提交过,几乎都救得回来。
cherry-pick:只摘一个提交
别的分支上有一条提交你想要,但不想整支合并:
git cherry-pick <hash> # 把该提交复制一份应用到当前分支典型场景:修复了 bug 的提交先合到了 main,现在要把这个修复摘到老的维护分支。
bisect:二分法揪出「哪次提交搞坏的」
测试告诉我「v2.0 是好的,现在坏了」,中间几百条提交,一条条 checkout 太蠢。Git 自带二分:
git bisect start
git bisect bad # 当前是坏的
git bisect good v2.0 # v2.0 是好的
# Git 自动切到中间某次提交,你测试后告诉它:
git bisect good # 或 git bisect bad
# ……对半逼近,log2(n) 步定位到元凶
git bisect reset # 结束,回到原分支子模块:仓库里套仓库
项目依赖另一个仓库(比如引用第三方库源码),用子模块把「外部仓库在某个版本」的引用嵌进来:
git submodule add https://github.com/lib/example.git libs/example
git clone --recurse-submodules <主仓库> # 克隆时一并拉子模块
git submodule update --init --recursive # 已克隆的主仓库补拉子模块子模块锁定在某个具体提交上,主仓库记录的只是「指针」。
凭证存储:别再每次输密码
git config --global credential.helper store # 明文存磁盘(简单但裸)
git config --global credential.helper cache # 内存缓存 15 分钟Windows 上用 manager(Git Credential Manager)最省心,macOS 用 osxkeychain,凭证进系统钥匙串。
其他值得一提的
- 交互式暂存
git add -p:把一个大改动按块(hunk)挑选暂存,凑出干净提交。 - 搜索
git grep "TODO":在工作区/历史中全文搜索;git log -S <串>找增删过该串的提交。 - 打包
git bundle create repo.bundle main:把分支打成单文件,用 U 盘离线传仓库(git clone repo.bundle即可恢复)。
小结
- 修订版本写法:
HEAD~n、区间、..与^过滤。 stash存现场、pop取回,切分支救急神器。reset三参数对应三棵树:soft 只动提交、mixed 动到暂存、hard 全动(危险)。reflog是后悔药的后备电源。cherry-pick摘单条提交、bisect二分定位坏提交、子模块嵌套仓库。
学完你会得到:
- reset 三参数一张表选对不慌
- 修 bug 前 stash、查回归 bisect 的条件反射
- 一堆让历史考古效率翻倍的写法
自测
自测
reset --soft HEAD~1和reset --mixed HEAD~1执行后,暂存区和工作区分别是什么状态?- 误
reset --hard后提交找不回来了吗?用什么命令找?- stash 和
reset --hard的本质区别是什么?git log feature..main显示的是哪边的提交?- bisect 中途某次测试结果是「好的」,该执行什么命令?