version-control-discipline
Use version control as a craft — atomic commits, buildable history, useful PRs, bisect-friendly main, recoverable mistakes. Use this skill whenever the task involves writing commits or PRs, choosing a branching model, deciding rebase vs. merge, recovering from a force-push or accidentally-committed secret, debugging a regression with `git bisect`, structuring a long change as a series of small reviewable steps, or judging whether a repo's history is readable. Use it especially when reviewing commit messages, PR descriptions, branching strategies, or merge policies. Built on Tim Pope and Chris Beams on commit messages, Paul Hammant on trunk-based development, Vincent Driessen on GitFlow (and his 2020 note retiring it for SaaS), Linus Torvalds on never rebasing public commits, and the Google Engineering Practices CL guide.
NPX Install
npx skill4agent add devitbetter/software-engineering-discipline version-control-disciplineTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Version Control Discipline
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
git bisectwip / fix / address PR commentsCommit messages
- 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.
Conventional Commits — when it helps, when it's theater
feat:fix:refactor:Branching models
- 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
Rebase vs. merge
Linus's rule
Thou Shalt Not Rebase Trees With History Visible To Others.
mainmaster--force-with-leasePR-time merge styles
- 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.
Pull requests
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 descriptions
- 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).
Fix bugFix buildAdd patchStacked PRs
Bisect
git bisect start; git bisect bad; git bisect good <known-good-SHA>git bisect run <script>- 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.
Recovering from mistakes
Reflog — the local safety net
git refloggit branch rescue-before-reset HEADgit switch -c recovery <sha>git reset --hard HEAD@{n}git statusgit reset --hardgit fsck --lost-foundForce-push discipline
git push --forcegit push --force-with-lease--force-with-leasepush.useForceIfIncludes=truegit fetch--force-with-leaseuseForceIfIncludesmainmaster--force-with-leaseCommitted a secret
git filter-repogit filter-branchHistory 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.
What history is for
git log path/to/filegit log -p path/to/filegit blameMonorepo vs. multi-repo, briefly
- 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.
Merge queues
mainmainmainmainSigned commits
- 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
Pre-commit hooks
- 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-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
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.
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
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