Setting Up a Team Git Workflow
A well-defined Git workflow is one of the highest-leverage investments a software team can make. It determines how code moves from an individual's laptop to production, how conflicts are resolved, who can review and approve changes, and what the history of the project looks like six months from now. Without a shared workflow, every developer invents their own conventions — leading to confusion, lost work, and broken deployments.
This guide covers everything a team needs: choosing the right workflow model, naming conventions, commit standards, pull-request etiquette, branch protection, onboarding, conflict resolution policy, and a full day-in-the-life walkthrough.
Choosing a Workflow
Three workflows dominate the industry. Each makes different trade-offs between simplicity, release cadence, and the number of long-lived branches.
Workflow | Long-lived branches | Best for | Complexity |
|---|---|---|---|
GitHub Flow | main only | Continuous deployment, SaaS, web apps | Low |
Gitflow | main + develop + support | Versioned software, mobile apps, libraries | High |
Trunk-based | main only (short-lived feature branches) | High-velocity teams with strong CI | Medium |
GitHub Flow is recommended for most teams. It has one rule: anything on main is deployable. All work happens on short-lived feature branches that merge into main via a pull request. There is no separate develop branch, no release branch ceremony, and no complex merge choreography.
Branch Naming Conventions
Consistent branch names make it easy to understand the purpose of a branch at a glance, automate CI triggers, and keep dashboards readable. Adopt a prefix-slash-description pattern:
Prefix | Purpose | Example |
|---|---|---|
feature/ | New functionality | feature/user-authentication |
fix/ | Bug fix in development | fix/login-redirect-loop |
hotfix/ | Urgent production fix | hotfix/payment-null-pointer |
release/ | Release preparation branch (Gitflow) | release/2.4.0 |
chore/ | Maintenance, dependency updates, tooling | chore/upgrade-react-19 |
docs/ | Documentation only changes | docs/api-authentication |
refactor/ | Code restructuring without behaviour change | refactor/extract-auth-service |
test/ | Adding or fixing tests only | test/coverage-payment-module |
Rules for branch names:
Use lowercase letters and hyphens only — no spaces, underscores, or slashes beyond the prefix.
Keep names short but descriptive:
feature/oauth-googlenotfeature/add-the-google-oauth-integration-to-the-login-page.Include a ticket/issue number when relevant:
feature/AUTH-142-oauth-google.Never use personal names:
johns-branchis opaque to everyone else.Delete branches after they are merged.
Commit Message Standards (Conventional Commits)
The Conventional Commits specification is a lightweight convention on top of commit messages that provides a readable history, powers automated changelogs, and enables semantic version bumping. Every commit message follows this structure:
Conventional Commit format
<type>(<optional scope>): <description> [optional body] [optional footer(s)]
Type | When to use | Version bump |
|---|---|---|
feat | A new feature visible to users | Minor (1.x.0) |
fix | A bug fix | Patch (1.0.x) |
docs | Documentation only | None |
style | Formatting, whitespace — no logic change | None |
refactor | Code change that is neither feat nor fix | None |
perf | Performance improvement | Patch |
test | Adding or correcting tests | None |
build | Build system or external dependency changes | None |
ci | CI/CD configuration files and scripts | None |
chore | Other changes not modifying src or test files | None |
revert | Reverts a previous commit | Patch |
A breaking change is indicated by appending ! after the type or by adding BREAKING CHANGE: in the footer:
Good commit messages
feat(auth): add Google OAuth login Users can now sign in with their Google account via the /login page. The feature uses the official Google Identity Services library. Closes #142 --- fix(cart): prevent negative quantity when decrementing The decrement button was allowing quantities below 1. Added a Math.max(0, quantity - 1) guard in CartItem.tsx. Fixes #209 --- feat(api)!: rename /users endpoint to /accounts BREAKING CHANGE: All clients must update their base URL from /api/v1/users to /api/v1/accounts. The old path returns 410 Gone.
Pull Request Requirements
A pull request (PR) is the formal mechanism for proposing, reviewing, and merging a change. Consistent PR requirements reduce review overhead and prevent low-quality code from reaching main.
PR description template — add this as .github/pull_request_template.md:
.github/pull_request_template.md
## What <!-- One sentence: what does this PR do? --> ## Why <!-- Why is this change needed? Link to issue/ticket if applicable. --> <!-- Closes #<issue-number> --> ## How to test <!-- Step-by-step instructions for a reviewer to verify the change works. --> 1. Check out this branch: `git checkout feature/...` 2. Run `npm install && npm run dev` 3. Navigate to ... 4. Expect to see ... ## Screenshots (if applicable) <!-- Before / After screenshots for UI changes --> ## Checklist - [ ] Tests added/updated - [ ] Documentation updated - [ ] No console.log left in code - [ ] Accessibility considered - [ ] Breaking changes noted above
Minimum requirements before requesting review:
All CI checks must pass (linting, type-checking, tests).
At least one reviewer assigned — preferably the team member most familiar with the affected code.
PR is small and focused: one logical change per PR, ideally under 400 lines changed.
Self-review performed: the author reads their own diff before tagging reviewers.
Description filled out — not just the default template with nothing in it.
Branch Protection Setup
Branch protection rules on GitHub prevent direct pushes to main, enforce review requirements, and block merges when CI is red.
Recommended settings for main on GitHub (Settings → Branches → Add rule):
Require a pull request before merging — prevents direct commits.
Required approvals: 1 or 2 — 1 for small teams, 2 for critical services.
Dismiss stale pull request approvals when new commits are pushed — re-review after any change.
Require status checks to pass before merging — select your CI jobs (tests, lint, build).
Require branches to be up to date before merging — prevents merge of stale code.
Do not allow bypassing the above settings — applies even to administrators.
Restrict who can push to matching branches — limit to release managers for hotfix scenarios.
.github/workflows/ci.yml — minimal CI to protect main
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --coverageOnboarding a New Team Member
A clear onboarding checklist means a new developer can make their first PR on day one without asking where everything is.
Day 1 checklist:
Create a GitHub account and request organisation access from the team lead.
Set up SSH key and add it to GitHub:
ssh-keygen -t ed25519 -C "you@example.com"→ copy~/.ssh/id_ed25519.pubto GitHub Settings → SSH keys.Clone the repository:
git clone git@github.com:org/project.git && cd project.Configure local Git identity:
git config user.name "Your Name"andgit config user.email "you@company.com".Install dependencies:
npm install(oryarn install/pnpm install).Run the project locally to confirm it works:
npm run dev.Read
CONTRIBUTING.md— this is the single source of truth for workflow, branch naming, and commit style.Create a test branch to verify you have push access:
git checkout -b chore/test-access && git push origin chore/test-access.Open a "Getting started" PR with a trivial change (fix a typo, add your name to CONTRIBUTORS) to practice the PR process end to end.
Delete the test branch after the PR is merged.
Conflict Resolution Policy
Merge conflicts are inevitable. A written policy prevents arguments and wasted time:
The branch author is responsible for resolving conflicts — not the reviewer, not the person who merged first.
Rebase your feature branch on
mainrather than mergingmaininto your branch:git fetch origin && git rebase origin/main. This keeps history linear.For complex conflicts, pair with the author of the conflicting code. Do not guess their intent.
When a conflict is in a generated file (lock file, build output), regenerate the file rather than manually editing the conflict markers.
Always run tests after resolving conflicts before pushing.
If a conflict is unresolvable in a reasonable time, open a discussion in the PR thread rather than blocking indefinitely.
Day-in-the-Life Walkthrough
Here is a complete example of a developer named Alex building the "dark mode toggle" feature using GitHub Flow.
1. Start the day — sync with main
git checkout main git pull origin main # Already up to date. # From github.com:org/project # * branch main -> FETCH_HEAD
2. Create a feature branch
git checkout -b feature/dark-mode-toggle # Switched to a new branch 'feature/dark-mode-toggle'
3. Work, stage, and commit incrementally
# ... edit src/components/ThemeToggle.tsx ... git add src/components/ThemeToggle.tsx git commit -m "feat(ui): add ThemeToggle component" # ... edit src/context/ThemeContext.tsx ... git add src/context/ThemeContext.tsx git commit -m "feat(theme): add ThemeContext with dark/light support" # ... add tests ... git add src/components/ThemeToggle.test.tsx git commit -m "test(ui): add unit tests for ThemeToggle"
4. Before pushing — rebase on latest main
git fetch origin git rebase origin/main # Successfully rebased and updated refs/heads/feature/dark-mode-toggle.
5. Push and open a PR
git push origin feature/dark-mode-toggle # Enumerating objects: 7, done. # Counting objects: 100% (7/7), done. # Writing objects: 100% (4/4), 1.23 KiB | 1.23 MiB/s, done. # remote: Create a pull request for 'feature/dark-mode-toggle': # remote: https://github.com/org/project/pull/new/feature/dark-mode-toggle
Alex opens the PR, fills in the template, assigns a reviewer, and waits. The reviewer requests one change.
6. Address review feedback
# Fix the issue the reviewer flagged git add src/components/ThemeToggle.tsx git commit -m "fix(ui): use prefers-color-scheme for initial theme" git push origin feature/dark-mode-toggle
7. PR approved and merged — clean up
# After the PR is merged on GitHub: git checkout main git pull origin main git branch -d feature/dark-mode-toggle # Deleted branch feature/dark-mode-toggle (was a3f9c21).
CONTRIBUTING.md Template
Every repository should have a CONTRIBUTING.md in the root. Here is a template your team can adapt:
CONTRIBUTING.md
# Contributing to <Project Name> Thank you for contributing! Please read this guide before opening an issue or pull request. ## Development setup ```bash git clone git@github.com:org/project.git cd project npm install npm run dev # start local server npm test # run tests npm run lint # lint & typecheck ``` ## Workflow We use **GitHub Flow**: 1. Branch off `main`: `git checkout -b feature/your-feature` 2. Commit using Conventional Commits (see below). 3. Push and open a pull request against `main`. 4. At least one approval + all CI checks green = ready to merge. 5. Delete your branch after merge. ## Branch naming | Prefix | Use for | |-------------|-------------------------------| | feature/ | New features | | fix/ | Bug fixes | | hotfix/ | Urgent production fixes | | chore/ | Maintenance, deps, tooling | | docs/ | Documentation | | test/ | Test additions/fixes | ## Commit messages We follow the Conventional Commits spec. Format: ``` <type>(<scope>): <description> ``` Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore Example: `feat(auth): add password reset flow` ## Pull requests - Keep PRs small and focused — one logical change per PR. - Fill in the PR template fully. - Link to the related issue: `Closes #123`. - All CI checks must pass. - Request review from at least one team member. ## Code style Run `npm run lint` before pushing. The project uses ESLint + Prettier. Configuration is in `.eslintrc.json` and `.prettierrc`. ## Questions? Open a GitHub Discussion or post in #dev-help on Slack.