Skip to content

Branching workflows, commits, and CI/CD

Mid / Senior

The 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

  1. 01Glossary — 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.PATCH version 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.
    #c-glossary-terms-used-in-this-page
  2. 02The SDLC phases and where Git fits
     Plan ─▶ 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.

    #c-the-sdlc-phases-and-where-git-fits
  3. 03Branching strategies — trunk-based, GitHub Flow, GitFlow

    Trunk-based development

    Everyone integrates into main at 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 — main must stay green.
    • Minimises merge conflicts and “integration hell”; the approach most correlated with high DORA performance.

    GitHub Flow

    A simplified variant: main is 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) and develop (integration), plus feature/*, release/*, and hotfix/* 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.3 to stabilise and patch a version. Fixes go to main first 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
    #c-branching-strategies-trunk-based-github-flow-gitflow
  4. 04Pull requests and code review

    A PR bundles a branch’s diff (main...feature, i.e. changes since the merge base) with discussion, review, and automated checks.

    Typical lifecycle

    1. Branch from up-to-date main; commit small, logical steps.
    2. Push and open a PR (draft early for feedback).
    3. CI runs: build, lint, type check, tests, security scanning, preview deploy.
    4. Reviewers (auto-assigned via CODEOWNERS) comment, request changes, approve.
    5. Update the branch: new commits, or rebase + --force-with-lease to keep it clean.
    6. Merge when checks are green and approvals met — the branch protection rules enforce this.
    7. Delete the branch; CD picks up the new main commit.

    Merge button strategies

    Strategy Result on main Trade-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 latest main before merging), signed commits, and no force pushes or deletions.

    #c-pull-requests-and-code-review
  5. 05Commit hygiene, Conventional Commits, and SemVer

    A good commit

    • Atomic — one logical change, builds and passes tests on its own (makes revert, cherry-pick, and bisect precise).
    • 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 #482
    

    Conventional Commits

    Format: type(scope)!: subject, with BREAKING CHANGE: footers.

    Type Meaning SemVer bump
    feat new feature MINOR
    fix bug fix PATCH
    feat! / BREAKING CHANGE: incompatible API change MAJOR
    docs, refactor, test, chore, ci, perf, build no 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.z means “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
    
    #c-commit-hygiene-conventional-commits-and-semver
  6. 06Git hooks and pre-commit checks

    Hooks are scripts in .git/hooks/ that Git runs at specific points. A non-zero exit aborts the operation.

    Hook Runs Typical use
    pre-commit before the commit is created format, lint, detect secrets, block large files
    commit-msg after the message is written enforce Conventional Commits, require a ticket ID
    pre-push before pushing run fast tests
    post-checkout / post-merge after 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: gitleaks
    

    Alternatives: Husky + lint-staged (JS), lefthook, or git config core.hooksPath .githooks pointing at a committed directory.

    #c-git-hooks-and-pre-commit-checks
  7. 07CI/CD pipelines — from push to production

    A 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 main green. 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.

    #c-cicd-pipelines-from-push-to-production
  8. 08Security and housekeeping in Git repositories

    Secrets 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:

    1. Rotate the credential immediately — assume it is compromised.
    2. 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.
    3. Prevent recurrence: secret scanning (gitleaks, GitHub push protection), .env in .gitignore.

    .gitignore and friends

    # .gitignore
    __pycache__/
    *.pyc
    .env
    .venv/
    dist/
    !dist/.gitkeep        # negation re-includes a path
    
    • .gitignore only 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 true with 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.4 checks 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.

    #c-security-and-housekeeping-in-git-repositories