Chapter 10

GitHub 协作与 Git 内部原理

深入 Git 对象模型——理解一切皆 SHA 指针的设计,以及 GitHub 高级协作实践

GitHub 协作最佳实践

PR 大小与描述

一个好的 Pull Request 的准则:

PR 描述模板

在仓库 .github/PULL_REQUEST_TEMPLATE.md 中设置 PR 模板:

## Why(为什么做这个改动)
关联 Issue: #123
解决了用户无法在移动端完成注册的问题。

## What(做了什么改动)
- 修复了手机号验证正则表达式
- 增加了对国际区号的支持
- 新增了 3 个单元测试用例

## How to test(如何测试)
- [ ] 访问 /register,用手机号注册
- [ ] 测试国际号码格式(+1-555-1234567)
- [ ] 确认错误信息显示正确

## Screenshots(截图,如有 UI 变更)

CODEOWNERS 文件

.github/CODEOWNERS 文件中定义代码所有者,当相关路径的文件被修改时,自动添加指定成员为 Reviewer:

# .github/CODEOWNERS
# 格式:路径模式  @用户名或@组

# 整个仓库的默认 owner
*                    @org/core-team

# 前端代码由前端团队负责
/src/frontend/       @org/frontend-team

# 支付相关代码需要特定成员审核
/src/payment/        @alice @bob

# 基础设施配置
/infra/              @org/devops-team
*.yml                @org/devops-team

GitHub 高级功能

Protected Branch(保护分支)

在 GitHub 仓库的 Settings → Branches 中可以为 main 等重要分支设置保护规则:

GitHub Actions 简介

GitHub Actions 是 GitHub 内置的 CI/CD 平台,与 Git Hooks 的核心区别:

维度Git HooksGitHub Actions
运行位置开发者本地机器GitHub 云端服务器
可被绕过可以(--no-verify)不可以(服务端运行)
触发条件Git 命令事件push、PR、定时等任意事件
典型用途本地快速检查完整 CI、自动部署
# .github/workflows/ci.yml
name: CI

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
      - run: npm run build

Git 内部原理——对象模型

理解 Git 的内部原理,是真正掌握 Git 的关键。Git 的底层其实是一个简单而优雅的内容寻址文件系统(Content-Addressable Filesystem)。

四种 Git 对象

Git 中的一切都存储为对象,存放在 .git/objects/ 目录中。每个对象由其内容的 SHA-1 哈希唯一标识。

对象之间的关系

tag v1.0.0

commit ──▶ parent commit ──▶ ...

tree (root/)
├──▶ blob (README.md)
├──▶ tree (src/)
│ ├──▶ blob (app.js)
│ └──▶ blob (utils.js)
└──▶ tree (tests/)
└──▶ blob (app.test.js)

亲手查看 Git 对象

# 查看某个文件对应的 blob 哈希
git hash-object README.md

# 查看 HEAD 指向的 commit 对象内容
git cat-file -p HEAD

# 输出示例:
# tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
# parent a3f2c1d8e5b9f2130459a781cd13f58cc6f88c9
# author 张三 <zhang@example.com> 1710000000 +0800
# committer 张三 <zhang@example.com> 1710000000 +0800
# feat: add profile page

# 查看 tree 对象的内容
git cat-file -p HEAD^{tree}
# 输出:100644 blob abc1234... README.md
#       040000 tree def5678... src

# 查看对象类型
git cat-file -t HEAD  # 输出:commit

# 在 .git/objects/ 中直接查看(二进制压缩,需要解压)
ls .git/objects/

引用(Refs)

分支和标签都是引用——存储在 .git/refs/ 目录下的文本文件,内容就是一个 40 字符的 commit 哈希。

# 查看 main 分支指向哪个 commit
cat .git/refs/heads/main

# 查看某个 tag 指向哪个对象
cat .git/refs/tags/v1.0.0

# 查看 HEAD 文件(指向当前分支)
cat .git/HEAD
# 输出:ref: refs/heads/main

# 创建分支本质上就是创建一个包含 commit 哈希的文件
echo a3f2c1d... > .git/refs/heads/new-branch

"Git 中一切都是 SHA 指针"的意义

理解这个设计哲学,可以解释很多 Git 的行为:

常见 Git 问题排查

问题 1:合并后发现 bug,如何回滚到合并前

# 找到 merge commit 的哈希
git log --oneline --merges

# 方案 A:revert merge commit(安全,适合公共分支)
# -m 1 表示保留第一个父(被合并进来的 main)
git revert -m 1 merge-commit-hash

# 方案 B:reset --hard 到合并前(适合个人分支)
git reset --hard commit-before-merge

问题 2:不小心提交了密钥,如何从历史中彻底删除

!

密钥一旦提交并推送,应立即撤销密钥!即使从历史中删除,也无法确保没有人缓存了那段历史。删除历史只是减少曝光风险,不能代替撤销密钥。

# 使用 git-filter-repo(推荐,比 filter-branch 快得多)
pip install git-filter-repo

# 从所有历史中删除含有密钥的文件
git filter-repo --path .env --invert-paths

# 或者替换文件中的敏感内容
git filter-repo --replace-text replacements.txt
# replacements.txt 格式:
# literal:my-secret-key==>REDACTED

# 处理完后强制推送所有分支(会改写历史!)
git push --force --all
git push --force --tags

# 通知所有协作者重新 clone 仓库

BFG Repo Cleaner 是另一个流行工具(bfg.jar),比 git filter-repo 更简单但功能略少。命令:java -jar bfg.jar --delete-files .env

问题 3:大文件误入仓库

将大型二进制文件(视频、数据集、编译产物)提交到 Git 仓库会导致仓库体积膨胀,克隆速度极慢。

# 使用 git-filter-repo 从历史中移除大文件
git filter-repo --strip-blobs-bigger-than 10M

# 或者使用 Git LFS(Large File Storage)管理大文件
# Git LFS 用指针文件替代实际的大文件,实际内容存储在 LFS 服务器

# 安装 Git LFS
git lfs install

# 追踪特定类型的大文件
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "datasets/**"

# 必须将 .gitattributes 文件提交到仓库
git add .gitattributes
git commit -m "chore: configure Git LFS for large files"

# 之后 add/commit/push 大文件,它们会自动走 LFS 通道

问题 4:仓库太大,克隆太慢

# 浅克隆(只拉取最近 N 次提交)
git clone --depth 1 https://github.com/user/large-repo.git

# 只克隆指定分支
git clone --single-branch --branch main URL

# 部分克隆(Git 2.19+,只下载需要的对象)
git clone --filter=blob:none URL  # 不下载 blob,按需加载

学习之旅完成!你已经学习了从 Git 基础到内部原理的全部核心内容。Git 是一门需要实践的技艺——从今天起,在每个项目中有意识地运用这些知识:规范 commit 信息、合理使用分支、通过 PR 协作。熟练之后,你会发现 Git 不只是工具,而是一种思考代码演进的方式。