nanda mochammad
Engineering Notes

Git workflow for teams: the daily habits a shared repo runs on

6 min read
Tagged Git

A branching strategy is a map; a workflow is how you actually walk it. Two teams can both say “we use GitHub Flow” and have completely different days. One ships calmly, the other drowns in merge conflicts, half-finished reviews, and a commit log nobody can read six months later.

The difference is rarely the tooling. It’s a handful of habits, repeated. Here’s the loop I teach every engineer I mentor, from the moment they branch to the moment their work lands on main.

Step 01: Start clean

Branch from an up-to-date main, name it for a human

Half of all conflicts are born at the very first command, when someone branches off a stale main. Pull first, always.

git switch main
git pull --ff-only

git switch -c feat/transfer-retry

Use a short, conventional prefix so anyone scanning the branch list knows what they’re looking at: feat/… for features, fix/… for bug fixes, chore/… for housekeeping. Keep the branch scoped to one thing. “Add transfer retry” is a branch. “Q3 improvements” is a swamp.

Step 02: Commit so the log explains itself

Write commits for the person debugging this at 2am

That person is usually you. A commit message has two jobs: say what changed in the subject, and why in the body. The diff already shows the how.

Conventional Commits give this a small, machine-readable grammar, type(scope): subject, which also powers automatic changelogs and version bumps.

feat(payments): retry failed top-ups with exponential backoff

A flaky gateway was dropping ~2% of top-ups on the first attempt.
Retries now back off 1s/2s/4s before surfacing an error to the user.

Closes #482

Common types: feat, fix, refactor, chore, docs, test. Keep the subject under about 50 characters, in the imperative mood (“add”, not “added”).

Step 03: Open a reviewable pull request

Small, described, and green before you ask for eyes

A pull request is a request for someone’s time. Respect it by making the PR easy to say yes to: keep it small (a few hundred lines beats a few thousand), write a description that explains the why and how to test it, and make sure CI is green before you tag a reviewer.

Diagram: the pull request lifecycle: branch off main, make small commits, open a PR, pass review and CI, then merge into main. Branch off latest main Commits small, scoped Open PR describe the why Review + CI people + checks Merge into main
The pull request loop. Each handoff is a chance to keep changes small and the main branch always releasable: branch, commit, open the PR, pass review and automated checks, then merge.

A good PR description answers three questions in a few lines: what does this change, why now, and how did you verify it? Screenshots for UI, a one-line repro for bug fixes. The reviewer should never have to reverse-engineer your intent from the diff.

Step 04: Review with care, on both sides

Review the code, support the coder

Code review is one of the highest-leverage habits a team has. It’s where bugs die young and where knowledge spreads. It only works if both sides play their part.

  • As the author: keep it small, respond to every comment (even a “done”), and don’t take feedback on the code as feedback on you.
  • As the reviewer: be timely. A PR sitting unreviewed for two days blocks a person. Comment on what matters, and distinguish “this is a bug” from “I’d prefer”. Approve when it’s good enough to ship, not when it’s how you’d have written it.
Step 05: Stay in sync without rewriting shared history

Update your branch with rebase; never rebase what others have pulled

While your PR is open, main keeps moving. Pull those changes into your branch before the gap grows. Rebasing replays your commits on top of the latest main, keeping history linear and conflicts small:

git switch feat/transfer-retry
git fetch origin
git rebase origin/main
# resolve any conflicts, then update the PR safely
git push --force-with-lease

Note --force-with-lease, not --force. It refuses to clobber the remote if someone else pushed in the meantime. That safety catch matters.

Step 06: Land it

Merge commit, squash, or rebase: pick on purpose

How a PR lands shapes your history for good. Three options, three different shapes:

Diagram: three ways a pull request lands: a merge commit keeps full history, a squash collapses the branch into one commit, and a rebase replays commits onto a linear history. Merge commit merge node Keeps full history Squash 1 commit One tidy commit per PR Rebase replayed Linear, no merge node
The same pull request, landed three ways. A merge commit preserves the branch and adds a join node; a squash collapses the whole PR into one commit; a rebase replays commits onto a straight line with no merge node.

Most product teams I’ve worked with standardise on squash merge: each PR becomes exactly one tidy commit on main, the messy WIP history disappears, and reverting a feature is a single git revert. Choose merge commits when the individual commits are meaningful and worth preserving. Choose rebase-merge when you want a strictly linear history and your commits are already atomic.

 Merge commitSquashRebase merge
History shapeBranch preserved + merge nodeOne commit per PRLinear, no merge node
Easy to revert?The whole merge, yesYes, one commitPer commit
Log readabilityShows true branchingCleanest summaryClean, detailed
Best when…Branch context mattersDefault for most teamsCommits are already atomic

Whatever you pick, enforce it with branch protection: no direct pushes to main, required reviews, and required green CI. The rule that can be bypassed at 5pm on a Friday is the rule that will be.

How should this PR land?

Choosing a merge style

Branch has messy WIP commitsjust want the outcome→ Squash(safe default)
Every commit is atomicwant a linear log→ Rebase merge
The branch’s shape is the storypreserve context→ Merge commit

None of this is about Git trivia. It’s about keeping changes small, history honest, and your teammates unblocked. Get these six habits into muscle memory and the tool disappears, which is the point.

Not sure which branching model these habits sit inside? Start with Git branching strategies that scale.

Cited sources