高级操作
本节涵盖 Git 的高级功能:stash、bisect、tag、worktree、submodule、hooks 等。
stash(暂存工作区)
将未提交的修改"暂存"起来,获得干净的工作区。
# 暂存当前修改
git stash
git stash push -m "WIP: refactoring login"
# 暂存所有(包括未跟踪的文件)
git stash -u
git stash --include-untracked
# 查看 stash 列表
git stash list
# stash@{0}: On main: WIP: refactoring login
# stash@{1}: On feature: Half-done work
# 恢复最近一次 stash(不删除 stash 记录)
git stash apply
git stash apply stash@{1}
# 恢复最近一次 stash(删除 stash 记录)
git stash pop
# 查看 stash 内容
git stash show -p
git stash show -p stash@{1}
# 删除 stash
git stash drop stash@{0}
git stash clear # 清空所有
# 从 stash 创建分支
git stash branch <branch_name> stash@{0}stash 典型场景
# 场景:开发到一半,需要紧急修复 bug
git stash -u # 暂存当前工作
git checkout main
git checkout -b hotfix
# ... 修复、提交 ...
git checkout feature
git stash pop # 恢复之前的工作💡 最佳实践:给 stash 写描述信息(
git stash push -m "message"),否则一段时间后很难回忆起它的内容。
bisect(二分查找)
用二分法定位引入 bug 的提交。自动化排查利器。
# 启动二分查找
git bisect start
# 标记当前版本为 bad(有问题)
git bisect bad HEAD
# 标记已知正常版本为 good
git bisect good v1.0
# Git 会自动 checkout 中间的某个提交
# 测试后标记
git bisect good # 这个版本没问题
git bisect bad # 这个版本有问题
# 重复 good/bad,直到定位到引入 bug 的第一个 commit
# 结束 bisect
git bisect reset # 回到原始分支
# 自动化 bisect(推荐)
git bisect start HEAD v1.0
git bisect run ./test_script.sh # 脚本返回 0=good, 非 0=bad自动化 bisect 示例
# test_script.sh
#!/bin/bash
npm test -- --testPathPattern="failing_test" > /dev/null 2>&1
exit $?
# 自动跑
git bisect start HEAD v2.0
git bisect run ./test_script.sh
# Git 自动二分查找,输出第一个 bad commit
git bisect resettag(标签)
# 轻量标签(只记录引用)
git tag v1.0
# 注释标签(推荐——包含元数据)
git tag -a v1.0 -m "Release version 1.0"
git tag -a v1.0 -m "Release v1.0" <commit_hash> # 为历史提交打标签
# 查看标签
git tag # 列出所有
git tag -l "v1.*" # 通配符过滤
git show v1.0 # 查看标签详情
# 推送标签
git push origin v1.0 # 推送单个
git push --tags # 推送所有本地标签
# 删除标签
git tag -d v1.0 # 本地
git push origin --delete v1.0 # 远程
# 基于标签创建分支
git checkout -b hotfix-v1.0 v1.0describe(描述位置)
git describe <ref>
# 输出:v1.0-5-gabc1234
# │ │ │
# │ │ └── hash 前缀(g = git)
# │ └── 距离 tag 有 5 个提交
# └── 最近的 tag
# 当 ref 正好是一个 tag 时,只输出 tag 名称
git describe HEAD
# → v2.0worktree(多工作目录)
在同一个仓库中同时检出多个分支到不同目录。
# 创建新的 worktree
git worktree add ../project-hotfix hotfix
# 在 ../project-hotfix 目录中检出 hotfix 分支
# 创建并检出到新分支
git worktree add -b new-feature ../project-feature main
# 查看所有 worktree
git worktree list
# 删除 worktree
git worktree remove ../project-hotfix
# 或先手动删除目录,然后:
git worktree prune💡 典型场景:正在 feature 分支开发,需要紧急切换到另一个分支修 bug,又不想 stash。直接用 worktree 在另一个目录操作,互不干扰。
submodule(子模块)
在仓库中引用另一个 Git 仓库。
# 添加子模块
git submodule add <url> <path>
git submodule add git@github.com:lib/common.git libs/common
# 克隆带子模块的仓库
git clone --recurse-submodules <url>
# 已克隆但忘加 --recurse-submodules 时:
git submodule update --init --recursive
# 更新子模块到最新提交
git submodule update --remote
# 查看子模块状态
git submodule status
git submodule foreach git status # 在每个子模块中执行命令🚨 陷阱:子模块会锁死在特定 commit 上。主仓库
git pull不会自动更新子模块,需要手动git submodule update。
subtree(子模块的替代方案)
# 添加外部仓库为子目录
git subtree add --prefix=libs/common <url> main --squash
# 拉取更新
git subtree pull --prefix=libs/common <url> main --squash
# 推送修改回上游
git subtree push --prefix=libs/common <url> main| 特性 | submodule | subtree |
|---|---|---|
| 外部仓库独立性 | 独立引用 | 直接合并到主仓库 |
| 主仓库大小 | 小(只存引用) | 大(存完整代码) |
| 克隆复杂度 | 需要额外步骤 | 普通 clone 即可 |
| 修改并回馈上游 | 较复杂 | 相对简单 |
hooks(钩子)
Git 在执行特定操作前后触发的脚本。
# 客户端钩子(在 .git/hooks/ 下)| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
pre-commit |
commit 前 | lint、格式化检查 |
commit-msg |
编辑 commit 信息后 | 验证 commit 信息格式 |
pre-push |
push 前 | 运行测试 |
pre-rebase |
rebase 前 | 禁止 rebase 特定分支 |
post-checkout |
checkout 后 | 安装依赖 |
post-merge |
merge 后 | 依赖更新提醒 |
# 示例:pre-commit 钩子(自动 lint)
# .git/hooks/pre-commit
#!/bin/sh
npm run lint
if [ $? -ne 0 ]; then
echo "Lint failed. Commit aborted."
exit 1
fi
# 启用钩子(文件需要可执行权限)
chmod +x .git/hooks/pre-commit共享 hooks(husky)
由于 .git/hooks 不在版本控制中,团队通常用工具管理:
# Husky(Node.js 项目)
npm install husky --save-dev
npx husky install
npx husky add .husky/pre-commit "npm run lint"
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'reflog 深度用法
恢复误删分支
# 分支被删除后,通过 reflog 找到其最后的 commit
git reflog
# 找到删除前该分支的 HEAD 位置
git branch recovered_branch <hash>恢复误 reset
git reflog
# HEAD@{0}: reset: moving to HEAD~5 ← 误操作
# HEAD@{1}: commit: Important work ← 丢失的重要提交
git reset --hard HEAD@{1}高级工具速查
| 工具 | 用途 | 核心命令 |
|---|---|---|
git archive |
导出代码快照(不含 .git) | git archive --format=tar HEAD |
git bundle |
离线传输仓库 | git bundle create repo.bundle --all |
git fsck |
校验仓库完整性 | git fsck --lost-found |
git filter-repo |
仓库历史重写(替代 filter-branch) | git filter-repo --path secrets/ --invert-paths |
git notes |
给 commit 添加备注(不改 hash) | git notes add -m "Reviewed" <hash> |
git rebase --onto |
将一段提交移植到新 base | git rebase --onto <newbase> <oldbase> <branch> |
git rerere |
自动重用在冲突解决方法 | git config --global rerere.enabled true |
git blame |
逐行查看修改者 | git blame -L 1,50 <file> |
git format-patch |
生成补丁文件 | git format-patch HEAD~3 |
git am |
应用补丁 | git am < patch.diff |
rebase –onto 详解
# 场景:feature 分支基于 develop,现在要移到 main 上
# git rebase --onto <new-base> <upstream> <branch>
git rebase --onto main develop feature
# 将 feature 中不在 develop 上的提交,移植到 main 上之前:
main: A---B---C---D
\
develop: E---F
\
feature: G---H
git rebase --onto main develop feature
之后:
main: A---B---C---D---G'---H' (feature)
\
develop: E---Frerere(Reuse Recorded Resolution)
# 开启 rerere
git config --global rerere.enabled true
# 当解决完一次冲突后,Git 记录解决方案
# 下次遇到相同冲突时,自动应用记录的解
# 查看记录
git rerere status
git rerere diff💡 典型场景:长期 feature 分支反复 rebase 到 main 时,rerere 会自动解决相同的冲突,大幅减少重复劳动。
Git LFS(大文件存储)
# 安装 Git LFS
git lfs install
# 追踪大文件
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "data/*.csv"
# 查看追踪模式
git lfs track
# .gitattributes 会被自动更新
# *.psd filter=lfs diff=lfs merge=lfs -text
# 之后像普通文件一样使用即可
git add file.psd
git commit -m "Add design file"
git push⚡ Git LFS 将大文件的实际内容存储在单独的服务器上,仓库中只保存指针,大幅缩减 clone 时间。