open atlas
↑ Back to track
Git, from zero to senior GIT · 10 · 01

A day in a real team

The full daily loop on a trunk-based team, end to end: branch off main, make atomic commits, rebase onto origin/main to stay current, push and open a PR, address review with fixup and force-with-lease, then squash-merge and sync. Every earlier unit clicks into one workflow.

GIT Senior ◷ 18 min
Level
FoundationsJuniorMiddleSenior

You have learned branches, remotes, rebase, reflog, and signing as separate skills. On a real team they are not separate — they are one continuous loop you run several times a day, mostly without thinking. The loop is short-lived branch, stay current, ship, repeat. A senior runs it so smoothly that the git commands disappear and only the work remains.

By the end of this lesson you will be able to walk the entire daily cycle of a trunk-based / GitHub-flow team — from git switch -c to a squash-merge and back to a clean main — and explain why each step keeps the branch short-lived and conflict-free.

Goal

After this lesson you can run the full GitHub-flow loop from memory: branch off main, commit atomically, rebase onto origin/main to stay current, push and open a PR, revise review feedback with --fixup and --force-with-lease, and squash-merge then sync — explaining the purpose of each step.

1

Start every unit of work from a fresh, up-to-date branch off main. Never build on a stale base. Fetch first, then cut the branch directly from the remote’s main so you start exactly where production is:

git switch main
git pull --ff-only            # fast-forward main to origin/main, refuse if diverged
git switch -c feat/checkout-coupons origin/main

--ff-only is the safety belt: if your local main somehow has commits that origin/main does not, the pull refuses instead of silently creating a merge. A branch named for its intent (feat/checkout-coupons) is what the PR and the eventual squash commit will inherit.

2

Do the work as small, atomic commits — each one a coherent, revertable step. A commit should build and tell one story. Stage deliberately and write a real message:

git add -p                    # stage hunks, not the whole tree blindly
git commit -m "Validate coupon code before applying discount"
# ... more work ...
git commit -m "Add expired-coupon error path + test"

Atomic commits are not bureaucracy: they make git revert, git bisect, and code review possible. A reviewer reads a sequence of clear steps; a single 600-line “wip” commit is unreviewable.

3

Stay current by rebasing onto origin/main — do not let the branch drift. While you work, teammates merge to main. A branch that diverges for days becomes a merge nightmare. Pull their work under yours so your commits always sit on top of the latest trunk:

git fetch origin
git rebase origin/main        # replay your commits on top of the newest main
# resolve any conflicts now, while they are small, then:
git rebase --continue

Rebasing keeps history linear and surfaces conflicts early and in small pieces, instead of one giant collision at merge time. Do this at least daily on a living branch.

Why this works

Why rebase the feature branch instead of merging main into it? Both integrate the latest trunk, but a merge littered with “Merge branch ‘main’ into feat/x” commits makes the branch history noisy and the eventual diff hard to read. Rebase produces a clean stack of just your commits on top of current main — exactly what a reviewer wants to see, and exactly what a squash-merge collapses cleanly.

4

Push, open a PR, and revise review feedback with fixup + force-with-lease. Publish the branch, open the pull request, and let CI run. When review lands, do not pile “address comments” commits on top — fold fixes into the commit they belong to:

git push -u origin feat/checkout-coupons      # first publish, sets upstream
# review asks for a change to the validation commit:
git add -p
git commit --fixup=<sha-of-validation-commit>
git rebase -i --autosquash origin/main        # autosquash slots the fixup in
git push --force-with-lease                    # safe force: refuses if remote moved

--force-with-lease is the non-negotiable detail: a plain --force would clobber any commit a teammate pushed to your branch since you last fetched. --force-with-lease checks that the remote is still where you think it is and aborts otherwise.

5

Once CI is green and review approves, squash-merge and return to a clean main. Most trunk-based teams squash the branch into a single commit on main — the branch’s messy intermediate steps collapse into one clean entry titled after the PR. Then sync and prune:

# merge happens via the PR "Squash and merge" button (or gh pr merge --squash)
git switch main
git pull --ff-only            # bring the new squash commit into local main
git branch -d feat/checkout-coupons       # delete local branch (merged)
git push origin --delete feat/checkout-coupons   # delete the remote branch

The loop is closed: main holds one tidy commit, no branches dangle, and you are positioned to cut the next feature from a fresh base.

Worked example

Wednesday morning, one feature, start to finish.

You pick up “coupons at checkout.” You sync and branch: git switch main && git pull --ff-only && git switch -c feat/checkout-coupons origin/main. Over the morning you land two atomic commits — validation, then the expired-coupon path with a test. Before lunch a teammate merges a refactor of the cart module to main. You run git fetch origin && git rebase origin/main; one small conflict in cart.ts, resolved in two minutes because it is fresh.

You push with -u and open the PR. CI passes; a reviewer asks you to tighten the validation regex. Instead of a new commit, you git commit --fixup=<validation sha>, git rebase -i --autosquash origin/main, and git push --force-with-lease. The PR now shows two clean commits, the fix folded into the right one. Approval comes; you click “Squash and merge.” Back on your machine: git switch main && git pull --ff-only && git branch -d feat/checkout-coupons. main has one new commit, “Coupons at checkout (#812),” and your tree is clean for the next task. Total branch lifetime: under a day.

Common mistake

The classic failure is the long-lived branch. A feature that lives for two weeks without rebasing drifts so far from main that the eventual merge is a multi-hour conflict marathon, and the diff is too big to review honestly. The fix is discipline, not tooling: keep branches small, rebase onto origin/main daily, and merge within a day or two. Short-lived branches are the whole point of trunk-based development.

Check yourself
Quiz

During code review you need to amend an earlier commit on your pushed feature branch and update the remote. Which final push is correct and safe?

Recap

The daily loop of a trunk-based team is one continuous cycle: branch off a fresh origin/main, make atomic commits, rebase onto origin/main to stay current, push and open a PR, revise review feedback with --fixup plus --force-with-lease, and finally squash-merge and sync back to a clean main. Every earlier unit — branches, remotes, rebase, force-safety — clicks into this one workflow, and the discipline that holds it together is keeping the branch short-lived and continuously rebased. Next you will run the same machinery under pressure, when a bad merge has turned main red.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.