Git 高级技巧
2026/3/20大约 11 分钟
Git 高级技巧
掌握 Git 高级特性,提升工作效率
Reflog - 引用日志
理解 Reflog
Reflog 记录了 HEAD 和分支引用的所有变化历史,是恢复"丢失"数据的救命工具。
# 查看 HEAD 的 reflog
git reflog
git reflog show HEAD
# 查看特定分支的 reflog
git reflog show main
# 详细格式
git reflog --date=iso
# 输出示例:
# abc1234 HEAD@{0}: commit: Add feature
# def5678 HEAD@{1}: checkout: moving from main to feature
# ghi9012 HEAD@{2}: commit: Fix bug
# jkl3456 HEAD@{3}: rebase finished: returning to refs/heads/main
使用 Reflog 恢复数据
# 恢复误删的分支
git reflog # 找到删除前的提交
git branch recovered-branch abc1234
# 恢复 reset --hard 后的提交
git reflog # 找到 reset 前的 HEAD
git reset --hard HEAD@{2}
# 恢复误操作的 rebase
git reflog
git reset --hard ORIG_HEAD # 或使用 reflog 中的位置
git reset --hard HEAD@{5}
# 恢复丢失的 stash
git fsck --unreachable | grep commit
git show <commit-hash>
git stash apply <commit-hash>
Reflog 过期策略
# 查看 reflog 过期配置
git config gc.reflogExpire # 默认 90 天
git config gc.reflogExpireUnreachable # 默认 30 天
# 修改过期时间
git config gc.reflogExpire 180 # 保留 180 天
git config gc.reflogExpireUnreachable 60
# 手动清理 reflog
git reflog expire --expire=now --all
git gc --prune=now
子模块(Submodule)
什么是子模块?
子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录,同时保持独立的版本控制。
主仓库/
├── .git/
├── .gitmodules # 子模块配置
├── src/
└── libs/
├── common/ # 子模块1 → 指向外部仓库
│ └── .git # 子模块自己的 git 目录引用
└── utils/ # 子模块2 → 指向另一个外部仓库
└── .git
主仓库记录的是子模块的特定提交,而非文件内容
子模块基本操作
# 添加子模块
git submodule add https://github.com/user/repo.git libs/common
git submodule add -b main https://github.com/user/repo.git libs/common
# 克隆包含子模块的仓库
git clone --recurse-submodules https://github.com/user/main-repo.git
# 或者
git clone https://github.com/user/main-repo.git
cd main-repo
git submodule init
git submodule update
# 一步完成初始化和更新
git submodule update --init
git submodule update --init --recursive # 递归处理嵌套子模块
# 查看子模块状态
git submodule status
git submodule status --recursive
# 进入子模块目录操作
cd libs/common
git checkout main
git pull
cd ../..
git add libs/common
git commit -m "Update submodule"
更新子模块
# 更新所有子模块到最新提交
git submodule update --remote
# 更新特定子模块
git submodule update --remote libs/common
# 指定更新的分支
git config -f .gitmodules submodule.libs/common.branch develop
git submodule update --remote
# 同时拉取主仓库和子模块
git pull --recurse-submodules
# 自动更新子模块
git config submodule.recurse true # 自动在 pull/checkout 时更新子模块
删除子模块
# 完整删除子模块的步骤
# 1. 删除 .gitmodules 中的条目
git config -f .gitmodules --remove-section submodule.libs/common
# 2. 删除 .git/config 中的条目
git config --remove-section submodule.libs/common
# 3. 删除暂存区中的子模块
git rm --cached libs/common
# 4. 删除子模块目录
rm -rf libs/common
# 5. 删除 .git/modules 中的子模块数据
rm -rf .git/modules/libs/common
# 6. 提交更改
git commit -m "Remove submodule libs/common"
子模块替代方案
子模块替代方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| Submodule | 精确版本控制、独立仓库 | 操作复杂、学习曲线陡 |
| Subtree | 简单直观、无需额外学习 | 历史混合、难以追踪上游 |
| 包管理器(npm/pip) | 标准化、版本语义化 | 需要发布包、不适合私有代码 |
| Monorepo(Nx/Lerna) | 统一管理、原子提交 | 仓库可能很大、需要工具支持 |
子树(Subtree)
Subtree 基本操作
# 添加子树
git subtree add --prefix=libs/common https://github.com/user/common.git main --squash
# 拉取子树更新
git subtree pull --prefix=libs/common https://github.com/user/common.git main --squash
# 推送更改到子树
git subtree push --prefix=libs/common https://github.com/user/common.git main
# 分离子树(创建独立的提交历史)
git subtree split --prefix=libs/common -b common-branch
Subtree vs Submodule
# 使用 Subtree 的简化配置
git remote add common https://github.com/user/common.git
# 添加
git subtree add --prefix=libs/common common main --squash
# 更新
git subtree pull --prefix=libs/common common main --squash
# 推送
git subtree push --prefix=libs/common common main
Git LFS(大文件存储)
安装和配置
# 安装 Git LFS
# macOS
brew install git-lfs
# Ubuntu
sudo apt install git-lfs
# Windows
# 包含在 Git for Windows 中,或从 https://git-lfs.github.com 下载
# 初始化 LFS
git lfs install
# 项目中启用 LFS
cd my-project
git lfs install
使用 LFS
# 跟踪大文件类型
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "*.mp4"
git lfs track "assets/**/*.png"
# 查看跟踪的模式
git lfs track
# 查看被 LFS 管理的文件
git lfs ls-files
# 添加 .gitattributes
git add .gitattributes
git commit -m "Configure Git LFS"
# 正常使用 git 命令
git add large-file.psd
git commit -m "Add design file"
git push
# 拉取 LFS 文件
git lfs pull
# 获取特定文件
git lfs fetch --include="*.psd"
# 迁移现有文件到 LFS
git lfs migrate import --include="*.psd"
git lfs migrate import --include="*.psd" --everything # 重写所有历史
LFS 配置
# 克隆时不下载 LFS 文件
GIT_LFS_SKIP_SMUDGE=1 git clone repo-url
# 配置 LFS 传输
git config lfs.concurrenttransfers 8
# 查看 LFS 存储使用
git lfs env
# 锁定文件(防止冲突)
git lfs lock design.psd
git lfs locks
git lfs unlock design.psd
Worktree(工作树)
基本使用
# 在新目录创建工作树
git worktree add ../feature-branch feature-branch
# 创建新分支的工作树
git worktree add -b hotfix ../hotfix main
# 列出所有工作树
git worktree list
# 移动工作树
git worktree move ../feature-branch ../new-location
# 删除工作树
git worktree remove ../feature-branch
# 清理过时的工作树信息
git worktree prune
# 锁定工作树(防止被修剪)
git worktree lock ../feature-branch
git worktree unlock ../feature-branch
使用场景
Worktree 使用场景:
| 场景 | 说明 |
|---|---|
| 同时开发多个功能 | main-repo/(主开发)、../feature-a/、../feature-b/、../hotfix/ |
| 并行编译测试 | 在一个工作树编译,在另一个工作树继续开发 |
| 代码审查 | 在单独的工作树检出 PR 分支进行审查 |
| 保持稳定版本 | 始终有一个工作树指向稳定版本,用于对比 |
示例工作流(紧急修复):
git worktree add -b hotfix/urgent ../hotfix main
cd ../hotfix
# 修复 bug
git commit -am "Fix urgent bug"
git push
cd ../main-repo
git worktree remove ../hotfix
Bisect(二分查找)
基本使用
# 开始二分查找
git bisect start
# 标记当前版本(有问题)
git bisect bad
# 标记已知正常的版本
git bisect good v1.0.0
# Git 会自动检出中间提交,测试后标记
git bisect good # 如果这个版本正常
git bisect bad # 如果这个版本有问题
# 找到问题提交后
# Git 会显示: abc1234 is the first bad commit
# 结束查找
git bisect reset
# 跳过无法测试的提交
git bisect skip
# 查看日志
git bisect log
自动化 Bisect
# 使用脚本自动测试
git bisect start HEAD v1.0.0
git bisect run ./test-script.sh
# test-script.sh 示例:
# #!/bin/bash
# make && ./run-tests
# 返回 0 表示正常,非 0 表示有问题
# 使用命令作为测试
git bisect run npm test
# 使用 grep 查找引入某段代码的提交
git bisect run sh -c 'grep -q "bug_pattern" file.txt && exit 1 || exit 0'
Bisect 技巧
# 只在特定路径中查找
git bisect start -- path/to/file
# 可视化查看 bisect 过程
git bisect visualize
# 使用术语(新/旧替代好/坏)
git bisect start --term-old=working --term-new=broken
git bisect working
git bisect broken
# 重放 bisect 会话
git bisect log > bisect.log
git bisect replay bisect.log
Filter-branch 与 Filter-repo
git filter-repo(推荐)
# 安装 git-filter-repo
pip install git-filter-repo
# 删除所有历史中的特定文件
git filter-repo --path passwords.txt --invert-paths
# 删除目录
git filter-repo --path secrets/ --invert-paths
# 修改作者信息
git filter-repo --email-callback '
if email == b"old@email.com":
return b"new@email.com"
return email
'
# 移动所有文件到子目录
git filter-repo --to-subdirectory-filter subdir/
# 提取子目录为独立仓库
git filter-repo --subdirectory-filter path/to/subdir/
# 替换敏感信息
git filter-repo --replace-text expressions.txt
# expressions.txt 格式:
# password123==>***REMOVED***
# api_key==>***REMOVED***
使用场景
历史重写使用场景:
| 场景 | 说明 |
|---|---|
| 删除敏感数据 | 不小心提交了密码、密钥等 |
| 减小仓库体积 | 删除历史中的大文件 |
| 拆分仓库 | 从单体仓库中提取部分代码 |
| 修复作者信息 | 统一历史提交的邮箱地址 |
⚠️ 警告:
- 这些操作会重写历史,所有提交的 SHA 都会改变
- 需要强制推送,协作者需要重新克隆
- 操作前一定要备份
高级 Rebase 技巧
自动压缩修复提交
# 创建修复提交
git commit --fixup=abc1234
# 创建压缩提交(会丢弃消息)
git commit --squash=abc1234
# 自动压缩
git rebase -i --autosquash main
# 配置默认启用 autosquash
git config --global rebase.autosquash true
批量修改提交
# 交互式变基修改多个提交
git rebase -i HEAD~10
# 批量重写提交消息
git filter-branch --msg-filter 'sed "s/old/new/g"' HEAD~10..HEAD
# 使用 rebase exec 执行命令
git rebase -i HEAD~5
# 在每个 pick 后添加:
# exec npm test
Rebase 保留合并
# 保留合并提交
git rebase --rebase-merges main
# 重新创建合并提交
git rebase -i --rebase-merges HEAD~20
高级 Diff 和 Patch
创建和应用补丁
# 创建补丁文件
git diff > changes.patch
git diff HEAD~3..HEAD > recent.patch
git format-patch HEAD~3 # 每个提交一个文件
# 应用补丁
git apply changes.patch
# 检查补丁是否可以应用
git apply --check changes.patch
# 应用并创建提交(使用 format-patch 创建的补丁)
git am 0001-commit-message.patch
git am *.patch
# 三方合并应用
git apply --3way changes.patch
# 反向应用补丁
git apply -R changes.patch
高级 Diff 选项
# 单词级别差异
git diff --word-diff
git diff --word-diff=color
# 字符级别差异
git diff --word-diff-regex=.
# 忽略空白变化
git diff -w
git diff --ignore-all-space
git diff --ignore-space-change
# 只看特定类型的变化
git diff --diff-filter=A # 只看添加的文件
git diff --diff-filter=D # 只看删除的文件
git diff --diff-filter=M # 只看修改的文件
# 二进制文件差异
git diff --binary
# 统计差异
git diff --stat
git diff --shortstat
git diff --numstat
# 生成可用于 patch 的格式
git diff --no-color > file.patch
Git Attributes
.gitattributes 文件
# 设置默认行为
* text=auto
# 文本文件(统一换行符)
*.txt text
*.md text
*.js text eol=lf
*.sh text eol=lf
# 二进制文件
*.png binary
*.jpg binary
*.pdf binary
# Git LFS
*.psd filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
# 自定义 diff 驱动
*.docx diff=word
# 合并策略
database.yml merge=ours
# 导出时排除
.gitattributes export-ignore
.gitignore export-ignore
test/ export-ignore
# 语言统计
*.md linguist-documentation
*.html linguist-vendored
docs/* linguist-documentation
配置自定义 Diff 驱动
# 配置 Word 文档 diff
git config diff.word.textconv docx2txt
# 配置图片 diff
git config diff.exif.textconv exiftool
# 配置 PDF diff
git config diff.pdf.textconv pdftotext
# 使用(在 .gitattributes 中)
*.docx diff=word
*.jpg diff=exif
*.pdf diff=pdf
高级搜索技巧
组合搜索
# 搜索添加了特定代码的提交
git log -S "function_name" --oneline
# 使用正则搜索
git log -G "function.*deprecated" --oneline
# 搜索提交消息
git log --grep="fix" --grep="bug" --all-match
# 搜索特定作者的特定时间段的提交
git log --author="John" --since="2024-01-01" --until="2024-03-01"
# 组合多个条件
git log --author="John" --grep="feature" --since="2024-01-01" -- src/
# 搜索合并提交
git log --merges --oneline
# 搜索特定文件的历史(包括重命名)
git log --follow -p -- filename
使用 git grep
# 搜索所有分支
git grep "pattern" $(git rev-list --all)
# 搜索并显示函数上下文
git grep -p "pattern" --heading
# 搜索多个模式(AND)
git grep -e "pattern1" --and -e "pattern2"
# 搜索多个模式(OR)
git grep -e "pattern1" -e "pattern2"
# 搜索不包含模式的行
git grep -v "pattern"
# 统计匹配数量
git grep -c "pattern"
性能优化
加速大型仓库
# 浅克隆
git clone --depth 1 https://github.com/user/repo.git
git clone --shallow-since="2024-01-01" https://github.com/user/repo.git
# 部分克隆(不下载 blob)
git clone --filter=blob:none https://github.com/user/repo.git
# 部分克隆(不下载树对象)
git clone --filter=tree:0 https://github.com/user/repo.git
# 稀疏检出
git clone --sparse https://github.com/user/repo.git
cd repo
git sparse-checkout set src/important-module
# 启用文件系统监控
git config core.fsmonitor true
git config core.untrackedCache true
# 启用提交图
git config core.commitGraph true
git commit-graph write --reachable
# 启用多包索引
git config core.multiPackIndex true
git multi-pack-index write
维护命令
# 垃圾回收
git gc
git gc --aggressive # 更彻底,但更慢
# 重新打包
git repack -a -d
# 验证数据完整性
git fsck
git fsck --full
# 清理不需要的文件
git clean -fd
git clean -fdx # 包括忽略的文件
# 修剪过期对象
git prune
实用技巧集合
快捷操作
# 显示文件的当前版本(不检出)
git show main:path/to/file.txt
# 显示文件在特定提交的版本
git show abc1234:path/to/file.txt
# 比较两个分支的特定文件
git diff main..feature -- path/to/file.txt
# 查看谁修改了某行代码
git blame -L 10,20 file.txt
# 查看某次提交中某个文件的变化
git show abc1234 -- file.txt
# 列出某次提交修改的文件
git diff-tree --no-commit-id --name-only -r abc1234
# 查找包含特定提交的分支
git branch --contains abc1234
# 查找两个分支的分叉点
git merge-base main feature
# 显示未合并的提交
git log main..feature --oneline
别名推荐
# 添加到 ~/.gitconfig
[alias]
# 交互式添加
ap = add -p
# 修改最后提交
amend = commit --amend --no-edit
# 查看提交图
graph = log --graph --oneline --all
# 显示今天的工作
today = log --since=midnight --author='YOUR_EMAIL' --oneline
# 显示分支状态
brs = for-each-ref --sort=-committerdate refs/heads/ --format='%(color:yellow)%(refname:short)%(color:reset) - %(color:cyan)%(committerdate:relative)%(color:reset) - %(contents:subject)'
# 快速保存进度
wip = !git add -A && git commit -m 'WIP'
# 撤销 WIP
unwip = reset HEAD~1
# 显示假设未更改的文件
hidden = !git ls-files -v | grep '^h'
# 找出大文件
fat = !git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sed -n 's/^blob //p' | sort -rnk2 | head -20