Developer Tools

Git Workflow Guide: Branching, Merging, and Team Collaboration

Master Git branching strategies, commit best practices, rebasing vs. merging, and the workflows used by professional engineering teams.

8 min read

Developer looking at code on a screen

Git is the foundation of every modern software team's workflow. Yet many developers use only commit, push, and pull — leaving behind the features that make collaborative development smooth and conflict-free. This guide covers the branching strategies, commit practices, and everyday commands used by professional teams.

The core mental model

Git is a directed acyclic graph of snapshots (commits). Each commit points to its parent(s). Branches are simply named pointers to commits — lightweight and cheap to create.

main:    A → B → C → D
feature:         C → E → F

Branching doesn't copy files — it just creates a new pointer. That's why creating a branch takes milliseconds regardless of repository size.

Branching strategies

GitHub Flow (simple, continuous delivery)

Best for teams that deploy frequently:

  1. main is always deployable
  2. Create a feature branch for every change
  3. Open a pull request when ready for review
  4. Merge to main after approval
  5. Deploy immediately
git checkout -b feature/add-user-auth
# ... make changes ...
git push origin feature/add-user-auth
# Open PR → review → merge → deploy

Git Flow (structured releases)

Best for products with versioned releases (apps, libraries):

  • main — production code only
  • develop — integration branch
  • feature/* — new features (branch from develop)
  • release/* — release preparation (branch from develop)
  • hotfix/* — urgent production fixes (branch from main)

More structured but adds overhead. Use GitHub Flow unless you have a genuine need for parallel release management.

Trunk-Based Development

Developers commit directly to main (or short-lived branches < 1 day). Feature flags control what users see. Requires strong CI/CD and test coverage. Used by Google, Facebook, and high-velocity teams.

Writing great commit messages

The commit message is a letter to your future self (and teammates). Follow the Conventional Commits format:

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

<optional body — what and why, not how>

<optional footer — breaking changes, issue refs>

Types:

  • feat — new feature
  • fix — bug fix
  • docs — documentation only
  • refactor — code change that neither fixes a bug nor adds a feature
  • test — adding or fixing tests
  • chore — build process, dependencies, tooling

Examples:

feat(auth): add JWT refresh token rotation

Implements sliding session windows using refresh token rotation.
Previous tokens are invalidated on use to detect theft.

Closes #142
fix(api): return 404 instead of 500 for missing user

The /users/:id endpoint was throwing an unhandled exception when
the user didn't exist. Now returns a proper 404 with error message.

Rule of thumb: If your commit message is fix stuff or WIP, your team hates you. Make it descriptive.

Merge vs. Rebase

Merge

git checkout main
git merge feature/my-feature

Creates a merge commit that joins two branches. Preserves full history — you can see exactly when branches diverged and merged.

A → B → C → M (merge commit)
         ↑   ↑
         E → F (feature)

Use merge for: integrating long-running branches, preserving context in history.

Rebase

git checkout feature/my-feature
git rebase main

Replays your commits on top of the target branch. Creates a linear history — looks like the feature was built on top of the latest main.

Before: A → B → C (main)
                 ↑
            D → E (feature, based off B)

After:  A → B → C → D' → E' (feature rebased onto C)

Use rebase for: cleaning up local commits before a PR, keeping feature branches up to date.

Golden rule: Never rebase commits that have been pushed to a shared branch. Rebase rewrites history — it breaks other people's local branches.

Interactive rebase: cleaning up history

Before opening a PR, squash and fixup messy work-in-progress commits:

git rebase -i HEAD~4   # interactively edit the last 4 commits

Options in the interactive editor:

  • pick — keep the commit
  • squash — merge into the previous commit, combine messages
  • fixup — merge into the previous commit, discard message
  • reword — keep changes, edit message
  • drop — delete the commit entirely

Handling conflicts

Conflicts happen when two branches modify the same lines. Git marks them:

<<<<<<< HEAD (your branch)
const timeout = 5000;
=======
const timeout = 3000;
>>>>>>> feature/update-timeouts

Resolve by editing the file to the correct state (removing the markers), then:

git add src/config.ts
git merge --continue   # or git rebase --continue

Prevention is better than cure:

  • Pull from main frequently to keep branches short-lived
  • Communicate with teammates when working on the same files
  • Keep PRs small — large PRs have more conflicts and harder reviews

Essential daily commands

# Start a new feature
git checkout -b feature/my-feature

# Stage specific changes (not the whole file)
git add -p

# View unstaged changes
git diff

# View staged changes
git diff --staged

# Amend the last commit (before pushing)
git commit --amend --no-edit

# Temporarily save uncommitted work
git stash
git stash pop

# Find which commit introduced a bug (binary search)
git bisect start
git bisect bad              # current commit is bad
git bisect good v1.2.0      # last known good commit

# Recover a deleted branch or lost commit
git reflog

# See a visual graph of branches
git log --oneline --graph --all

Pull request best practices

A good PR:

  • Does one thing — reviewers can understand the full change
  • Has a clear description — what changed, why, and how to test it
  • Is small — < 400 lines of diff as a rough target
  • Passes CI — never ask for review on a broken build
  • Links to the issueCloses #42 auto-closes the issue on merge

Use our README Generator to scaffold documentation alongside your code changes.

.gitignore essentials

Always add these to .gitignore:

# Environment variables
.env
.env.local
.env.*.local

# Dependencies
node_modules/
vendor/

# Build output
dist/
build/
.next/

# OS files
.DS_Store
Thumbs.db

# Editor files
.vscode/settings.json
.idea/
*.swp

Generate a project-specific .gitignore at gitignore.io — it knows about hundreds of languages and tools.

Git mastery compounds over time. Learn the mental model, adopt good commit habits, and your team's collaboration will become dramatically smoother.