Branching Workflow
A short protocol for collaborating on this repo without stepping on each other’s toes. Use this when you’re starting a piece of work that isn’t a one-line hotfix.
Mental model
Section titled “Mental model”mainis the canonical line of commits — what we treat as “live”.scripts/deploy-worker.shdeploys from whatever HEAD is, so by convention prod should always matchmain.- A branch is just a moveable label pointing at a commit. Cheap — no file copy, just a pointer. You can have unlimited branches.
- Working on a branch means your commits don’t touch
mainuntil a Pull Request is reviewed and merged. Multiple branches can run in parallel.
Everyday workflow
Section titled “Everyday workflow”1. Start from the latest main
Section titled “1. Start from the latest main”git fetch origingit checkout maingit pull --ff-only origin main--ff-only refuses to silently merge if your local main has diverged —
you want this. If it errors, something on your local main isn’t yet on
origin; sort that first before continuing.
2. Make a branch
Section titled “2. Make a branch”git checkout -b graham/partial-onboardingConvention: <your-name>/<short-feature-name>. Lowercase, hyphens.
Examples: cathal/lrc-insights, graham/gp-listing-api,
cathal/fix-pleper-timeout.
3. Work and commit
Section titled “3. Work and commit”Commit as often as you like on the branch. Small commits are easier to review than one giant one.
git add worker/src/handlers/partial-onboarding.jsgit commit -m "feat: scaffold partial-onboarding endpoint"
git add worker/src/handlers/partial-onboarding.jsgit commit -m "feat: claude listing-draft prompt"Commit-message style in this repo: lowercase prefix
(feat: / fix: / perf: / tweak: / spike: / chore:) plus a
short imperative sentence. Match the git log you see when you run
git log --oneline -20.
4. Push the branch
Section titled “4. Push the branch”First push needs -u to set upstream tracking; after that just
git push.
git push -u origin graham/partial-onboarding5. Open a Pull Request
Section titled “5. Open a Pull Request”Easiest from the terminal:
gh pr create --title "Partial onboarding: GP listing automation" --body "$(cat <<'EOF'## Summary- New module worker/src/handlers/partial-onboarding.js- Adds /dashboard-partial-onboarding-run endpoint- New BQ table fcr_operations.partial_onboarding_log
## Test plan- [ ] Manual: POST /dashboard-partial-onboarding-run with a known dealId- [ ] Verify HubSpot task is created with draft body + preview URL- [ ] Verify BQ log row is writtenEOF)"Or use the web UI on GitHub — it’ll detect the pushed branch and offer “Compare & pull request”.
Title: short, under 70 characters. Detail goes in the description.
6. Review
Section titled “6. Review”The PR shows a side-by-side diff. The reviewer can:
- Leave comments on specific lines (click the
+in the gutter next to a line) - Click Files changed → Review changes → choose Comment / Approve / Request changes
- Iterate by pushing more commits to the same branch — they appear in the PR automatically
7. Merge
Section titled “7. Merge”Once approved, click Merge pull request on GitHub. You’ll see three options — pick Squash and merge by default in this repo (see below).
After merge, delete the branch (GitHub button does it for you). Locally:
git checkout maingit pull --ff-only origin main # bring the merged commit downgit branch -d graham/partial-onboarding # delete the local branchMerge strategies — when to pick which
Section titled “Merge strategies — when to pick which”GitHub gives you three options when you click Merge. Pick per-PR:
| Strategy | What it does | When |
|---|---|---|
| Squash and merge | Collapses every commit on the branch into one clean commit on main | Default for most PRs. Keeps main history readable. |
| Rebase and merge | Replays each branch commit linearly onto main | Only when each individual commit is meaningful on its own. |
| Create merge commit | Adds a merge commit joining the branch into main | Rare. Avoid — clutters history. |
For this repo: default to Squash. Looking at our existing git log it’s already roughly one-commit-per-feature.
Deploy implications
Section titled “Deploy implications”scripts/deploy-worker.sh deploys from whatever HEAD currently is —
it doesn’t care about branches. The discipline is:
- PR opened from a branch
- Reviewed and merged into
main - Pull
mainlocally - Run
scripts/deploy-worker.shfrommain
Don’t deploy from a feature branch unless it’s a deliberate emergency
hotfix — and even then, follow up with a PR + merge so prod state is
reachable from main.
Three safety rules
Section titled “Three safety rules”-
Never
--forcepush tomain. It would wipe other people’s commits and thedeploy/worker/*rollback tags. If git ever suggests--forceon main, that’s a stop sign — pause and ask before doing it. -
Always
git pull --ff-onlywhen pulling main. The--ff-onlyflag refuses to silently merge, so if your local has diverged you see an error instead of a surprise merge commit. -
Branch before working on anything substantial. It’s much easier to throw a branch away than to untangle a mistake on main.
Common situations
Section titled “Common situations””I started work on main and now I want it on a branch"
Section titled “”I started work on main and now I want it on a branch"”# You have uncommitted changes on main, none of them pushedgit checkout -b graham/my-work # branch keeps your changes# now commit on the branch instead"I already committed to main locally, didn’t push, want to move it to a branch"
Section titled “"I already committed to main locally, didn’t push, want to move it to a branch"”git branch graham/my-work # branch label points at your commitsgit reset --hard origin/main # rewind local main to remotegit checkout graham/my-work # work continues on the branch"I pushed to main by accident, can I undo?”
Section titled “"I pushed to main by accident, can I undo?””Don’t undo. Open a revert PR if the commit needs reverting, otherwise just leave it — main history is append-only for everyone’s sanity.
”My branch has fallen behind main while I was working”
Section titled “”My branch has fallen behind main while I was working””# On your branchgit fetch origingit rebase origin/main # replays your commits on top of latest main# resolve any conflicts, git add, git rebase --continuegit push --force-with-lease # safer than --force; refuses if remote moved unexpectedly--force-with-lease is the one place forcing IS okay — but only on
your own branch, never on main.
”Two people working on the same file”
Section titled “”Two people working on the same file””Open both PRs. Whoever merges first wins by default. Whoever merges second has to rebase their branch on top of the now-updated main and resolve the conflicts. Talk to each other if it’s going to be ugly.
Branch protection (recommended setup)
Section titled “Branch protection (recommended setup)”On GitHub: Settings → Branches → Add rule for main. Recommended
settings:
- Require a pull request before merging — enforces the workflow
- Require approvals: 1 — at least one reviewer signs off
- Require status checks — if you add CI later
- Do not allow bypassing the above settings — including for admins
Once this is on, “no direct push to main” becomes an enforced rule rather than just a habit.
Quick reference card
Section titled “Quick reference card”# Start a branchgit fetch origin && git checkout main && git pull --ff-onlygit checkout -b you/your-feature
# Mid-workgit add … && git commit -m "feat: ..."git push -u origin you/your-feature # first push onlygit push # subsequent pushes
# Open PRgh pr create --title "..." --body "..."
# After review, GitHub web UI: Squash and merge
# Clean upgit checkout main && git pull --ff-onlygit branch -d you/your-feature
# Branch falls behind main mid-workgit fetch origin && git rebase origin/maingit push --force-with-lease
# Deploy after mergescripts/deploy-worker.sh