Skip to content

Latest commit

 

History

History
387 lines (280 loc) · 9.23 KB

File metadata and controls

387 lines (280 loc) · 9.23 KB

Git Workflow & Branching Procedure

Branch Model

main              ← Production. Protected. Only receives merges from dev.
  └── dev         ← Integration. All feature work merges here first.
       ├── feat/  ← New features
       ├── fix/   ← Bug fixes
       ├── refactor/
       ├── chore/
       └── docs/

Golden rules:

  • Never push directly to main or dev.
  • Always work on a feature branch.
  • Always rebase before opening a PR (keeps history linear).
  • Always squash-merge feature branches into dev (one commit per feature).
  • Always create a merge commit when merging devmain (preserves release boundary).

Day-to-Day Workflow

1. Start New Work

# Make sure dev is up to date
git checkout dev
git pull origin dev

# Create your feature branch
git checkout -b feat/user-authentication

Branch naming: <type>/<short-description>

Type Use
feat/ New feature
fix/ Bug fix
refactor/ Code restructuring (no behavior change)
chore/ Dependencies, tooling, config
docs/ Documentation only
perf/ Performance improvement
test/ Adding or updating tests

2. Work & Commit

Make small, logical commits as you work:

# Stage specific files (not git add .)
git add src/features/auth/components/login-form.tsx
git add src/features/auth/api/use-login.ts

# Commit with conventional commit message
git commit -m "feat(auth): add login form with email/password"

Commit message rules:

  • Format: <type>(<scope>): <summary>
  • Imperative mood: "add", not "added" or "adds"
  • Lowercase, no period, max 72 chars
  • Body (optional): explain why, not what
# Multi-line commit when you need a body
git commit -m "fix(convex): handle null cursor in pagination

The paginate() call returns null cursor on the last page.
Previously this caused a runtime error when attempting to
fetch the next page.

Closes #42"

3. Keep Your Branch Updated (Rebase)

While working, dev may receive new merges. Stay updated with rebase (not merge):

# Fetch latest changes
git fetch origin

# Rebase your branch on top of latest dev
git rebase origin/dev

If there are conflicts:

# Git will pause at the conflicting commit
# 1. Fix the conflicts in your editor
# 2. Stage the resolved files
git add <resolved-files>

# 3. Continue the rebase
git rebase --continue

# If things go wrong and you want to abort
git rebase --abort

Why rebase instead of merge?

  • Rebase replays your commits on top of dev → linear history, no merge bubbles.
  • Merge creates an extra merge commit and tangled history.
  • Linear history is easier to read, bisect, and revert.

4. Clean Up Commits Before PR (Interactive Rebase)

Before opening a PR, squash WIP/fixup commits into clean logical units:

# Squash last 3 commits into one (adjust number as needed)
git rebase -i HEAD~3

This opens an editor. Change pick to squash (or s) for commits you want to fold:

pick abc1234 feat(auth): add login form skeleton
squash def5678 wip: styling tweaks
squash ghi9012 fix typo in login form

Save and close. Git will let you write a new combined commit message:

feat(auth): add login form with email/password validation

Common interactive rebase commands:

Command What it does
pick Keep the commit as-is
squash Merge into previous commit
fixup Like squash but discard this message
reword Keep commit, edit the message
drop Delete the commit entirely

5. Push & Open PR

# First push (set upstream)
git push -u origin feat/user-authentication

# Subsequent pushes
git push

# After a rebase, you need force push (your branch only!)
git push --force-with-lease

--force-with-lease vs --force:

  • --force-with-lease is safer — it fails if someone else pushed to your branch.
  • --force overwrites blindly. Never use on shared branches.
  • Never force push to dev or main.

Open the PR:

gh pr create --base dev --title "feat(auth): add login form" --body "## Summary
- Added email/password login form
- Connected to Convex auth backend
- Added form validation with error states

## Test Plan
- [ ] Login with valid credentials
- [ ] Login with invalid email shows error
- [ ] Login with wrong password shows error
- [ ] Empty form shows validation messages"

