Branching workflows, commits, and CI/CD
Mid / SeniorThe SDLC phases, trunk-based development vs GitHub Flow vs GitFlow, pull requests and code review, commit conventions and semantic versioning, hooks, and the CI/CD pipeline from push to production.
Created 15 October 2026
8 concepts · detail hidden
01Glossary — terms used in this page
#c-glossary-terms-used-in-this-page- SDLC (Software Development Lifecycle) — the end-to-end process of planning, building, testing, releasing, and operating software.
- CI (Continuous Integration) — every change is merged to the main line frequently and verified automatically (build + tests).
- CD (Continuous Delivery / Deployment) — every change that passes CI is releasable (delivery) or is automatically released to production (deployment).
- PR / MR (Pull Request / Merge Request) — a request to merge a branch, with review, discussion, and CI checks.
- Trunk — the single main integration branch (
main). - Feature flag — a runtime switch that enables or disables code paths without deploying.
- SemVer (Semantic Versioning) —
MAJOR.MINOR.PATCHversion numbering with defined compatibility meaning. - Conventional Commits — a structured commit-message format (
feat:,fix:, …) that tools can parse. - DORA metrics — four delivery-performance metrics from the DevOps Research and Assessment program: deployment frequency, lead time for changes, change failure rate, time to restore service.
- CODEOWNERS — a file mapping paths to required reviewers on GitHub/GitLab.
02The SDLC phases and where Git fits
#c-the-sdlc-phases-and-where-git-fitsPlan ─▶ Design ─▶ Implement ─▶ Review ─▶ Test/Verify ─▶ Release ─▶ Deploy ─▶ Operate/Monitor │ │ │ │ │ │ │ issues / branches, PRs, CI pipeline tags, CD pipeline, metrics, alerts, tickets commits review (unit, integ., changelog environments incidents ─┐ security) │ ▲───────────────────────────── feedback: bugs, postmortems, usage data ─────────────────────────┘Methodologies (Waterfall, Scrum, Kanban) differ mostly in how big each loop is: Waterfall runs the phases once per large release; Agile/Scrum runs them per sprint; continuous delivery runs them per commit. Modern teams aim for small batches: a change should go from commit to production in hours, not months — small changes are easier to review, test, roll back, and bisect.
Git is the backbone of the Implement → Release stretch: branches isolate work, PRs gate review, the commit hash is the identity that CI tests and CD deploys, and tags mark releases.
03Branching strategies — trunk-based, GitHub Flow, GitFlow
#c-branching-strategies-trunk-based-github-flow-gitflowTrunk-based development
Everyone integrates into
mainat least daily; branches (if any) live hours to a couple of days.main: ──●──●──●──●──●──●──●──●──▶ (always releasable; deployed continuously) \─●─/ \─●●─/ short-lived branches / direct small commits- Unfinished features are merged behind feature flags, not kept on long branches.
- Requires strong CI and good test coverage —
mainmust stay green. - Minimises merge conflicts and “integration hell”; the approach most correlated with high DORA performance.
GitHub Flow
A simplified variant:
mainis always deployable; every change is a feature branch → PR → review + CI → merge → deploy. Branches typically live a few days. Default for most web services and SaaS.GitFlow
Long-lived
main(production releases) anddevelop(integration), plusfeature/*,release/*, andhotfix/*branches:main: ●────────────────●───────────● (tags v1.0, v1.1, v1.1.1) \ / \ / hotfix: \ / ●──────● ─────┐ \ / │ merged into main AND develop release: \ ●──● │ \ / \ ▼ develop: ──────●──●──●──●──●──●──●──●──●──●── \ / \ / feature: ●─● ●──●──●- Fits software with explicit, versioned releases shipped to customers and multiple supported versions (desktop apps, libraries, on-prem products).
- Heavy for continuously deployed services: long-lived branches drift, merges are big, hotfixes must be merged twice.
Release branches for supported versions
Even with trunk-based development you may cut
release/2.3to stabilise and patch a version. Fixes go tomainfirst and are cherry-picked (-x) into release branches — or, in “merge forward” setups, fixed on the oldest release and merged upward.Strategy Branch lifetime Release style Good for Trunk-based hours–1 day continuous services with mature CI, feature flags GitHub Flow days deploy on merge most web teams GitFlow weeks+ scheduled, versioned shipped/installable software, many supported versions 04Pull requests and code review
#c-pull-requests-and-code-reviewA PR bundles a branch’s diff (
main...feature, i.e. changes since the merge base) with discussion, review, and automated checks.Typical lifecycle
- Branch from up-to-date
main; commit small, logical steps. - Push and open a PR (draft early for feedback).
- CI runs: build, lint, type check, tests, security scanning, preview deploy.
- Reviewers (auto-assigned via
CODEOWNERS) comment, request changes, approve. - Update the branch: new commits, or rebase +
--force-with-leaseto keep it clean. - Merge when checks are green and approvals met — the branch protection rules enforce this.
- Delete the branch; CD picks up the new
maincommit.
Merge button strategies
Strategy Result on mainTrade-off Merge commit All branch commits + a merge commit Full history; noisy graph Squash and merge One commit per PR Clean, one revert undoes a PR; loses intermediate commits (hashes differ from the branch) Rebase and merge Branch commits replayed linearly Linear and granular; every commit should be meaningful and green Good review practice
- Keep PRs small (a few hundred lines) — review quality drops sharply with size.
- Separate refactors from behaviour changes.
- The description states why, how it was tested, and risk/rollback.
- Review for correctness, design, tests, security, and readability — leave formatting to automated linters.
Branch protection (server-side)
Require PRs for
main, required status checks, N approvals, CODEOWNERS approval, up-to-date branch before merge (or a merge queue that tests each PR on top of the latestmainbefore merging), signed commits, and no force pushes or deletions.- Branch from up-to-date
05Commit hygiene, Conventional Commits, and SemVer
#c-commit-hygiene-conventional-commits-and-semverA good commit
- Atomic — one logical change, builds and passes tests on its own (makes
revert,cherry-pick, andbisectprecise). - Message — imperative subject ≤ ~50–72 chars, blank line, body explaining why (the diff already shows what).
fix(payments): reject negative refund amounts Refunds with amount < 0 were accepted by the API and produced a credit instead of a debit. Validate in the request schema and add a DB check constraint as a second line of defence. Closes #482Conventional Commits
Format:
type(scope)!: subject, withBREAKING CHANGE:footers.Type Meaning SemVer bump featnew feature MINOR fixbug fix PATCH feat!/BREAKING CHANGE:incompatible API change MAJOR docs,refactor,test,chore,ci,perf,buildno user-facing feature/fix none (by default) Tools such as semantic-release, release-please, or commitizen parse the history since the last tag to compute the next version and generate the changelog automatically.
Semantic Versioning
MAJOR.MINOR.PATCH, e.g.2.4.1:- MAJOR — breaking changes to the public API.
- MINOR — backwards-compatible features.
- PATCH — backwards-compatible bug fixes.
- Pre-releases:
3.0.0-rc.1;0.y.zmeans “anything may change”.
Releases are marked with annotated, ideally signed tags:
git tag -a v2.4.1 -m "Release 2.4.1" git push origin v2.4.1 git describe --tags # v2.4.1-3-g9f8e7d6 -> 3 commits after v2.4.1, at 9f8e7d6- Atomic — one logical change, builds and passes tests on its own (makes
06Git hooks and pre-commit checks
#c-git-hooks-and-pre-commit-checksHooks are scripts in
.git/hooks/that Git runs at specific points. A non-zero exit aborts the operation.Hook Runs Typical use pre-commitbefore the commit is created format, lint, detect secrets, block large files commit-msgafter the message is written enforce Conventional Commits, require a ticket ID pre-pushbefore pushing run fast tests post-checkout/post-mergeafter switching / merging reinstall dependencies, regenerate files pre-receive/update(server)on the server, before refs are updated enforce policy — cannot be bypassed by the client Client-side hooks are not versioned or shared by default (
.git/is not committed) and can be skipped with--no-verify, so they are a convenience, not enforcement. Share them via a framework, and enforce the same checks in CI:# .pre-commit-config.yaml (the "pre-commit" framework; install with: pre-commit install) repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.9 hooks: - id: ruff args: [--fix] - id: ruff-format - repo: https://github.com/gitleaks/gitleaks rev: v8.21.0 hooks: - id: gitleaksAlternatives: Husky + lint-staged (JS), lefthook, or
git config core.hooksPath .githookspointing at a committed directory.07CI/CD pipelines — from push to production
#c-cicd-pipelines-from-push-to-productionA pipeline is a sequence of automated stages triggered by Git events (push, PR, tag), each running against an exact commit hash — the same hash flows all the way to production, giving full traceability.
push / PR ─▶ ┌─────────────── CI ───────────────┐ ─▶ ┌──────────────── CD ─────────────────┐ │ checkout ─ build ─ lint/typecheck │ │ publish artifact (image:sha) │ │ unit tests ─ integration tests │ │ deploy staging ─ smoke/e2e tests │ │ security: SAST, deps, secrets │ │ approval gate (optional) │ └───────────────────────────────────┘ │ deploy prod: canary / blue-green │ │ monitor ─ auto-rollback on SLO burn │ └──────────────────────────────────────┘Example: GitHub Actions
name: ci on: pull_request: push: branches: [main] tags: ["v*"] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # shallow clone at the triggering commit (detached HEAD) - uses: actions/setup-python@v5 with: { python-version: "3.12", cache: pip } - run: pip install -r requirements.txt - run: ruff check . && pytest -q build-and-deploy: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: docker build -t registry.example.com/app:${{ github.sha }} . - run: docker push registry.example.com/app:${{ github.sha }} - run: ./deploy.sh staging ${{ github.sha }}Principles
- Build once, promote the same artifact. Tag images with the commit SHA (immutable), not
latest; staging and production run byte-identical artifacts, differing only in configuration. - Fast feedback first. Lint and unit tests in minutes; slow suites in parallel or later stages.
- Keep
maingreen. A broken main blocks everyone; fix or revert immediately. - Deploy ≠ release. Feature flags decouple turning a feature on from shipping the code.
- Progressive delivery and rollback. Canary or blue-green deployments with automated rollback when error rates or latency degrade — rolling back means redeploying the previous SHA, not reverting under pressure.
- Infrastructure and config as code in Git too (Terraform, Helm, Kubernetes manifests). GitOps goes further: the desired state of each environment lives in a repo, and an agent (Argo CD, Flux) continuously reconciles the cluster to it — a deploy is a commit, a rollback is a revert.
Measuring it — DORA metrics
Metric Elite teams (rough benchmark) Deployment frequency on demand, multiple times per day Lead time for changes (commit → production) less than a day Change failure rate roughly 0–15% Time to restore service less than an hour Small batches, trunk-based development, and automated testing and deployment move all four in the right direction at once.
- Build once, promote the same artifact. Tag images with the commit SHA (immutable), not
08Security and housekeeping in Git repositories
#c-security-and-housekeeping-in-git-repositoriesSecrets in history
Committing a secret and deleting it in the next commit does not remove it — it is still in the old commit’s blob, in every clone and fork. Response:
- Rotate the credential immediately — assume it is compromised.
- Optionally purge it from history with
git filter-repo --replace-text(or BFG), force-push, and ask everyone to re-clone; hosting caches and forks may still hold it. - Prevent recurrence: secret scanning (gitleaks, GitHub push protection),
.envin.gitignore.
.gitignoreand friends# .gitignore __pycache__/ *.pyc .env .venv/ dist/ !dist/.gitkeep # negation re-includes a path.gitignoreonly affects untracked files. An already-tracked file stays tracked:git rm --cached .env, then commit..git/info/exclude— personal, unshared ignores;core.excludesFile— global ones (editor files)..gitattributes— line endings (* text=auto), diff/merge drivers, LFS tracking (*.onnx filter=lfs diff=lfs merge=lfs -text).
Signed commits
git config commit.gpgsign truewith a GPG or SSH key (gpg.format ssh) proves who authored a commit — the author field alone is just free text anyone can set. Hosting platforms show a “Verified” badge and can require signatures on protected branches.Worktrees
git worktree add ../app-hotfix release/1.4checks out a second branch in another directory, sharing the same object database — no stash dance, no second clone. Explained in detail under “Worktrees” on the Moving HEAD page.