这一篇在干嘛?

《Pro Git》第 7 章有 16 节,全讲完要一本书。这一篇挑日常回报率最高的几件武器讲透:修订版本怎么指、stash、reset 三棵树、cherry-pick、bisect、子模块和凭证存储。对应第 7 章精选。

指定修订版本的多种写法

git loggit diffcheckout 都要指定「某个版本」,写法越丰富越省事:

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 的条件反射
  • 一堆让历史考古效率翻倍的写法

自测

下一篇:🎛️ 自定义 Git:配置、属性与钩子