6. PR Review & Merge

After approval, squash merge into dev:

# Via GitHub CLI
gh pr merge --squash --delete-branch

Or use the GitHub UI: select "Squash and merge" from the merge dropdown.

Why squash merge?

  • One clean commit per feature in dev.
  • Internal WIP commits don't pollute the history.
  • Easy to revert an entire feature with one git revert.

7. Clean Up Local Branch

# Switch back to dev
git checkout dev

# Pull the squash-merged commit
git pull origin dev

# Delete the local feature branch
git branch -d feat/user-authentication

# Prune remote tracking branches that were deleted
git fetch --prune

Release Workflow (dev → main)

When dev is stable and ready for production:

# Make sure dev is up to date
git checkout dev
git pull origin dev

# Create a release PR (merge commit, not squash)
gh pr create --base main --title "release: v0.2.0" --body "## Summary
- feat(auth): add login/signup flow
- fix(dashboard): loading state on slow connections
- refactor(convex): optimize query indexes

## Test Plan
- [ ] Full regression test on staging
- [ ] Verify all new features work end-to-end"

Merge with a merge commit (not squash):

gh pr merge --merge

Why merge commit for releases?

  • Preserves the individual feature commits in main's history.
  • The merge commit marks a clear release boundary.
  • Easy to see what was included in each release.

After merging, tag the release:

git checkout main
git pull origin main
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0

Quick Reference: Merge Strategies

Merge Into Strategy Why
dev ← feature branch Squash merge One clean commit per feature
maindev Merge commit Preserves history, marks release boundary

Common Scenarios

Scenario: I committed to dev by accident

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

# Create the proper branch
git checkout -b feat/my-feature

# Commit there
git commit -m "feat: the thing I was working on"

Scenario: I need changes from another feature branch

Don't merge the other branch. Cherry-pick the specific commit:

git cherry-pick <commit-hash>

Or if you truly need the whole branch, rebase onto it (but this is rare and should be discussed with the team).

Scenario: My rebase has too many conflicts

If a rebase is painful, you can squash first then rebase:

# Squash all your commits into one
git reset --soft origin/dev
git commit -m "feat(auth): my feature (squashed)"

# Now rebase — only one commit to resolve conflicts for
git rebase origin/dev

Scenario: I need to undo a merged feature

Since we squash-merge, each feature is one commit in dev:

git checkout dev
git pull origin dev

# Revert the specific feature commit
git revert <commit-hash>
git push origin dev

Scenario: Hotfix for production

# Branch from main (not dev)
git checkout main
git pull origin main
git checkout -b fix/critical-bug

# Fix, commit, push
git commit -m "fix: prevent crash on null user session"
git push -u origin fix/critical-bug

# PR into main directly
gh pr create --base main --title "fix: prevent crash on null user session"

# After merge, backport to dev
git checkout dev
git pull origin dev
git cherry-pick <hotfix-commit-hash>
git push origin dev

Git Config Recommendations

Run these once per machine for a cleaner experience:

# Default to rebase when pulling (avoids accidental merge commits)
git config --global pull.rebase true

# Auto-prune deleted remote branches on fetch
git config --global fetch.prune true

# Better diff algorithm
git config --global diff.algorithm histogram

# Sort branches by most recent commit
git config --global branch.sort -committerdate

# Default branch for new repos
git config --global init.defaultBranch main

Summary Cheat Sheet

# Start work
git checkout dev && git pull origin dev
git checkout -b feat/my-feature

# Commit
git add <files>
git commit -m "feat(scope): description"

# Stay updated
git fetch origin && git rebase origin/dev

# Clean up before PR
git rebase -i HEAD~N

# Push
git push -u origin feat/my-feature   # first time
git push --force-with-lease           # after rebase

# Open PR
gh pr create --base dev --title "feat(scope): description"

# After merge, clean up
git checkout dev && git pull origin dev
git branch -d feat/my-feature
git fetch --prune