version-control-discipline
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVersion Control Discipline
版本控制规范
Version control is the documentation of how the code came to be. The asymmetry: code is read more often than written, and commits and PRs are read by people who are not the author — reviewers today, debuggers tomorrow, archaeologists six months from now. Sloppy history is a tax that compounds.
The discipline has two halves: what lands on the main branch (atomic, buildable, well-described, bisectable) and how you get it there (small PRs, sensible branching, clean rebases on your own branches, never on shared ones).
版本控制是记录代码演变过程的文档。这里存在一种不对称性:代码被阅读的次数远多于被编写的次数,而提交记录和PR会被非作者阅读——包括当前的评审者、未来的调试人员,以及六个月后的代码维护者。混乱的提交历史会像复利一样不断产生技术债务。
这项规范分为两部分:最终合并到主分支的内容(原子化、可构建、描述清晰、便于bisect)和合并到主分支的方式(小型PR、合理的分支策略、在个人分支上进行干净的rebase,绝不在共享分支上操作)。
What main looks like, when discipline holds
遵循规范时主分支的样子
- Every commit on main builds.
- Every commit on main passes the test suite (or at minimum the regression test you'd use as a oracle).
git bisect - Each commit is one logical change. Refactor and behavior change land in separate commits — not separate paragraphs of one commit.
- Each commit message tells you why, not just what. The diff is the what.
- on a path reads like a reasoned changelog.
git log --onelineanswers "why does this line exist?"git log -p
The killer use case for this discipline is . Binary-searching across a clean history pinpoints a regression in log₂(n) commits. Across a history of commits that don't individually build, bisect produces noise. The Linux kernel uses bisect on a multi-million-line codebase routinely — the practice scales as far as the discipline holds.
git bisectwip / fix / address PR comments- 主分支上的每个提交都能成功构建。
- 主分支上的每个提交都能通过测试套件(至少能通过你用作判定依据的回归测试)。
git bisect - 每个提交对应一个逻辑变更。重构和行为变更应分开提交,而不是放在同一个提交的不同段落中。
- 每个提交消息要说明为什么做这个变更,而不只是做了什么。diff已经展示了具体内容。
- 针对某路径执行时,输出应像一份条理清晰的变更日志。
git log --oneline能回答“这行代码为什么存在?”git log -p
这项规范最强大的应用场景是。在整洁的提交历史上进行二分查找,可以在*log₂(n)*次提交内定位到回归问题。而在充满这类无法单独构建的提交历史中,bisect只会产生无效结果。Linux内核经常在数百万行代码的仓库中使用bisect——只要遵循规范,这种方法就能无限扩展。
git bisectwip / fix / 处理PR评论Commit messages
提交消息
Two canonical sources:
- Tim Pope, A Note About Git Commit Messages (2008). The 50 / 72 rule: subject ≤ 50 characters; body wrapped at 72. Imperative subject ("Fix bug," not "Fixed bug" or "Fixes bug") because that matches the language of ,
git revert, and the rest of the tool surface. The blank line between subject and body is structural —git mergeand other tools rely on it.git rebase - Chris Beams, How to Write a Git Commit Message (2014). Seven rules, expanding Pope's:
- Separate subject from body with a blank line.
- Limit the subject line to 50 characters.
- Capitalize the subject line.
- Do not end the subject line with a period.
- Use the imperative mood in the subject line.
- Wrap the body at 72 characters.
- Use the body to explain what and why vs. how.
Beams' load-bearing line: "A diff will tell you what changed, but only the commit message can properly tell you why."
The tactical version: subject is what a busy reviewer reads in a list. Body is what an investigator reads two years later trying to understand whether the constraint that produced this code still applies. The body is for reasons — the constraint, the bug being fixed, the trade-off rejected, the upstream issue. Not for restating the diff.
有两份权威参考资料:
- Tim Pope,《关于Git提交消息的说明》(2008):50/72规则:主题行不超过50个字符;正文每行不超过72个字符。主题行使用祈使语气(如“Fix bug”,而非“Fixed bug”或“Fixes bug”),因为这与、
git revert等工具的输出语言一致。主题行和正文之间的空行是结构化要求——git merge等工具依赖这个格式。git rebase - Chris Beams,《如何编写Git提交消息》(2014):七条规则,扩展了Pope的内容:
- 主题行与正文之间用空行分隔。
- 主题行限制在50个字符以内。
- 主题行首字母大写。
- 主题行末尾不加句号。
- 主题行使用祈使语气。
- 正文每行不超过72个字符。
- 正文用于解释做了什么和为什么做,而非怎么做。
Beams的核心观点:“diff会告诉你做了什么变更,但只有提交消息才能准确告诉你为什么做这个变更。”
实操层面:主题行是忙碌的评审者在列表中会快速浏览的内容;正文是两年后调查人员为了解当初编写这段代码的约束条件是否仍然适用时会阅读的内容。正文要说明原因——比如约束条件、修复的bug、舍弃的方案、上游问题等,不要重复diff的内容。
Conventional Commits — when it helps, when it's theater
约定式提交——何时有用,何时多余
Conventional Commits (, , , etc.) are useful when downstream tooling consumes them: automated CHANGELOGs, semver-driven releases, monorepo release tools (Changesets, semantic-release), library publishing. They are bureaucratic theater on internal SaaS apps with continuous deployment where releases aren't versioned — the prefix becomes ritual without semantic value.
feat:fix:refactor:Default: Beams' seven rules. Layer Conventional Commits only when something in your pipeline reads them.
约定式提交(、、等前缀)在下游工具需要解析时很有用:比如自动生成CHANGELOG、基于语义化版本的发布、单仓库发布工具(Changesets、semantic-release)、库发布等。但在使用持续部署、不做版本标记的内部SaaS应用中,这些前缀就成了无意义的形式主义——前缀沦为一种仪式,没有实际语义价值。
feat:fix:refactor:默认遵循Beams的七条规则。只有当你的流水线需要解析提交消息时,再添加约定式提交的前缀。
Branching models
分支模型
The honest take in 2026:
- Trunk-based development (Paul Hammant, ) is the default for continuous-delivery shops. Developers integrate to a single branch (
trunkbaseddevelopment.com/main) at high frequency. Branches, when used, are short-lived (hours to a day or two). Incomplete features hide behind release flags. DORA's research consistently correlates trunk-based development with strong delivery performance through the small-batch mechanism (small changes are easier to review, debug, and roll back). Concretely, DORA's threshold: three or fewer active branches, merge to trunk at least daily, no code freezes.trunk - GitFlow (Vincent Driessen, A successful Git branching model, 2010) — five branch kinds (,
master,develop,feature/*,release/*) — was the default for a decade. Driessen himself added a 2020 note retiring it for continuous-delivery contexts: "If your team is doing continuous delivery of software, I would suggest to adopt a much simpler workflow (like GitHub flow) instead of trying to shoehorn git-flow into your team." GitFlow remains appropriate for versioned, on-prem, multi-version-supported software — libraries, OS distributions, enterprise installers. For SaaS in 2026 it is overhead.hotfix/* - GitHub Flow — the lightweight default. main + short-lived feature branches; merge via PR; main is always deployable. The simplest model that works for most webapps and services.
- GitLab Flow — variant of GitHub Flow with environment branches () for staged deploys. Also supports
main → pre-production → production,release/v1for the versioned-software middle ground.release/v2
Pick by what you actually ship. Continuous delivery ⟹ trunk-based or GitHub Flow. Versioned product with multiple maintained majors ⟹ GitFlow or GitLab Flow with release branches.
2026年的务实观点:
- 主干开发(Paul Hammant,)是持续交付团队的默认选择。开发者高频集成到单个分支(
trunkbaseddevelopment.com/main)。即使使用分支,也都是短期分支(几小时到一两天)。未完成的功能通过发布隐藏。DORA的研究一致表明,主干开发通过小批量机制与优秀的交付性能相关联——小型变更更易于评审、调试和回滚。具体来说,DORA的阈值:活跃分支不超过3个,每天至少合并到主干一次,无代码冻结。trunk - GitFlow(Vincent Driessen,《成功的Git分支模型》,2010)——五种分支类型(、
master、develop、feature/*、release/*)——曾经是十年间的默认选择。Driessen本人在2020年添加了说明,宣布在持续交付场景中弃用它:“如果你的团队正在进行软件的持续交付,我建议采用更简单的工作流(比如GitHub Flow),而不是强行让GitFlow适配你的团队。” GitFlow仍然适用于有版本标记、本地化部署、支持多版本维护的软件——比如库、操作系统发行版、企业安装包。对于2026年的SaaS场景来说,它是一种额外负担。hotfix/* - GitHub Flow——轻量级默认方案。主分支加短期特性分支;通过PR合并;主分支始终可部署。这是适用于大多数Web应用和服务的最简单模型。
- GitLab Flow——GitHub Flow的变体,增加了环境分支()用于分阶段部署。也支持
main → pre-production → production、release/v1等分支,适配需要版本标记的软件场景。release/v2
根据实际的交付方式选择分支模型。持续交付⟹主干开发或GitHub Flow。支持多版本维护的有版本标记产品⟹GitFlow或带发布分支的GitLab Flow。
Rebase vs. merge
Rebase vs Merge
The technical difference: merge preserves topology (a merge commit with two parents); rebase rewrites history into a linear sequence (each rebased commit gets a new SHA). Both are legitimate; the question is when.
The canonical advice: rebase to clean up before merging; merge to integrate. Use rebase to polish your private feature branch (squash fixups, reorder, reword); use merge or fast-forward to land it on main.
技术差异:merge会保留提交拓扑结构(一个有两个父节点的合并提交);rebase会将提交历史重写为线性序列(每个被rebase的提交都会获得新的SHA值)。两者都是合法操作,问题在于何时使用。
权威建议:合并前用rebase清理历史;用merge进行集成。使用rebase来优化你的私有特性分支(压缩修复提交、调整顺序、修改提交消息);使用merge或快进合并将分支合并到主分支。
Linus's rule
Linus规则
Jonathan Corbet's Rebasing and merging: some git best practices on LWN (April 2009) summarized the kernel-community rule as:
Thou Shalt Not Rebase Trees With History Visible To Others.
The phrase is Corbet's distillation, not a verbatim Linus quote — Linus's own framing is closer to "if you're still in the 'git rebase' phase, you don't push it out." Either way, the rule stands. The Linux kernel maintainer documentation reinforces it: "History that has been exposed to the world beyond your private system should usually not be changed. … If you have pulled changes from another developer's repository, you are now a custodian of their history. You should not change it."
Practical consequence: never rebase , , release branches, or any branch others have based work on. Never force-push them. Force-push to your own feature branch before it merges is fine — with and after coordinating with anyone who's pulled it.
mainmaster--force-with-leaseJonathan Corbet在LWN上发表的《Rebasing and merging: some git best practices》(2009年4月)总结了内核社区的规则:
绝不要重写已公开的提交历史。
这句话是Corbet的提炼,并非Linus的原话——Linus自己的表述更接近*“如果你还在进行git rebase操作,就不要把它推出去。”无论如何,这条规则都成立。Linux内核维护者文档也强调了这一点:“已公开到你个人系统之外的提交历史通常不应被修改。……如果你从其他开发者的仓库拉取了变更,你就成了他们提交历史的保管者。你不应该修改它。”*
实际影响:绝不要对、、发布分支或其他有人基于其开展工作的分支进行rebase。绝不要对这些分支进行强制推送。在合并前对自己的特性分支进行强制推送是可以的——但要使用,并与任何拉取过该分支的人协调。
mainmaster--force-with-leasePR-time merge styles
PR合并方式
- Fast-forward merge. Branch is a strict descendant; main pointer just moves. Cleanest history; no merge commit. Requires rebasing the branch on main before merging.
- Merge commit (no-ff). Explicit topology preserved. Useful when the branch boundary is meaningful (release integration, long-running feature work).
- Squash merge. Collapses the branch to a single commit on main. Pro: every main commit is a complete feature. Con: loses the incremental commits below feature granularity. Whether that hurts bisect depends on whether those underlying commits were individually buildable — squash is harmful when they were (you've lost real bisect granularity), helpful when they weren't (you've collapsed un-bisectable noise).
- Rebase-and-merge. Replays each branch commit linearly. Pro: keeps atomic commits with linear history. Con: requires every branch commit to be individually buildable.
Pick a default per repo and stick with it. The principle is consistency.
- 快进合并:分支是主分支的严格子分支;主分支指针直接移动。历史最整洁;无合并提交。需要在合并前将分支rebase到主分支上。
- 保留合并提交(no-ff):明确保留提交拓扑结构。当分支边界有意义时(比如发布集成、长期特性开发)很有用。
- 压缩合并:将分支的所有提交压缩成一个提交合并到主分支。优点:主分支的每个提交都对应一个完整特性。缺点:丢失了特性粒度以下的增量提交。这是否会影响bisect取决于这些底层提交是否能单独构建——如果可以,压缩合并会破坏bisect的精度;如果不能,压缩合并则能消除无法bisect的无效提交。
- Rebase后合并:将分支的每个提交线性重放。优点:保留原子提交和线性历史。缺点:要求分支的每个提交都能单独构建。
为每个仓库选择一个默认合并方式并坚持使用。核心原则是一致性。
Pull requests
拉取请求(PR)
Google's Code Author's Guide () is the canonical source for sizing:
google.github.io/eng-practices/- One self-contained change per CL. Refactors separate from behavior changes; tests with the code they validate; experiment / config changes separate from code.
- Small CLs. Google's explicit numerical guidance: "100 lines is usually a reasonable size for a CL, and 1000 lines is usually too large."
- The reviewer-time argument: it's easier to find five minutes several times for small CLs than to set aside thirty minutes for one large one.
谷歌的《代码作者指南》()是关于PR大小的权威参考:
google.github.io/eng-practices/- 每个CL对应一个独立的变更。重构与行为变更分开;测试代码与被测试代码放在一起;实验/配置变更与业务代码分开。
- 小型CL。谷歌明确的数值指导:“100行通常是合理的CL大小,1000行通常过大。”
- 从评审者时间角度考虑:多次花5分钟评审小型CL,比一次花30分钟评审大型CL更容易。
PR descriptions
PR描述
A useful template:
- What changed (one or two sentences; matches the commit subject).
- Why (the problem, the user impact, the constraint).
- How tested (specific commands run, environments verified, manual cases checked).
- Risk (what could go wrong, blast radius, who else is affected).
- Rollback (how to revert if it breaks; whether the migration is reversible).
The Google guide's bad-example list — , , — is exactly the antipattern set to flag in review.
Fix bugFix buildAdd patch实用模板:
- 变更内容(一两句话;与提交主题一致)。
- 变更原因(解决的问题、对用户的影响、约束条件)。
- 测试方式(执行的具体命令、验证的环境、手动检查的用例)。
- 风险(可能出现的问题、影响范围、涉及的其他人员)。
- 回滚方案(如果出现问题如何回滚;迁移是否可逆)。
谷歌指南中的反面例子——、、——正是评审时需要标记的反模式。
Fix bugFix buildAdd patchStacked PRs
堆叠PR
Tools (Graphite, Sapling, ghstack) let one PR depend on another so a chain of changes can be reviewed independently. Useful when you have a genuinely-dependent chain that each deserves its own review. Overhead when the underlying SCM doesn't support stacks well, or when changes are independent enough to be parallel branches.
工具(Graphite、Sapling、ghstack)允许一个PR依赖另一个PR,这样一系列关联变更可以独立评审。适用于确实存在依赖关系、每个变更都值得单独评审的场景。如果底层版本控制系统对堆叠支持不佳,或者变更足够独立可以并行分支开发,那么堆叠PR会带来额外开销。
Bisect
Bisect
git bisect start; git bisect bad; git bisect good <known-good-SHA>git bisect run <script>Bisect-friendliness has three preconditions:
- Every commit on main builds.
- Every commit on main passes the test suite (or at least the test you're using as the bisect oracle).
- Commits are small enough that the bad commit localizes to a meaningful unit.
The Linux kernel routinely bisects across a multi-million-line codebase. If it scales there, it scales anywhere — provided the discipline holds.
git bisect start; git bisect bad; git bisect good <已知正常的SHA>git bisect run <脚本>便于bisect的三个前提条件:
- 主分支上的每个提交都能成功构建。
- 主分支上的每个提交都能通过测试套件(至少能通过你用作bisect判定依据的测试)。
- 提交足够小,错误提交能定位到有意义的单元。
Linux内核经常在数百万行代码的仓库中使用bisect。如果这种方法在那里可行,那么在任何地方都可行——只要遵循规范。
Recovering from mistakes
错误恢复
Reflog — the local safety net
Reflog——本地安全网
Every HEAD movement is logged. Default retention: 90 days for reachable entries, 30 days for unreachable. shows the journal. Prefer preserving work first with or ; use only after confirms no needed working-tree changes remain.
git refloggit branch rescue-before-reset HEADgit switch -c recovery <sha>git reset --hard HEAD@{n}git statusA botched , a deleted branch, a rebase gone wrong — usually recoverable from reflog. If reflog has expired, finds dangling commits.
git reset --hardgit fsck --lost-found每次HEAD移动都会被记录。默认保留时间:可访问条目保留90天,不可访问条目保留30天。会显示这个日志。优先使用或保存工作;只有在确认工作区没有需要保留的变更后,才使用。
git refloggit branch rescue-before-reset HEADgit switch -c recovery <sha>git statusgit reset --hard HEAD@{n}失败的、删除的分支、出错的rebase——通常都可以通过reflog恢复。如果reflog已过期,可以使用查找悬空提交。
git reset --hardgit fsck --lost-foundForce-push discipline
强制推送规范
git push --forcegit push --force-with-lease--force-with-leasepush.useForceIfIncludes=truegit fetch--force-with-leaseuseForceIfIncludesThe rule: never force-push to , , or release branches. On your own feature branches, is the default.
mainmaster--force-with-leasegit push --forcegit push --force-with-lease--force-with-leasepush.useForceIfIncludes=truegit fetch--force-with-leaseuseForceIfIncludes规则:绝不要对、或发布分支进行强制推送。在你自己的特性分支上,是默认选项。
mainmaster--force-with-leaseCommitted a secret
提交了密钥
The actual fix is rotate the secret immediately. The commit exists in every clone, every fork, every CI cache, every backup. Assume it is compromised the moment it is pushed.
History rewriting (after rotation, to keep the secret out of fresh clones): use (Elijah Newren). The older is officially deprecated; the Git docs say plainly: "its use is not recommended. Please use an alternative history filtering tool such as git filter-repo." BFG Repo-Cleaner is also widely recommended for this specific use case.
git filter-repogit filter-branchHistory rewriting is not a substitute for rotation. It is a hygiene step after rotation.
真正的解决方法是立即轮换密钥。该提交存在于每个克隆、每个分支、每个CI缓存、每个备份中。一旦推送,就假设密钥已泄露。
历史重写(轮换密钥后,防止新克隆获取到密钥):使用****(Elijah Newren开发)。旧的已正式弃用;Git文档明确说明:“不推荐使用它。请使用其他历史过滤工具,比如git filter-repo。” BFG Repo-Cleaner也广泛推荐用于这个场景。
git filter-repogit filter-branch历史重写不能替代密钥轮换。它是轮换后的卫生步骤。
History rewriting
历史重写
- — interactive;
git rebase -i/pick/reword/edit/squash/fixup.drop - plus
git commit --fixup <SHA>slots a fix into the commit it belongs to. The cleanest workflow for "this fixes the commit I made yesterday."git rebase --autosquash - Force-push your own feature branch with before merge — fine, after coordinating with anyone who has pulled it.
--force-with-lease - Never rewrite shared history. Linus's rule.
- ——交互式重写;可选择
git rebase -i/pick/reword/edit/squash/fixup操作。drop - 搭配
git commit --fixup <SHA>可以将修复提交合并到对应的原始提交中。这是“修复我昨天提交的内容”最整洁的工作流。git rebase --autosquash - 合并前使用强制推送自己的特性分支——没问题,但要与任何拉取过该分支的人协调。
--force-with-lease - 绝不要重写共享分支的历史。遵循Linus规则。
What history is for
提交历史的用途
git log path/to/filegit log -p path/to/filegit blameThe canonical record is: PR description + commit messages + linked issue. Slack threads, hallway conversations, design docs in unlinked Google folders — all decay. The git history travels with the code and is the source of truth in the long run.
The implication: clean what you push for review. Do whatever you want on your local branch — squash fixups, reorder, drop dead-end attempts. What lands on main should be readable. Intermediate scratch commits are not the record.
git log path/to/filegit log -p path/to/filegit blame权威记录是:PR描述 + 提交消息 + 关联的issue。Slack线程、走廊对话、未关联的Google文件夹中的设计文档——都会逐渐失效。Git历史随代码一起迁移,长期来看是事实来源。
这意味着:清理你推送给评审的内容。在本地分支上你可以随意操作——压缩修复提交、调整顺序、删除无效尝试。合并到主分支的内容应该是易读的。中间的临时提交不应成为正式记录。
Monorepo vs. multi-repo, briefly
单仓库vs多仓库(简要说明)
The trade-offs are real and context-dependent:
- Monorepo — atomic cross-cutting changes; unified versioning; code discoverability. Costs: build-system complexity (Bazel/Pants/Buck2/Nx/Turborepo); tooling demands at scale (Sapling/Scalar); coupling can mask boundaries.
- Multi-repo — clear ownership boundaries; independent release cadence; off-the-shelf tooling. Costs: cross-cutting changes require coordinated PRs; dependency upgrades become a manual chase across repos.
Public references at scale: Google's monorepo (~2 billion lines, Piper VCS); Meta's Sapling (open-sourced 2022); Microsoft's VFS for Git (now superseded by Scalar, upstreamed into Git). Pick by team scale, build-system maturity, and ownership clarity. There is no universally right answer.
两者的权衡是真实存在的,且取决于上下文:
- 单仓库——支持跨模块的原子变更;统一版本控制;代码可发现性高。成本:构建系统复杂度高(Bazel/Pants/Buck2/Nx/Turborepo);大规模场景下对工具要求高(Sapling/Scalar);耦合可能模糊模块边界。
- 多仓库——所有权边界清晰;发布节奏独立;可使用现成工具。成本:跨模块变更需要协调多个PR;依赖升级需要手动在多个仓库中进行。
大规模场景的公开参考:谷歌的单仓库(约20亿行代码,Piper VCS);Meta的Sapling(2022年开源);微软的VFS for Git(现已被Scalar取代,已并入Git上游)。根据团队规模、构建系统成熟度和所有权清晰度选择。没有普遍正确的答案。
Merge queues
合并队列
At trunk-based-development scale, the bottleneck shifts from "is this PR ready" to "is still green after every merge." Naïve merge of N PRs against a moving produces semantic conflicts (each individually green, none green together) that bisect later as mysterious failures.
mainmainMerge queues serialize and rebase merges through a common gate: GitHub's native Merge Queue (GA 2023), Bors, Mergify, Aviator. The queue rebases each PR onto the latest , runs CI, and merges only if CI passes against the actual post-merge state. Trades latency (your PR waits in line) for the guarantee that is always built-and-tested as it actually is, not as it was at PR time. For teams shipping dozens of PRs per day, this is the missing piece between trunk-based development and "main is always green."
mainmain在主干开发的大规模场景中,瓶颈从“这个PR是否准备好”转移到“每次合并后是否仍然正常”。直接合并N个基于当前的PR会产生语义冲突(每个PR单独正常,但合并后全部异常),后续bisect会出现神秘的失败。
mainmain合并队列通过统一的网关序列化并rebase合并操作:GitHub原生合并队列(2023年正式发布)、Bors、Mergify、Aviator。队列会将每个PR重新base到最新的上,运行CI,只有当CI在合并后的实际状态下通过时才会合并。以PR等待时间为代价,保证始终处于可构建、可测试的状态,而非PR创建时的状态。对于每天提交数十个PR的团队来说,这是主干开发和“主分支始终正常”之间缺失的环节。
mainmainSigned commits
签名提交
Commit signatures bind authorship to a verifiable identity. Two formats:
- GPG-signed — the older standard. (default).
gpg.format=openpgp - SSH-signed — supported since Git 2.34 (November 2021), . Reuses your existing SSH identity. Increasingly the default for new setups.
gpg.format=ssh - Sigstore-signed via — short-lived OIDC-bound certs, no long-lived key material. See
gitsignfor sigstore depth.build-and-dependencies
Signed commits are weaker than artifact signing (anyone with push access can author a malicious signed commit) but raise the bar against impersonation and are the foundation for branch-protection rules that require verified commits. For protected branches and release tags, require signatures.
提交签名将作者身份与可验证的身份绑定。有两种格式:
- GPG签名——较旧的标准。(默认)。
gpg.format=openpgp - SSH签名——Git 2.34(2021年11月)开始支持,。复用现有的SSH身份。越来越多的新环境将其作为默认选项。
gpg.format=ssh - 通过进行Sigstore签名——基于OIDC的短期证书,无需长期密钥材料。关于Sigstore的详细内容请参考
gitsign。build-and-dependencies
提交签名的安全性弱于 artifact签名(任何有推送权限的人都可以提交恶意的签名提交),但能提高 impersonation的门槛,是要求验证提交的分支保护规则的基础。对于受保护的分支和发布标签,要求提交签名。
Pre-commit hooks
预提交钩子
Pre-commit hooks run client-side before a commit lands locally. They are appropriate for fast checks that catch obvious mistakes early — lint, format, simple secret detection, basic tests on changed files. The discipline:
- Sub-second. Pre-commit hooks that take five seconds train developers to use . Move slow checks to pre-push or CI.
--no-verify - Idempotent and deterministic. A hook that randomly fails poisons the workflow.
- Match what CI runs. Pre-commit catching things CI doesn't is fine; CI catching things pre-commit also catches is duplication. CI catching things pre-commit should have caught is the right division of labor.
- Frameworks: pre-commit (), husky (JS), lefthook (cross-language), simple-git-hooks. Pick by team.
pre-commit.com
--no-verify预提交钩子在提交本地生效前运行。适用于快速检查,尽早发现明显错误——比如代码 lint、格式化、简单的密钥检测、对变更文件的基础测试。规范如下:
- 耗时不超过一秒。耗时五秒的预提交钩子会让开发者养成使用的习惯。将慢检查移到预推送钩子或CI中。
--no-verify - 幂等且确定。随机失败的钩子会破坏工作流。
- 与CI检查一致。预提交钩子能检测到CI未检测的内容是可以的;CI检测到预提交钩子也能检测的内容是重复的。CI检测到预提交钩子应该检测的内容才是合理的分工。
- 框架:pre-commit()、husky(JS)、lefthook(跨语言)、simple-git-hooks。根据团队选择。
pre-commit.com
--no-verifyCommon antipatterns
常见反模式
- Commit messages: ,
wip,fix,asdf,more changes,address PR comments,final.final-2 - 1000+ line PRs (Google's "usually too large" threshold).
- Long-lived feature branches (weeks). They accumulate merge debt and die.
- Force-push to / release branches.
main - on commits to skip pre-commit hooks "just this once" — habits form fast.
--no-verify - Committed secrets without rotation.
- Mixing refactor + behavior change in one commit (un-bisectable, un-revertable independently).
- Gratuitous merge bubbles — merge commits when the branch was a strict descendant. Use fast-forward.
- Branch names like ,
dev-jeff-final-final-2,temp. Use the repo's convention (typicallytest123) consistently.<author>/<issue>-<slug> - Empty commit body when the why isn't obvious.
- Squash-merging everything reflexively — collapses bisect granularity below feature size.
- Five commits to revert the previous five because there was no review discipline.
- Bypassing branch protection via admin override to push to main.
- Using (which silently merges) where
git pullwas intended; producing surprise merge commits in personal branches.git pull --rebase
- 提交消息:、
wip、fix、asdf、more changes、address PR comments、final。final-2 - 超过1000行的PR(谷歌的“通常过大”阈值)。
- 长期特性分支(数周)。会积累合并债务,最终被废弃。
- 对/发布分支进行强制推送。
main - 为了跳过预提交钩子而使用“就这一次”——习惯会快速形成。
--no-verify - 提交了密钥但未轮换。
- 在一个提交中混合重构和行为变更(无法bisect,无法独立回滚)。
- 不必要的合并提交——当分支是主分支的严格子分支时仍然创建合并提交。应使用快进合并。
- 分支名类似、
dev-jeff-final-final-2、temp。应一致使用仓库约定的命名格式(通常是test123)。<作者>/<issue编号>-<简短描述> - 当变更原因不明显时,提交消息正文为空。
- 习惯性地压缩合并所有内容——破坏了特性粒度以下的bisect精度。
- 提交五次来回滚之前的五次提交,因为没有评审规范。
- 通过管理员权限绕过分支保护直接推送到主分支。
- 本应使用却使用了
git pull --rebase(会自动合并);在个人分支中产生意外的合并提交。git pull
What to flag in review
评审时需要标记的问题
- A PR with or
wipcommits as final history rather than as scaffolding.fix - A commit that mixes refactor and behavior change.
- A commit message body that restates the diff instead of explaining the constraint.
- A long-lived feature branch (over a few days) without a tracking issue and merge plan.
- A force-push to a shared branch.
- A commit that introduces a hard-to-bisect failure (the change is so large bisect can't localize).
- A merge commit on a personal branch where fast-forward was the intent.
- A repo policy that allows direct push to main without review.
- A history-rewriting fix to a leaked secret with no rotation.
- A PR description without why or rollback.
- 1000+ lines split across 30 commits as if that compensates.
- PR将或
wip提交作为最终历史,而非临时脚手架。fix - 一个提交中混合了重构和行为变更。
- 提交消息正文重复diff内容,而非解释约束条件。
- 长期特性分支(超过几天)没有跟踪issue和合并计划。
- 对共享分支进行强制推送。
- 提交引入了难以bisect的错误(变更过大,bisect无法定位)。
- 个人分支上出现本应使用快进合并却创建了合并提交的情况。
- 仓库允许无需评审直接推送到主分支的规则。
- 对泄露密钥的提交进行历史重写,但未轮换密钥。
- PR描述缺少原因或回滚方案。
- 超过1000行的变更拆分为30个提交,试图以此弥补过大的问题。
Reference library
参考库
- — Pope and Beams in depth, Conventional Commits trade-offs, examples of subject-and-body that earn their keep.
references/commit-message-craft.md - — trunk-based vs. GitHub Flow vs. GitFlow with the criteria for choosing, including Driessen's 2020 note in full context.
references/branching-models-in-practice.md - — reflog, force-with-lease, filter-repo, BFG, and the discipline of rotating secrets that escaped into history.
references/recovery-and-rewriting.md
- ——深入介绍Pope和Beams的理论、约定式提交的权衡、优秀的主题+正文示例。
references/commit-message-craft.md - ——主干开发vs GitHub Flow vs GitFlow的选择标准,包括Driessen 2020年说明的完整上下文。
references/branching-models-in-practice.md - ——reflog、force-with-lease、filter-repo、BFG,以及对泄露到历史中的密钥进行轮换的规范。
references/recovery-and-rewriting.md
Sibling skills
关联技能
- — review and triage; how to handle PR feedback as a craft (overlaps with the orchestrator).
engineering-discipline - — trunk-based development as the upstream of small-batch deployment; CL size correlated with deployment frequency in DORA's research.
deployment-and-release-engineering - — commit messages and PR descriptions as documentation.
documentation-and-technical-writing - — Beck's Tidy First? discipline of separating tidying from behavior change, which is the same discipline as one-logical-change-per-commit.
refactoring - — bisect requires every commit on main to pass tests; that is a property of the test suite, not just the commits.
testing-discipline
- ——评审和分类;如何将处理PR反馈视为一门技艺(与编排器重叠)。
engineering-discipline - ——主干开发作为小批量部署的上游;CL大小与DORA研究中的部署频率相关。
deployment-and-release-engineering - ——提交消息和PR描述作为文档。
documentation-and-technical-writing - ——Beck的《先整理?》中关于分离整理和行为变更的规范,与每个提交对应一个逻辑变更的规范一致。
refactoring - ——bisect要求主分支上的每个提交都能通过测试;这是测试套件的属性,而非仅仅是提交的属性。
testing-discipline