Tools

Git Workflow Guide for Development Teams

Master Git workflows and collaboration strategies for effective team development.

Plake
Mar 28, 20266 min read1,160 words
Git Workflow Guide for Development Teams

Version control is the foundation every other engineering process rests on. A team that moves fast with Git can ship safely, review confidently, and recover cleanly. A team that muddles through it loses hours to mystery merges, undocumented reverts, and the dreaded “just force-push it” moments. This guide covers the practices that hold up at scale.

Git Fundamentals

If you are new to Git, the mental model to start with: every commit is a snapshot of the entire repository, not a diff. Git stores a graph of these snapshots. Most commands are just operations on that graph.

# Configure once per machine
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global pull.rebase true    # keeps history linear on pull

# Start a repo
git init
git clone https://github.com/org/repo.git

# The daily loop
git status                      # what changed?
git diff                        # what exactly changed?
git add src/feature.ts          # stage a specific file
git add -p                      # stage interactively, hunk by hunk
git commit -m "feat: add login" # commit what's staged

Branching Strategy

Pick one strategy and document it. The most widely used are:

Simple, linear, CI-friendly. One long-lived branch (main), everything else is short-lived feature branches.

main
├── feat/login-page          (open PR → review → merge → delete)
├── fix/password-reset-email
└── chore/upgrade-dependencies

Rules:

  • main is always deployable.
  • Create a branch for every change.
  • Open a PR as soon as the branch exists (mark it Draft if not ready).
  • Merge only when CI is green and at least one review is approved.
  • Delete the branch after merge.

Git Flow (for versioned / release-based projects)

Adds develop, release/*, and hotfix/* branches. Useful when you maintain multiple production versions simultaneously. Adds complexity most web teams don’t need.

Trunk-Based Development

Everyone commits to main (or a very short-lived branch — less than a day). Requires strong feature flags and comprehensive CI. Favoured by teams shipping many times per day.

Naming Conventions

Consistent branch names make git branch -a readable and CI regex rules easy.

feat/short-description        # new feature
fix/what-it-fixes             # bug fix
chore/what-it-touches         # maintenance, dependency bumps
docs/what-it-documents        # documentation only
refactor/what-changed         # no behaviour change

Commit Messages

A good commit message is a gift to the person debugging this code six months from now — including yourself.

Follow Conventional Commits:

<type>(<optional scope>): <short summary>

<optional body — explain the WHY, not the what>

<optional footer: BREAKING CHANGE, closes #123>
# Good
git commit -m "feat(auth): add JWT refresh token rotation"
git commit -m "fix(cart): prevent double-submit on slow connections"
git commit -m "chore: upgrade Astro to 5.1.0"

# Avoid — tells you nothing
git commit -m "fix stuff"
git commit -m "WIP"
git commit -m "changes"

Multi-line commit for complex changes:

git commit -m "$(cat <<'EOF'
feat(billing): add proration when upgrading plans

Stripe's default is to charge the full amount on upgrade.
Proration gives users credit for unused days, which significantly
reduced upgrade churn in A/B testing.

Closes #412
EOF
)"

Pull Requests

A PR is a conversation, not a formality. The goal is shared understanding — not just getting code merged.

Writing a good PR description:

## What changed
- Added `ProrationService` that calculates credit amounts
- Wired to `BillingController.upgrade()` endpoint
- Updated integration tests to cover mid-cycle upgrades

## Why
Stripe charges full price by default. Proration reduced upgrade
abandonment by ~18% in the experiment (see #412).

## Testing
- [x] Unit tests for `ProrationService`
- [x] Integration test for full upgrade flow
- [x] Tested manually against Stripe test mode

## Screenshots
[before / after if UI changed]

Reviewing a PR:

  • Read the PR description before the diff.
  • Check the “why” first — disagreeing with the approach is more important than nitpicking the style.
  • Distinguish blocking comments from suggestions: prefix non-blocking with nit: or optional:.
  • Approve only when you would be comfortable being paged for it.

Conflict Resolution

Conflicts happen when two branches modify the same lines. They are not a crisis — they are information.

# Update your branch before resolving conflicts
git fetch origin
git rebase origin/main     # preferred — keeps history linear
# or
git merge origin/main      # creates a merge commit

# Git marks conflicts in the file
<<<<<<< HEAD
const timeout = 5000;
=======
const timeout = 3000;
>>>>>>> origin/main

Resolve by editing the file to the correct state, then:

git add src/config.ts
git rebase --continue      # or git commit if merging

For visual conflict resolution:

git config --global merge.tool vscode
git mergetool

After resolving, always run tests. The build passing before the merge does not mean it passes after.

Useful Daily Commands

# Review your own changes before committing
git diff --staged

# Interactive rebase — clean up commits before pushing
git rebase -i HEAD~3

# Find which commit introduced a bug
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
# Mark each checkout as good/bad until Git isolates the commit

# Stash work in progress
git stash push -m "WIP: half-done feature"
git stash list
git stash pop

# Undo the last commit but keep the changes staged
git reset --soft HEAD~1

# See who changed this line last
git log -p -S "functionName" src/module.ts

Tags and Releases

# Create an annotated tag for a release
git tag -a v1.5.0 -m "Release v1.5.0"
git push origin v1.5.0

# List tags
git tag -l "v1.*"

# Delete a remote tag (careful — informs downstream)
git push origin --delete v1.5.0

Editorial Git workflow illustration

.gitignore

Keep your repository clean. Common patterns:

# Dependencies
node_modules/
.pnp
.pnp.js

# Build output
dist/
.astro/
out/

# Environment — NEVER commit secrets
.env
.env.local
.env.*.local

# OS / editor
.DS_Store
Thumbs.db
.idea/
.vscode/settings.json   # commit .vscode/extensions.json instead

# Logs
*.log
npm-debug.log*

Continuous Integration

Every push should trigger CI. The minimum bar:

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  check:
    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 -- --ci --coverage

  # Optional: block merges with failing status
  required-checks:
    needs: check
    runs-on: ubuntu-latest
    steps:
      - run: echo "All checks passed"

Protect main with branch protection rules that require CI to pass and at least one approved review before merge. Friction there is intentional.

Closing Thoughts

Git is the one tool every person on your team uses every day. Time spent making the workflow legible — clear branches, honest commit messages, descriptive PRs — pays compound interest. The engineer debugging a production incident at 2 AM will thank whoever wrote the commit message that explained the why.

Share:

About the author

The Plake Team

Writes long-form essays on craft, software, and the slow web. Curates the Sunday digest read by 88,000+ readers.

Read more

Keep reading

Related Articles

Back to AllBack to All
Docker and Containerization for Modern Web Development

Mar 18, 2026

4 min

Docker and Containerization for Modern Web Development