Git 远不止 add、commit 和 push。掌握 Git 内部原理、分支策略、交互式变基、钩子和 CI/CD 集成将彻底改变你的开发工作流。本指南涵盖 13 个必备 Git 主题,为专业开发者提供实用示例。
关键要点
- Git 将所有内容存储为由 SHA-1 哈希标识的对象(blob、tree、commit),而不是文件差异。
- 选择 Git Flow 处理复杂发布,GitHub Flow 追求简单,或基于主干开发适合高速团队。
- 交互式变基干净地重写历史——使用 squash、fixup、reword 和 edit 来编写有意义的提交。
- Git bisect 执行二分查找,精确定位引入 bug 的提交。
- Git 钩子(pre-commit、commit-msg、pre-push)自动化代码检查、测试和提交信息验证。
- 使用 GPG 或 SSH 密钥签名提交,验证作者身份并建立代码库信任。
Git 内部原理
Git 是一个内容可寻址的文件系统。每个文件、目录和提交都作为对象存储,由 SHA-1 哈希标识。理解这个模型能解释 Git 为何如此快速,以及分支、合并和变基的底层工作原理。
# Explore Git objects
git cat-file -t HEAD # commit
git cat-file -p HEAD # show commit content
git cat-file -p HEAD^{tree} # show tree (directory listing)
# Every object is stored in .git/objects/
# Object types: blob (file), tree (dir), commit, tag
echo "hello" | git hash-object --stdin # compute SHA-1
git rev-parse HEAD # full SHA of HEAD
git rev-parse --short HEAD # short SHA
# Pack files compress objects for efficiency
git count-objects -vH # show object stats
git gc # garbage collect and pack
git verify-pack -v .git/objects/pack/*.idx | head对象模型
运行 git add 时,Git 为每个文件创建 blob 对象。提交时,Git 为每个目录创建 tree 对象和一个指向根 tree 的 commit 对象。分支和标签只是包含提交 SHA 的文件,使它们成为轻量级引用而非数据副本。
分支策略
分支策略定义了团队如何创建、合并和发布代码。正确的选择取决于团队规模、发布节奏和部署模型。业界已形成三种主流策略。
# Git Flow: structured releases
git flow init # initialize git-flow
git flow feature start user-auth # create feature branch
git flow feature finish user-auth # merge to develop
git flow release start 2.0.0 # create release branch
git flow release finish 2.0.0 # merge to main + develop
# GitHub Flow: simple continuous delivery
git checkout -b feat/add-search # branch from main
git push -u origin feat/add-search # push and open PR
# PR review -> merge -> auto deploy
# Trunk-based: short-lived branches
git checkout -b fix/typo main
git commit -am "fix: correct typo in header"
git push origin fix/typo # merge within hours分支命名规范
一致的分支名称提高可读性和自动化能力。常见模式包括 type/description(feat/user-auth、fix/login-bug)、type/ticket-id(feat/JIRA-123)和 owner/description(alice/refactor-api)。许多团队使用 feat/、fix/、hotfix/、chore/ 或 docs/ 前缀来自动分类分支。
交互式变基
交互式变基让你在分享之前重写提交历史。可以将多个提交压缩为一个、改写提交信息、重排提交顺序或编辑提交进行拆分。这使历史保持清晰有意义。
# Interactive rebase last 4 commits
git rebase -i HEAD~4
# Editor opens with:
# pick a1b2c3d Add user model
# pick e4f5g6h Fix typo in model -> fixup
# pick i7j8k9l Add user controller -> squash
# pick m0n1o2p Update error messages -> reword
# Commands:
# pick = keep commit as-is
# squash = combine with previous, edit message
# fixup = combine with previous, discard message
# reword = keep commit, edit message
# edit = pause to amend the commit
# drop = remove commit entirely
# Abort if something goes wrong
git rebase --abortAutosquash 工作流
使用 git commit --fixup=<sha> 创建修正提交,然后 git rebase -i --autosquash 自动将它们移到目标提交旁边并设置 fixup 命令。这个工作流让你在工作时进行修正,之后再清理,无需手动重排。
Cherry-Pick 和 Bisect
Cherry-pick 将特定提交从一个分支应用到另一个分支而不合并整个分支。Bisect 使用二分查找定位引入 bug 的提交。它们是必备的调试和选择性合并工具。
# Cherry-pick a single commit to current branch
git cherry-pick abc1234
# Cherry-pick range without committing
git cherry-pick abc1234..def5678 --no-commit
git commit -m "feat: backport fixes from develop"
# Bisect: find the bug-introducing commit
git bisect start
git bisect bad # current commit is broken
git bisect good v1.0.0 # this tag was working
# Git checks out midpoint, test it, then:
git bisect good # or git bisect bad
# Repeat until Git identifies the culprit
git bisect reset # return to original HEAD
# Automated bisect with a test script
git bisect start HEAD v1.0.0
git bisect run npm test自动化 Bisect
为了最大效率,编写一个能重现 bug 的小脚本,失败时返回非零退出码。将它传给 git bisect run,Git 会无人值守地执行整个二分查找。对于 1000 个提交,bisect 只需约 10 次测试即可找到罪魁祸首。
暂存和工作树
Git stash 临时搁置未提交的更改,方便切换分支。工作树让你在独立目录中同时检出多个分支,避免频繁暂存和切换分支。
# Stash with a descriptive message
git stash push -m "WIP: auth refactor"
git stash list # list all stashes
git stash show -p stash@{0} # show stash diff
git stash pop # apply and remove
git stash apply stash@{1} # apply without removing
git stash drop stash@{2} # delete specific stash
# Stash including untracked files
git stash push -u -m "include new files"
# Worktrees: multiple branches simultaneously
git worktree add ../hotfix-branch hotfix/v2
git worktree add ../feature-branch feat/search
git worktree list # list all worktrees
git worktree remove ../hotfix-branch工作树使用场景
常见工作树场景包括:在处理自己功能的同时审查 PR、在一个分支上运行长时间构建的同时在另一个分支上编码、并排比较不同分支的行为、以及维护一个始终检出并准备好进行紧急补丁的生产热修复分支。
从暂存创建分支
如果暂存了更改但发现它们应该有自己的分支,使用 git stash branch new-branch-name stash@{0}。这会从暂存最初创建时的提交创建新分支,应用暂存并删除它。这是将暂存工作提升为正式功能分支最干净的方式。
Git 钩子
钩子是 Git 在提交、推送或合并等事件前后运行的脚本。它们强制执行代码质量标准、验证提交信息、运行测试并防止坏代码进入仓库。
#!/bin/sh
# .husky/pre-commit
npx lint-staged
# .husky/commit-msg
npx --no -- commitlint --edit "\$1"
# package.json lint-staged config
# "lint-staged": {
# "*.{js,ts}": ["eslint --fix", "prettier --write"],
# "*.css": ["stylelint --fix"]
# }
# Setup husky in a project
npx husky init
echo "npx lint-staged" > .husky/pre-commit
echo "npx commitlint --edit \$1" > .husky/commit-msg钩子管理器比较
Husky 是最流行的钩子管理器,拥有 30k+ GitHub star,通过 .husky/ 目录简单配置。Lefthook 更快,用 Go 编写,支持并行钩子执行。simple-git-hooks 是零依赖的替代方案,适合需要最小化设置的项目。三者都将钩子存储在仓库中,确保每个贡献者运行相同的检查。
lint-staged 配置
lint-staged 仅对暂存文件运行检查器,即使在大型仓库中也能保持 pre-commit 钩子快速。在 package.json 或单独的 .lintstagedrc 文件中配置。将 glob 模式映射到命令:JavaScript 文件到 ESLint 和 Prettier,CSS 到 Stylelint,Markdown 到 markdownlint。检查失败会中止提交,防止格式不好的代码进入仓库。
子模块和子树
子模块和子树让你在项目中包含外部仓库。子模块维护指向另一个仓库特定提交的指针。子树将外部仓库历史直接合并到你的项目树中。
# Submodules: pointer to external repo commit
git submodule add https://github.com/lib/utils.git libs/utils
git submodule update --init --recursive
git submodule update --remote # pull latest
# Clone repo with submodules
git clone --recurse-submodules https://github.com/org/project
# Subtrees: merge external repo into your tree
git subtree add --prefix=libs/utils \
https://github.com/lib/utils.git main --squash
# Pull updates from subtree remote
git subtree pull --prefix=libs/utils \
https://github.com/lib/utils.git main --squash
# Push changes back to subtree remote
git subtree push --prefix=libs/utils \
https://github.com/lib/utils.git mainMonorepo vs Multi-repo
Monorepo 将所有项目存储在单个仓库中,简化依赖管理和跨项目原子更改。Multi-repo 给团队自主权和隔离性。子模块桥接两种方式:父仓库在特定提交引用子仓库。Nx、Turborepo 和 Lerna 等工具帮助管理 monorepo 构建和依赖图。
合并 vs 变基
合并创建合并提交保留分支历史。变基将提交重放到另一个分支之上创建线性历史。每种方式在可读性、冲突解决和协作安全方面各有取舍。
# Merge: preserves branch history
git checkout main
git merge feature/auth --no-ff # always create merge commit
# Rebase: linear history
git checkout feature/auth
git rebase main # replay commits on main
git checkout main
git merge feature/auth # fast-forward merge
# Resolve conflicts during rebase
git rebase main
# CONFLICT in file.js
# Edit file.js to resolve
git add file.js
git rebase --continue
# Merge strategies
git merge -s ours legacy-branch # keep ours, discard theirs
git merge -X theirs feature # prefer theirs on conflict冲突解决技巧
使用 git mergetool 打开可视化差异工具。配置 merge.conflictstyle=diff3 同时查看共同祖先和冲突双方。解决后使用 git diff --check 验证没有残留冲突标记。启用 rerere(重用已记录的解决方案)通过 git config rerere.enabled true 自动解决重复冲突。
压缩合并策略
压缩合并将功能分支的所有提交合并为目标分支上的单个提交。这创建了干净的主分支历史,每个提交代表一个完整功能。代价是失去功能分支的单独提交历史。GitHub 和 GitLab 提供压缩合并作为 PR 合并选项,与常规合并和变基合并并列。
Git Reflog
reflog 记录 HEAD 和分支指针的每次变更,包括提交、变基、重置和检出。它是恢复丢失提交、撤销错误变基和恢复已删除分支的安全网。
# View reflog (every HEAD movement)
git reflog
# abc1234 HEAD@{0}: commit: add feature
# def5678 HEAD@{1}: rebase: updating HEAD
# ghi9012 HEAD@{2}: checkout: moving to main
# Recover from bad rebase
git reset --hard HEAD@{2} # go back to before rebase
# Restore a deleted branch
git reflog | grep "checkout: moving from feature"
git checkout -b feature/restored abc1234
# Recover a dropped stash
git fsck --unreachable | grep commit
git stash apply <sha>
# Reflog for a specific branch
git reflog show feature/auth常见恢复模式
最常见的恢复场景包括:撤销错误变基(重置到变基前的 reflog 条目)、恢复已删除的分支(在 reflog 中找到最后的提交 SHA)、恢复修改过的提交(之前的版本存在于 reflog 中)、以及找回丢弃的暂存(使用 git fsck 查找不可达的提交对象)。
Reflog vs git fsck
reflog 跟踪 HEAD 和分支指针的移动,所以它能找到最近引用过的提交。git fsck --unreachable 找到所有孤立对象,包括从未被任何分支引用的对象。先用 reflog 进行近期恢复,fsck 作为最后手段用于超出 reflog 窗口期的对象。
签名提交
签名提交证明代码由已验证的身份创建。GitHub 和 GitLab 在签名提交旁显示已验证徽章。可以使用 GPG 密钥或 SSH 密钥(Git 2.34+)签名。
# GPG signing setup
gpg --full-generate-key # generate GPG key
gpg --list-secret-keys --keyid-format=long
git config --global user.signingkey ABC123DEF456
git config --global commit.gpgsign true
# SSH signing (Git 2.34+)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
# Sign a single commit
git commit -S -m "feat: signed commit"
# Verify signatures
git log --show-signature -1
git verify-commit HEAD
git verify-tag v1.0.0警戒模式
GitHub 提供警戒模式,将未签名的提交标记为未验证,明确显示提交缺少有效签名。在 GitHub 设置的 SSH 和 GPG 密钥中启用它。结合要求签名提交的分支保护规则,这为代码库建立了强大的信任链。
为发布签名标签
使用 git tag -s v1.0.0 创建签名的注释标签。签名标签验证发布由授权人员创建。CI 管道可以在构建和部署前验证标签签名。这对开源项目尤其重要,因为任何人都可以提交 PR,但只有维护者应该创建发布。
大文件存储
Git LFS 用轻量级指针文件替换仓库中的大文件(图片、视频、二进制文件、数据集),将实际文件内容存储在远程服务器上,保持仓库小巧、克隆快速。
# Install and initialize Git LFS
git lfs install
# Track file patterns
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "datasets/**"
# .gitattributes is updated automatically
# *.psd filter=lfs diff=lfs merge=lfs -text
git add .gitattributes
git add assets/design.psd
git commit -m "feat: add design files with LFS"
# Check LFS status
git lfs ls-files # list tracked files
git lfs status # show pending changes
git lfs env # show LFS config
# Migrate existing files to LFS
git lfs migrate import --include="*.psd"LFS 最佳实践
在使用 LFS 跟踪文件之前先将 .gitattributes 添加到仓库。使用 git lfs migrate 转换现有大文件。为无法合并的二进制文件(设计文件、编译资源)设置 LFS 锁定。监控 LFS 存储使用量,因为大多数托管商对超出免费额度的带宽和存储收费。
高级 Log 和 Diff
Git log 和 diff 有强大的格式化和过滤选项。Pretty 格式为变更日志和报告创建自定义输出。patience 和 histogram 差异算法为复杂变更产生更清晰的差异。
# Pretty log formats
git log --oneline --graph --all --decorate
git log --pretty=format:"%h %an %ar %s" -10
git log --since="2 weeks ago" --author="Alice"
git log --grep="fix:" --oneline
# Diff algorithms for cleaner output
git diff --patience file.js # better for moved blocks
git diff --histogram # improved patience
git diff --word-diff # inline word changes
# Find who changed a line
git blame -L 10,20 src/app.js
git log -p -S "functionName" # pickaxe search
# Shortlog for changelogs
git shortlog -sn --no-merges # commit count by author
git log --format="%s" v1.0..v2.0 # messages between tags实用 Git 别名
为常用的 log 和 diff 命令创建别名。在全局 .gitconfig 的 [alias] 部分添加它们。流行的别名包括 lg(装饰图形日志)、last(显示最后一次提交)、unstage(重置暂存文件)和 amend(修改最后一次提交而不编辑信息)。
仓库统计
使用 git shortlog -sn 按提交数排名贡献者。使用 git log --format="%ae" | sort | uniq -c | sort -rn 按邮箱列出贡献者。git diff --stat 命令显示更改文件的摘要。对于详细的贡献分析,git-fame 和 gitstats 等工具生成包含图表和时间线的综合报告。
CI/CD 集成
Git 与 CI/CD 管道紧密集成。约定式提交通过语义化发布实现自动版本管理。GitHub Actions 工作流在推送、拉取请求或标签事件时触发,自动构建、测试和部署。
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
# Conventional commits + semantic release
# feat: -> minor, fix: -> patch, BREAKING CHANGE: -> major
npm install -D semantic-release
npx semantic-release # auto version + changelog分支保护规则
在 main 上配置分支保护,要求拉取请求审查、通过 CI 检查、签名提交和线性历史。这防止直接推送到 main,确保代码审查,并维护干净的提交历史。GitHub、GitLab 和 Bitbucket 都支持这些规则,粒度各有不同。
自动变更日志生成
conventional-changelog 和 release-please 等工具解析约定式提交信息生成格式化的变更日志。每个发布部分按类型分组更改:功能、错误修复、破坏性更改和文档更新。这消除了手动维护变更日志的需要,确保每个合并的 PR 都有记录。
常见问题
git merge 和 git rebase 有什么区别?
合并创建合并提交组合两个分支,保留完整历史。变基将你的提交重放到目标分支之上,创建线性历史。共享分支用合并,本地清理用变基。
如何撤销 git rebase?
使用 git reflog 找到变基前的提交,然后 git reset --hard 到该提交。reflog 记录每次 HEAD 变更,所以总能从错误变基中恢复。
应该使用 Git Flow 还是基于主干开发?
有计划发布和多环境的团队用 Git Flow。实践持续部署、使用功能标志和短生命周期分支的团队用基于主干开发。
如何用 SSH 密钥签名提交?
配置 git 的 gpg.format=ssh 和 user.signingkey 指向你的 SSH 公钥。然后使用 git commit -S 签名。Git 2.34+ 原生支持 SSH 签名。
什么是 git bisect,什么时候使用?
Git bisect 对提交历史执行二分查找,精确定位引入 bug 的提交。标记已知的好提交和坏提交,bisect 测试中间点直到隔离出问题提交。
Git LFS 如何工作?
Git LFS 用小指针文件替换仓库中的大文件。实际内容存储在独立的 LFS 服务器上。Git LFS 钩子拦截检出和推送操作,透明地下载和上传大文件。
最有用的 git 钩子有哪些?
Pre-commit 在每次提交前运行检查器和格式化器。Commit-msg 验证提交信息格式。Pre-push 在推送前运行测试。使用 Husky 或 lefthook 在团队中管理钩子。
如何恢复已删除的分支?
使用 git reflog 找到删除分支的最后一个提交,然后 git checkout -b branch-name commit-sha 重建。reflog 条目默认保留 90 天。