Git workflow for teams: the daily habits a shared repo runs on
Welcome back. Today we look at the daily Git habits that keep a shared repo free of weekly merge conflicts and a commit log nobody can read.
By the end of this article, you will know the six habits that take a change from a fresh branch to a clean landing on main: starting from an up-to-date branch, writing commits that explain themselves, opening small pull requests, reviewing well on both sides, staying in sync with rebase, and picking a merge style on purpose.
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 a handful of habits, repeated, more than the tooling. Here is the loop I teach every engineer I mentor, from the moment they branch to the moment their work lands on main.
Branch from an up-to-date main, name it for a human
Half of all conflicts start at the very first command. Someone branches off an old main. Pull first, always.
git switch main
git pull --ff-only
git switch -c feat/transfer-retry
Use a short, common prefix so anyone scanning the branch list knows what they are looking at: feat/… for features, fix/… for bug fixes, chore/… for housekeeping. Keep the branch about one thing. “Add transfer retry” is a branch. “Q3 improvements” is a swamp.
Write commits for the person debugging this at 2am
That person is usually you. A commit message has two jobs. The subject says what changed. The body says why. The diff already shows the how.
Conventional Commits give this a small format that tools can read: type(scope): subject. That same format 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. Write it as a command (“add”, not “added”).
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. Make sure CI is green before you tag a reviewer.
A good PR description answers three questions in a few lines: what does this change, why now, and how did you verify it? Add screenshots for UI work. Add a one-line way to reproduce the bug for bug fixes. The reviewer should never have to guess your intent from the diff.
Review the code, support the coder
Code review is one of the most useful habits a team has. It is 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 quick. A PR sitting unreviewed for two days blocks a person. Comment on what matters, and separate “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.
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. That keeps history in a straight line and keeps 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 overwrite the remote if someone else pushed in the meantime.
Merge commit, squash, or rebase: pick on purpose
How a PR lands shapes your history for good. Three options, three different shapes:
Most product teams I’ve worked with settle on squash merge. Each PR becomes exactly one tidy commit on main. The messy WIP history disappears. Reverting a feature is a single git revert. Choose merge commits when the individual commits are meaningful and worth keeping. Choose rebase-merge when you want a strictly straight-line history and your commits are already atomic.
| Merge commit | Squash | Rebase merge | |
|---|---|---|---|
| History shape | Branch preserved + merge node | One commit per PR | Linear, no merge node |
| Easy to revert? | The whole merge, yes | Yes, one commit | Per commit |
| Log readability | Shows true branching | Cleanest summary | Clean, detailed |
| Best when… | Branch context matters | Default for most teams | Commits 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 skipped at 5pm on a Friday is the rule that will be.
How should this PR land?
Choosing a merge style
Recap
The goal was to give you six habits that take a change from a fresh branch to a clean landing on main. Together they keep changes small, history honest, and your teammates unblocked.
- Branch from an up-to-date
main, and keep each branch about one thing. - Write commits where the subject says what changed and the body says why.
- Keep pull requests small, described, and green before you ask for review.
- Review quickly and kindly, on both sides.
- Rebase your own branch to stay in sync, but never rebase a branch others have pulled. Then pick squash, merge commit, or rebase merge on purpose and lock it in with branch protection.
Not sure which branching model these habits sit inside? Start with Git branching strategies that scale.
I hope this writing helps you. Thanks for reading.
Cited sources