Left to right is time. The thick line is main (the only long-lived Git branch).
A ticket is a short feature branch: it leaves main, gets a Preview site, then squash-merges back.
GitHub Actions then updates Dev. Staging and Production are tags on one commit, later.
New to this? Start on Normal way. Developer commands: Commands.
save on main save on a feature branch squash-merge onto the trunk Dev (newest main) Staging sticker Production sticker
Preview lives on the short spur. After squash-merge, GitHub’s robot puts that save on Dev.
Later the Release owner names the same save for Staging (v1.5.0-rc.1), then Production (v1.5.0 + Approve).
Dev keeps following newest main. Staging stays still. There is no Git branch called develop or staging.
Hotfix, rollback, and Git errors are under Exceptions.
Git vs servers
Git
Long-lived branch: main only
Short feature/… branches for tickets
A tag (sticker) names one commit, like v1.5.0
No Git branch named develop or staging
Servers (URLs the team opens)
Preview — website for that PR
Dev — newest main (Actions, at squash-merge)
Staging — QA, frozen on one tag
Production — that same commit, after Approve
Page 1 · The normal way
How we ship code
Pick the role that matches the job. Developer loop is in the green box. Copy-paste Git is on Commands.
Stuck? FAQ. Something weird? Exceptions.
Developer makes a branch → Senior developer merges it → Dev updates.
Later, Release owner sends that same code to Staging (QA), then Production (users).
Developer Every ticket
This is the whole Developer job. Copy-paste is under Commands.
git checkout main then git pull origin main
git checkout -b feature/JIRA-101-short-name — never commit on main
Release owner: send newest main to Staging or Production
Page 2 · Commands (normal way only)
Commands for each role
Developer: the daily loop is first. Copy the block that matches the numbered step.
Happy path is here. Production shipped mid-ticket is the extra Developer block.
Hotfix, QA bugs, and Git errors: Exceptions.
Developer Daily loop — every ticket
First time on this computer: clone the repogit status should say On branch main.
Get latest main, then make a feature branchName: feature/JIRA-101-short-name. Do not save while still on main.
Edit → git add the file path → git commit → git pushType the path. Do not use git add . (secrets can sneak in).
Open a Pull Request. Base must be mainUse the Preview link on the PR. Then stop and wait for a Senior developer.
Step 1 — first time onlygit clone https://github.com/YOUR-ORG/YOUR-REPO.git
cd YOUR-REPO
git status
Steps 2–4 — every ticketgit checkout main
git pull origin main
git checkout -b feature/JIRA-101-user-authentication
git add src/auth/login.ts
git commit -m "feat(auth): implement JWT token generation"
git push -u origin feature/JIRA-101-user-authentication
gh pr create --base main --title "feat(auth): JWT token generation"
# Or GitHub → Compare & pull request → base = main
Developer Production shipped, branch still open
Users just got a tag (example: v1.5.0). The Developer is still on feature/JIRA-101-….
That commit is already in Git. Stay on the feature branch. Rebase or merge newest main into it.
If Production was a hotfix, wait until that fix is on main first (Exception 5).
Stay on the feature branch. Finish or stash unsaved editsgit status must say the feature branch name. Do not git checkout v1.5.0 — that leaves the ticket.
Download tags and newest main. Confirm the Production saveDo not copy files from the Production server.
Put newest main into this feature branch, then keep workingNo PR comments yet: git rebase origin/main. Comments already exist: git merge origin/main. Same rule as Exception 3.
Step 1 — still on the unfinished feature branchgit status
Sample for step 1 — git statusOn branch feature/JIRA-101-user-authentication
Your branch is up to date with 'origin/feature/JIRA-101-user-authentication'.
nothing to commit, working tree clean
# If Git lists unsaved files: git add path + git commit, or git stash -u, then continue.
Step 2 — fetch. Confirm what users just gotgit fetch origin --tags
git log -1 --oneline v1.5.0
Sample for step 2 — git log -1 --oneline v1.5.0a1b2c3d feat(auth): implement JWT token generation
# That save is the Production sticker. It is already in Git. Do not copy from the server.
Step 3 — no PR comments yet: rebase onto newest maingit rebase origin/main
git push --force-with-lease
# force-with-lease updates the feature branch only — never main
Step 3 — PR already has comments: merge main in. Do not replaygit merge origin/main
git push
Then keep working on the same feature branchgit log --oneline -5
# Should show the Developer’s saves on top of a1b2c3d (and any other main work).
# Next edit: git add path → git commit → git push. Still not on main.
Senior developer Review and merge
Open the PR. Base must say mainIf it does not, send it back. Do not merge.
Wait for a green check, then read the codeChecks tab, or gh pr checks 123.
Click Squash and merge. Delete the branchNot Merge. Not Rebase.
Open Dev and click the ticketActions job Deploy to EC2 (dev) is green. Do not use Staging for this ticket.
On Dev, pick the version QA should testgit log --oneline -5. Copy the first short ID (left column). Sample below.
Name it v1.5.0-rc.1 and publish as a pre-releaseGitHub → Releases → tick Set as a pre-release. Staging updates.
When QA says yes, name that same version v1.5.0Do not pick newest main. Publish (no pre-release tick). A required reviewer clicks Approve.
Step 1 — list saves on Devgit checkout main
git pull origin main
git log --oneline -5
Sample for step 1 — git log --oneline -5 (newest first)a1b2c3d feat(auth): implement JWT token generation
7c8d9e0 fix(auth): handle expired refresh tokens
b2c3d4e feat(settings): add timezone picker
e1f2a3b chore: update composer.lock
4d5e6f7 feat(dashboard): show last login
# Copy the first ID. Here that is a1b2c3d — that is the version on Dev.
Step 2 — tag for Staginggit tag -a v1.5.0-rc.1 a1b2c3d -m "Staging candidate v1.5.0"
git push origin v1.5.0-rc.1
# GitHub → Releases → this tag → tick Pre-release → Publish
Pressing Merge or Rebase instead of Squash and merge
Sending newest main to Production because QA took a long time
Adding extra tickets to Staging “while we are here”
git add . (use the file path)
Force-pushing main
Exception 1 — Developer is on main
When
git status says On branch main and the Developer wants to save a ticket.
Who
Developer
Do this
Make a branch first: git checkout -b feature/TICKET. Then add / save / upload.
Do not
Run git commit while still on main.
Exception 2 — GitHub will not let the Senior developer merge
When
The PR says “Merging is blocked”, or the check is red.
Who
Whoever is looking at the PR (any role). This is not a fourth job.
Do this
Open the Checks tab. Fix the failing Action, or wait for a Senior developer to review.
Do not
Ask a Senior developer to merge anyway. Do not skip the green check.
Exception 3 — Someone merged to main before the Developer’s PR was reviewed
When
The Developer’s PR is still open, and main got new work.
Who
Developer (ask a Senior developer if stuck)
Do this
No comments on the PR yet: Developer replays the branch on top of newest main (paste under Fixes). People already commented: Developer merges main into the feature branch instead. Do not replay.
Do not
Force-push main. Replay only the Developer’s feature branch.
Exception 4 — QA found a bug on Staging
When
Staging is on v1.5.0-rc.1 and QA says it is broken.
Who
Senior developer writes the fix. Release owner names the new copy.
Do this
Senior developer: new PR into main (same as a normal ticket). Then tell the Release owner.
Release owner: name v1.5.0-rc.2 and send that to Staging. Users later get rc.2, not rc.1.
Do not
Send the old rc.1 to Production. Do not pile extra unrelated tickets onto this QA copy.
Exception 4 — Release owner after the fix is on maingit checkout main
git pull origin main
git log --oneline -5
Sample: git log --oneline -5 (newest first)9f8e7d6 fix(auth): correct expiration offset
a1b2c3d feat(auth): implement JWT token generation
7c8d9e0 fix(auth): handle expired refresh tokens
b2c3d4e feat(settings): add timezone picker
e1f2a3b chore: update composer.lock
# Copy the first ID. Here that is 9f8e7d6 — the QA fix. Do not reuse a1b2c3d (rc.1).
Name the new ID for Staginggit tag -a v1.5.0-rc.2 9f8e7d6 -m "Staging candidate v1.5.0 rc.2"
git push origin v1.5.0-rc.2
# GitHub Release from v1.5.0-rc.2, tick Pre-release. Later Production uses rc.2, not rc.1.
Exception 5 — Live users are broken (hotfix)
When
Production is down, andmain already has other unfinished tickets. Example: Production is v1.4.3, Staging is testing v1.5.0-rc.1.
Who
Senior developer writes the fix. Release owner sends it to users.
Do this
Senior developer starts from the live version (v1.4.3), not newest main. The patch is v1.4.4 — not v1.5.1.
Senior developer also opens a PR into main so Dev is not left buggy. Leave Staging on v1.5.0-rc.1.
Do not
Send newest main to Production (that ships unfinished work). Do not rename Staging to v1.5.1.
Exception 6 — Staging and Production are not the same version
When
The Release owner is about to click Approve on Production.
Who
Release owner
Do this
Check GitHub → Actions / Environments. Staging’s short ID and Production’s short ID must match (like both a1b2c3d).
Do not
Approve if the IDs differ. Users would get untested work.
Exception 7 — Rollback to last version
When
The new Production sticker is bad (example: v1.5.0 just went live and users are broken). The last good Production sticker still exists (example: v1.4.4).
Who
Release owner rolls Production back. Senior developer later fixes the bad release with a normal PR.
Do this
Put users back on the last good Production sticker. That is the same save as before, not newest main.
First choice: GitHub → Actions → last green Deploy to EC2 (prod) for that old sticker → Re-run jobs → Approve.
If Re-run is gone: new sticker on that same save ID (paste under Fixes). Leave Staging where it is.
Do not
Send newest main to Production. Do not write a hotfix unless last good is not good enough (that is exception 5). Do not delete tags.
Exception 8 — Production shipped, Developer’s feature branch is not finished
When
The Release owner just published a Production sticker (example: v1.5.0). The Developer is still on an unfinished feature branch.
Who
Developer
Do this
Stay on the feature branch. Fetch. Put newest main into that branch, then keep working (paste under Commands).
If Production was a hotfix, wait until that fix is on main (Exception 5), then do this.
Do not
Check out the Production sticker and keep coding. Do not copy files from the Production server. Do not git commit on main.
Page 4 · Hotfix, rollback, and Git errors
Hotfix, rollback, and Git errors
Production users broken? Use the chooser, then paste. A Git error? Use the table. If anyone is lost, stop and ping a Senior developer — do not guess with --force on main.
Finish with git add + git commit, or git stash -u now and git stash pop later.
On branch main when the Developer meant to save a feature
Exception 1. The Developer is on the shared branch.
Do not save. git checkout -b feature/TICKET first.
rejected (non-fast-forward)
GitHub has work the laptop does not, or the Developer replayed after already uploading.
The feature branch only: git push --force-with-lease. Never force-push main.
GitHub: “Merging is blocked”
Exception 2. Checks are red, or a review is missing.
Open the Checks tab. Fix it, or wait for a Senior developer.
CONFLICT
The same lines changed in two places.
Ask a Senior developer: edit files, remove <<<<<<<, git add, then continue.
pathspec … did not match
That branch or version name is not on the laptop yet.
git fetch origin --tags and try again.
Developer Exception 3 — replay the branch
Only if nobody has commented on the PR yet.
Developer: replay the feature branch on top of newest maingit fetch origin
git rebase origin/main
git push --force-with-lease
# force-with-lease updates the feature branch only — never main
If people already commented: git fetch origin && git merge origin/main && git push. Do not replay.
Conflict while replaying (Senior developer helps)git status
# Edit files, remove <<<<<<< markers, then:
git add path/to/file
git rebase --continue
git push --force-with-lease
Senior developer Exception 5 — Production hotfix
Start from the live version. Example: Production v1.4.3 → patch v1.4.4.
Senior developer — write the fix from the live versiongit fetch origin --tags
git checkout -b hotfix/JIRA-999-db-leak v1.4.3
git add src/db/pool.ts
git commit -m "fix(db): resolve connection pool leak"
git push -u origin hotfix/JIRA-999-db-leak
gh pr create --base main --title "fix(db): resolve connection pool leak"
# Squash and merge into main so Dev also has the fix
# Copy the fix save ID and send it to the Release owner
Release owner — copy that one fix onto the live version, then usersgit fetch origin --tags
git checkout v1.4.3
git cherry-pick PASTE_THE_FIX_COMMIT_ID
# cherry-pick = copy that one save, without unfinished tickets
git tag -a v1.4.4 -m "Hotfix v1.4.4"
git push origin v1.4.4
# GitHub → Releases → publish v1.4.4 → Approve production
Release owner Exception 7 — Rollback to last version
Example: bad Production is v1.5.0. Last good Production is v1.4.4. Skip names with -rc — those are Staging only.
Release owner — find the last good Production stickergit fetch origin --tags
git tag --list "v*" --sort=-v:refname
Sample: git tag --list (newest name first)v1.5.0
v1.5.0-rc.1
v1.4.4
v1.4.3
v1.4.2
# Bad live sticker: v1.5.0. Last good Production: v1.4.4 (not the rc).
Confirm that sticker’s save IDgit log -1 --oneline v1.4.4
Sample: git log -1 --oneline v1.4.4c0ffee1 fix(db): resolve connection pool leak
# Users must get this save again — not newest main.
First choice — no new sticker. Re-run the last good Production deploy# GitHub → Actions
# Find the last green job: Deploy to EC2 (prod) for v1.4.4
# Re-run jobs → Approve the production environment
If Re-run is gone — new sticker on the same save as v1.4.4git fetch origin --tags
git log -1 --oneline v1.4.4
git tag -a v1.4.5 c0ffee1 -m "Rollback: restore v1.4.4"
git push origin v1.4.5
# GitHub → Releases → publish v1.4.5 (do not tick Pre-release) → Approve
# Staging can stay on v1.5.0-rc.1. Senior developer still fixes v1.5.0 on main.
FAQ · By role
FAQ by role
Find the role heading. Open the question. Paste blocks are under Commands.
Unusual cases are under Exceptions.
Developer
Can the Developer commit on main?
No. main is the shared branch. Make a feature branch first, then git add / git commit / git push. If git status says On branch main, that is Exception 1.
What does the Developer name the branch?
feature/JIRA-101-short-name. Base of the Pull Request must be main.
Why not git add .?
Secrets and junk files can sneak in. Type the file path, like git add src/auth/login.ts.
Is Preview on the Developer’s computer?
No. The Developer writes code on their own computer. That is local. Preview is a website for this Pull Request. GitHub’s robot builds it after the Developer pushes the branch. Open the Preview link on the PR. It is safe to break, and it disappears when the PR is merged or closed.
Preview or Dev — which one does the Developer use?
Preview is a website for this Pull Request. Dev is newest main after a Senior developer squash-merges. The Developer stops after the PR. The Developer does not send work to Staging or Production.
When does the Developer stop?
After the Pull Request is open and Preview is checked. Then wait for a Senior developer. Do not squash-merge. Do not tag a release.
Production just shipped, and the feature branch is not finished. What now?
Stay on the feature branch. Fetch. Put newest main into it, then keep working. Do not check out the Production sticker. Do not copy files from the Production server. That is Exception 8. Paste under Commands.
Someone merged to main before this PR was reviewed. Replay or merge?
No comments on the PR yet: git rebase origin/main, then git push --force-with-lease on the feature branch only. Comments already exist: git merge origin/main. Do not rebase. Do not force-push main. That is Exception 3.
Git says Please commit your changes or stash them.
Unsaved edits are blocking a branch switch. Finish with git add + git commit, or git stash -u now and git stash pop later. See Fixes.
Senior developer
Which merge button?
Only Squash and merge. Not Merge. Not Rebase. Then delete the feature branch. Dev updates by itself.
The PR says “Merging is blocked” or the check is red.
Do not merge. Open the Checks tab. Fix the failing Action, or wait. Do not skip the green check. That is Exception 2.
Should the Senior developer open Staging to test this ticket?
No. After squash-merge, open Dev. Staging is the QA copy of a named save, later, by the Release owner.
QA found a bug on Staging. What does the Senior developer do?
Open a normal PR into main (same as any ticket). Tell the Release owner. The Release owner names v1.5.0-rc.2 for Staging. Users later get rc.2, not rc.1. Do not pile extra tickets onto that QA copy. That is Exception 4.
Production users are broken. Hotfix or rollback?
Last good Production is still good enough: Release owner rolls back (Exception 7). Last good is not enough: Senior developer writes a hotfix from the live sticker, not newest main. Example: live v1.4.3 → patch v1.4.4, not v1.5.1. Also open a PR into main so Dev is not left buggy. That is Exception 5.
A Developer is stuck on replay or a conflict.
Help on the feature branch only. Edit files, remove <<<<<<< markers, git add, then continue. Never force-push main. Paste under Fixes.
Release owner
What is a sticker?
A version name on one save, like v1.5.0 or v1.5.0-rc.1. Git calls that a tag. A GitHub Release is how the robot sees the sticker. The short ID (like a1b2c3d) is the save it points at.
May the Release owner send newest main to Staging or Production?
No. That ships unfinished tickets that landed on Dev after QA started. Pick one short ID from Dev, name it, and send that save. Paste under Commands.
What is the difference between v1.5.0-rc.1 and v1.5.0?
v1.5.0-rc.1 is a pre-release sticker. Staging (QA) gets it and stays still. v1.5.0 is the same save for users. Publish with no pre-release tick. A required reviewer clicks Approve. Production updates.
Staging’s short ID and Production’s short ID do not match.
Do not Approve. Users would get untested work. Both must be the same ID, like both a1b2c3d. That is Exception 6.
QA said Staging is broken.
Do not send rc.1 to Production. Wait for the Senior developer’s fix on main, then name v1.5.0-rc.2. Users later get rc.2. That is Exception 4.
Production is down, and main already has unfinished tickets. What version?
Patch the live sticker. Example: Production v1.4.3, Staging testing v1.5.0-rc.1 → hotfix is v1.4.4, not v1.5.1. Leave Staging on rc.1. Do not send newest main to users. That is Exception 5.
The new Production sticker is bad. Last good still exists.
Put users back on the last good Production sticker (same save, not newest main). First: GitHub → Actions → last green Deploy to EC2 (prod) for that old sticker → Re-run jobs → Approve. If Re-run is gone: new sticker on that same save ID. Leave Staging where it is. Do not delete tags. That is Exception 7.
May anyone copy files onto a server from a laptop?
No. GitHub’s robot copies code to Preview, Dev, Staging, and Production. There is no Git branch called develop or staging.