Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive ยท DevOps

A Git Workflow That Teams Can Follow

Trunk-based development with short-lived branches, pull requests and protected main: commands, branch protection, commit conventions, and how to recover from mistakes.

4 min read

  • Git 2.36
  • GitHub
  • Branch protection
  • CODEOWNERS

A team needs a workflow simple enough that everyone follows it under pressure. The version below keeps one long-lived branch (main), does all changes through short-lived branches and pull requests, and lets CI decide whether a change may merge. It works for application code, infrastructure code and documentation alike.

Versions in use on this date. Git 2.36 (released 18 April 2022) and GitHub. Note a security change in the 2.35.2 line (April 2022): Git now refuses to work in a repository owned by a different user unless it is listed in safe.directory, which you will meet in CI containers and shared servers (git config --global --add safe.directory /path/to/repo).

The flow

flowchart TB
  M[main - always deployable] --> B[git switch -c feature/login-timeout]
  B --> C[Small commits]
  C --> P[git push -u origin and open pull request]
  P --> CI{CI checks: tests, lint, scan}
  CI -->|fail| C
  CI -->|pass| R[Review by CODEOWNERS]
  R -->|changes requested| C
  R -->|approved| MG[Squash or rebase merge]
  MG --> M
  M --> D[Deploy from main or tag]
  M -->|hotfix: branch from main, same flow| B

Principles: main is always releasable; branches live for hours or a few days, not weeks; one logical change per pull request; and nobody pushes straight to main.

Daily commands

git switch main && git pull --ff-only
git switch -c fix/payment-timeout          # `git switch` and `git restore` are the clearer modern commands
# edit, test
git status; git diff
git add -p                                 # stage hunks deliberately
git commit -m "Fix payment timeout by retrying idempotent calls"
git fetch origin && git rebase origin/main     # stay current; resolve conflicts here, not in the PR
git push -u origin fix/payment-timeout
gh pr create --fill                        # GitHub CLI, or use the web UI

After merge: git switch main && git pull --ff-only && git branch -d fix/payment-timeout, and delete the remote branch (GitHub can do it automatically).

Good commits and pull requests

  • Commit messages: a short imperative subject (about 50 characters), a blank line, then why the change was needed. Optionally follow Conventional Commits (fix:, feat:, chore:) if you want automatic changelogs and versioning.
  • Small pull requests (under about 400 changed lines) get faster, better reviews and break less.
  • Describe what changed, how to test it, and the risk or rollback plan; link the ticket.
  • Squash merge for a clean history with one commit per change, or rebase merge to keep individual commits; avoid merge commits from long-lived branches unless the team prefers them.
  • Never commit secrets, large binaries or generated files. Add a .gitignore early, and a secret scanner such as gitleaks in a pre-commit hook and CI.

Protect main on GitHub

In Settings, Branches, Branch protection rules for main:

  • Require a pull request with at least 1 approval (2 for critical repos), and dismiss stale approvals on new commits.

  • Require status checks (tests, lint, security scan) to pass and the branch to be up to date.

  • Require review from code owners using a CODEOWNERS file:

    # .github/CODEOWNERS
    /infra/            @acme/platform-team
    /services/payments/ @acme/payments-team
    *.tf               @acme/platform-team
  • Block force pushes and deletions; require signed commits if your policy needs them; include administrators in the rules.

Releases and hotfixes

Tag releases from main: git tag -a v1.8.0 -m "Release 1.8.0" && git push origin v1.8.0 (semantic versioning). If you must support old versions, create a release/1.7 branch from the tag and cherry-pick fixes (git cherry-pick -x <sha>). Hotfix process is the same as normal changes, just faster, and never skips review and CI.

Alternatives, briefly

Model Fits Cost
Trunk-based (above) Continuous delivery, small teams, strong CI Needs feature flags for unfinished work
GitFlow (develop, release, hotfix) Versioned products with scheduled releases Many long-lived branches, painful merges
Fork and pull request Open source, external contributors More setup per contributor

Recover from common mistakes

git restore --staged file.txt             # unstage
git restore file.txt                      # discard working changes (irreversible)
git commit --amend                        # fix the last commit (only if not pushed or only you use the branch)
git revert <sha>                          # undo a pushed commit with a new commit
git reflog                                # every place HEAD has been: your safety net
git switch -c rescue <sha-from-reflog>    # recover "lost" commits
git push --force-with-lease               # after rebasing your own branch; never plain --force on shared branches
git stash push -m "wip"; git stash pop

Rules for rewriting history: rebase and amend only commits that exist only on your branch. Once something is merged to main, use revert. If a secret was pushed, rotating the secret matters more than rewriting history, because clones and caches may already have it.