StackPractices
beginner By Mathias Paulenko

Git Workflow

A practical branching strategy for teams: feature branches, pull requests, and clean commit history.

Topics: devops

Overview

A Git workflow defines how your team uses branches to manage parallel work, review changes, and keep the main branch stable. The workflow described here — often called “GitHub Flow” — is lightweight, scales from solo developers to large teams, and integrates naturally with CI/CD pipelines.

When to Use

Use this workflow when:

  • Working in a team where multiple developers touch the same codebase. See GitHub Actions for CI/CD automation.
  • You want every change to be reviewed before it reaches production. See Unit Testing for pre-merge validation.
  • Your project deploys continuously from the main branch. See Docker Basics for containerized deployments.
  • You need a clear rollback path when something goes wrong. See Feature Flags for instant toggles.

Solution

Branching Model

main  ───────────────────────────────────────────►
       \
feature/login   ──────────►  (PR → review → merge)
       \
feature/payments  ────────►  (PR → review → merge)

Daily Commands

# Start a new feature
$ git checkout main
$ git pull origin main
$ git checkout -b feature/description

# Make commits
$ git add .
$ git commit -m "feat: add password reset endpoint"

# Push and open a pull request
$ git push -u origin feature/description
# Open PR on GitHub, request review, wait for CI to pass

# After merge, clean up
$ git checkout main
$ git pull origin main
$ git branch -d feature/description

Keeping a Clean History (optional)

# Before merging, rebase onto latest main
$ git fetch origin
$ git rebase origin/main

# If you have messy local commits, squash them
$ git rebase -i HEAD~3
# Change "pick" to "squash" for commits you want to combine

Commit Message Convention (Conventional Commits)

feat: add user authentication
fix: resolve race condition in checkout
docs: update API reference for v2
refactor: extract payment service

Explanation

  • main is always deployable: only merge code that passes tests and review.
  • Feature branches isolate work: each feature, bugfix, or experiment lives in its own branch.
  • Pull requests enforce quality: code review catches bugs, shares knowledge, and keeps standards consistent.
  • Rebasing vs. merging: rebasing rewrites your branch on top of main for a linear history; merging preserves the exact sequence of events. Use rebase for personal cleanup, merge for team history.

Variants

ModelBest ForComplexity
GitHub FlowContinuous deployment, small teamsLow
Git FlowScheduled releases, QA cyclesMedium
Trunk-basedMonorepos, very fast CILow
Release branchesLong-term support versionsMedium

What Works

  • Keep branches short-lived: a branch open for weeks accumulates merge conflicts and stale code. Aim for days, not weeks.
  • Write meaningful commit messages: explain why, not just what. Future you (and your teammates) will thank you.
  • Review your own PR first: read the diff before requesting reviewers. You will catch obvious issues.
  • Automate with CI: run lint, tests, and security scans on every pull request before anyone reviews.
  • Protect main: require PR reviews, passing CI, and up-to-date branches before merging into main.

Common Mistakes

  • Long-running branches: the longer a branch lives, the harder it is to merge. Rebase frequently.
  • Committing to main directly: even in a solo project, using branches keeps your history clean and rollback easy.
  • Giant pull requests: PRs with hundreds of files are impossible to review well. Split large capabilities into stacked PRs.
  • Ignoring merge conflicts: resolving conflicts hastily without understanding both sides introduces bugs.
  • Messy commit history: “fix”, “fix again”, “actually fix” makes git blame useless. Squash or amend before pushing.

Performance Tips

  1. Use shallow clones for CI. Reduce clone time by fetching only the latest commit:
$ git clone --depth 1 https://github.com/org/repo.git
# For a specific branch:
$ git clone --depth 1 --branch main https://github.com/org/repo.git
  1. Use git sparse-checkout for monorepos. Check out only the directories you need:
$ git clone --no-checkout https://github.com/org/monorepo.git
$ cd monorepo
$ git sparse-checkout init --cone
$ git sparse-checkout set packages/api packages/shared
$ git checkout main
  1. Enable fsmonitor for faster status checks. Git uses the OS file watcher to detect changes:
$ git config core.fsmonitor true
$ git config core.untrackedcache true
  1. Use git gc to optimize repository size. Run periodically to compress objects:
$ git gc --aggressive --prune=now
  1. Use git worktree for parallel work. Work on multiple branches simultaneously without cloning:
$ git worktree add ../repo-hotfix main
$ cd ../repo-hotfix
# Now you're on main in a separate directory
# Make hotfix changes while your feature branch stays open in the original

Frequently Asked Questions

Should I use merge or rebase?

Use rebase to keep your feature branch up to date with main locally. Use merge (or squash-merge) when integrating the feature into main via a pull request. Never rebase branches that other people are working on.

How often should I commit?

Commit whenever you reach a logical checkpoint — a working test, a completed function, a fixed bug. Frequent commits make reverting and reviewing easier.

What if I commit a secret (password, API key) to Git?

Rotate the secret immediately — it is now in Git history. Use tools like git-filter-repo or BFG Repo-Cleaner to remove it from history, then force-push. Prevention beats cleanup: use pre-commit hooks with secret scanning.