Git Workflow
Intro
Use typed branch names, Conventional Commits, and PR descriptions
that explain the why. Squash-merge feature branches for a clean
history. Keep commits atomic and never force-push to shared
branches.
Overview
Branch naming
Format: <type>/<issue>-<short-description>
Types: feat/, fix/, refactor/, docs/, chore/
Examples:
feat/42-add-user-auth
fix/17-null-pointer-crash
refactor/89-extract-payment-service
Version-line release branches
For concurrently maintained major lines, use explicit development and
release-integration branches. main is the published-history branch, not
the active development or tagging authority.
| Line |
Development |
Integration and tag authority |
Publication |
| v0.x |
v0.x-dev |
v0.x-release |
merge tagged release into main |
| v1.x prerelease |
v1.x-dev |
v1.x-pre-release |
prerelease tag stays on integration branch |
| v1.x GA |
v1.x-pre-release |
v1.x-release |
merge tagged release into main |
- Create a version-line release branch from the currently published tag,
then create its development branch from that release branch.
- Merge development into the relevant integration branch for each release;
validate, prepare, and tag only on that integration branch.
- Immediately merge the tagged integration branch into
main, so every
stable release tag is reachable from main.
- Do not commit directly to
main or an integration branch. Protect them
and use merge commits for long-lived branch integration.
- For a maintenance hotfix, branch from the latest stable tag, merge into
the release branch, tag, merge the release branch into
main, then
merge the result back into the development branch.
Commit messages
Follow Conventional Commits:
- Format:
<type>: <description> — lowercase, imperative
mood, no trailing period
- Types:
feat, fix, refactor, docs, test, chore,
ci
- Body: explain WHY, not WHAT (the diff shows what)
- Footer:
fixes #N or refs #N for issue links
PR descriptions
- Title: same format as commit messages, under 70 characters
- Body: Summary (what and why), test plan, breaking changes
- Link related issues
Merge strategy
- Squash merge for feature branches — keeps mainline history
clean
- Merge commit for long-lived branches — preserves context
across the merge
- Rebase to keep a feature branch up to date before opening
the PR
Gotchas
Agent-specific failure modes — provider-neutral pause-and-self-check items:
- Mega-commits ("various fixes", 40 files changed). A commit that mixes unrelated changes is impossible to review, impossible to revert selectively, and tells a lie in its message. Each commit should represent one logical change that can stand alone.
- Force-pushing to shared branches. Force-pushing to
main or any branch others have checked out rewrites history they depend on, causing diverged local states that are painful to recover. Never force-push to a shared branch.
- Branches that live for months. Long-lived branches diverge from main, accumulate merge conflicts, and eventually require heroic merge efforts. Rebase weekly or split long-running work into shorter-lived feature branches.
- Tagging a development branch. A development branch has not passed the
integration and release gate. Stable and prerelease tags must originate
on their designated integration branch.
- PRs with no description. "See commits" is not a description. The PR description is for the reviewer — it explains what changed, why it changed, and how to verify it. Write it before requesting review.
- Conventional Commit type misuse. Using
fix: for features or feat: for refactors corrupts any automation that generates changelogs or determines version bumps from commit types. Match the type to the nature of the change.
- Squash-merging a long-lived integration branch. Squash-merge is for feature branches with noisy "wip" commits. A long-lived branch with meaningful commit history should be merge-committed so the history is preserved for future investigation.
- Committing unrelated changes in the same PR to "save time". Reviewers are slower on unrelated changes combined in one PR; rollback becomes more expensive if one change needs reverting. Split unrelated changes into separate PRs.
Full reference
General rules
- Commit early and often on feature branches; squash at merge time.
- Never force-push to shared branches (
main, develop, release
branches).
- Keep commits atomic — one logical change per commit. A commit
that touches three unrelated concerns is three commits.
- A commit message body wraps at 72 columns; the subject line at
50 if you can.
Worked example
User: "What should I name this branch for adding search?"
Suggest feat/30-add-search-functionality if there's an issue
#30; otherwise feat/add-search-functionality. Match the
existing repo's convention if it differs.
Anti-patterns
- Mega-commits — "various fixes" with 40 files changed.
Impossible to review or revert.
- Force-push to main — destroys other contributors' work.
- Commit messages like "wip", "fix", "stuff" — every commit
should be reviewable on its own.
- PRs with no description — the PR description is for the
reviewer, not the author. "See commits" is not a description.
- Branches that live for months — long-lived branches
diverge from main and become merge nightmares. Rebase weekly
or split the work.
When to break the rules
Solo-on-a-private-branch force-pushes are fine. Trivial typo
fixes don't need a long body. Use judgment, but the defaults
above are right for almost every team setting.
1---2name: git-workflow-33description: Git workflow conventions — branch naming, conventional commits, PR descriptions, and merge strategy. Use when deciding on a branch name, writing a commit message or PR description, or choosing a merge strategy.4---56# Git Workflow78## Intro910Use typed branch names, Conventional Commits, and PR descriptions11that explain the why. Squash-merge feature branches for a clean12history. Keep commits atomic and never force-push to shared13branches.1415## Overview1617### Branch naming1819Format: `<type>/<issue>-<short-description>`2021Types: `feat/`, `fix/`, `refactor/`, `docs/`, `chore/`2223Examples:2425- `feat/42-add-user-auth`26- `fix/17-null-pointer-crash`27- `refactor/89-extract-payment-service`2829### Version-line release branches3031For concurrently maintained major lines, use explicit development and32release-integration branches. `main` is the published-history branch, not33the active development or tagging authority.3435| Line | Development | Integration and tag authority | Publication |36| --- | --- | --- | --- |37| v0.x | `v0.x-dev` | `v0.x-release` | merge tagged release into `main` |38| v1.x prerelease | `v1.x-dev` | `v1.x-pre-release` | prerelease tag stays on integration branch |39| v1.x GA | `v1.x-pre-release` | `v1.x-release` | merge tagged release into `main` |4041- Create a version-line release branch from the currently published tag,42 then create its development branch from that release branch.43- Merge development into the relevant integration branch for each release;44 validate, prepare, and tag only on that integration branch.45- Immediately merge the tagged integration branch into `main`, so every46 stable release tag is reachable from `main`.47- Do not commit directly to `main` or an integration branch. Protect them48 and use merge commits for long-lived branch integration.49- For a maintenance hotfix, branch from the latest stable tag, merge into50 the release branch, tag, merge the release branch into `main`, then51 merge the result back into the development branch.5253### Commit messages5455Follow Conventional Commits:5657- **Format:** `<type>: <description>` — lowercase, imperative58 mood, no trailing period59- **Types:** `feat`, `fix`, `refactor`, `docs`, `test`, `chore`,60 `ci`61- **Body:** explain WHY, not WHAT (the diff shows what)62- **Footer:** `fixes #N` or `refs #N` for issue links6364### PR descriptions6566- **Title:** same format as commit messages, under 70 characters67- **Body:** Summary (what and why), test plan, breaking changes68- Link related issues6970### Merge strategy7172- **Squash merge** for feature branches — keeps mainline history73 clean74- **Merge commit** for long-lived branches — preserves context75 across the merge76- **Rebase** to keep a feature branch up to date before opening77 the PR7879## Gotchas8081Agent-specific failure modes — provider-neutral pause-and-self-check items:8283- **Mega-commits ("various fixes", 40 files changed).** A commit that mixes unrelated changes is impossible to review, impossible to revert selectively, and tells a lie in its message. Each commit should represent one logical change that can stand alone.84- **Force-pushing to shared branches.** Force-pushing to `main` or any branch others have checked out rewrites history they depend on, causing diverged local states that are painful to recover. Never force-push to a shared branch.85- **Branches that live for months.** Long-lived branches diverge from main, accumulate merge conflicts, and eventually require heroic merge efforts. Rebase weekly or split long-running work into shorter-lived feature branches.86- **Tagging a development branch.** A development branch has not passed the87 integration and release gate. Stable and prerelease tags must originate88 on their designated integration branch.89- **PRs with no description.** "See commits" is not a description. The PR description is for the reviewer — it explains what changed, why it changed, and how to verify it. Write it before requesting review.90- **Conventional Commit type misuse.** Using `fix:` for features or `feat:` for refactors corrupts any automation that generates changelogs or determines version bumps from commit types. Match the type to the nature of the change.91- **Squash-merging a long-lived integration branch.** Squash-merge is for feature branches with noisy "wip" commits. A long-lived branch with meaningful commit history should be merge-committed so the history is preserved for future investigation.92- **Committing unrelated changes in the same PR to "save time".** Reviewers are slower on unrelated changes combined in one PR; rollback becomes more expensive if one change needs reverting. Split unrelated changes into separate PRs.9394## Full reference9596### General rules9798- Commit early and often on feature branches; squash at merge time.99- Never force-push to shared branches (`main`, `develop`, release100 branches).101- Keep commits atomic — one logical change per commit. A commit102 that touches three unrelated concerns is three commits.103- A commit message body wraps at 72 columns; the subject line at104 50 if you can.105106### Worked example107108User: "What should I name this branch for adding search?"109110Suggest `feat/30-add-search-functionality` if there's an issue111#30; otherwise `feat/add-search-functionality`. Match the112existing repo's convention if it differs.113114### Anti-patterns115116- **Mega-commits** — "various fixes" with 40 files changed.117 Impossible to review or revert.118- **Force-push to main** — destroys other contributors' work.119- **Commit messages like "wip", "fix", "stuff"** — every commit120 should be reviewable on its own.121- **PRs with no description** — the PR description is for the122 reviewer, not the author. "See commits" is not a description.123- **Branches that live for months** — long-lived branches124 diverge from main and become merge nightmares. Rebase weekly125 or split the work.126127### When to break the rules128129Solo-on-a-private-branch force-pushes are fine. Trivial typo130fixes don't need a long body. Use judgment, but the defaults131above are right for almost every team setting.