Most developers know the basics of Git. Fewer have thought carefully about how to structure a branching workflow for a team in a way that reduces conflicts, supports deployment, and maintains a readable history.
The goal of a workflow
A good Git workflow:
- Makes it easy to integrate changes from multiple developers without conflict
- Supports independent deployment of features when needed
- Keeps the main branch in a deployable state
- Makes it possible to understand why changes were made from the commit history
Different teams have different deployment models and risk tolerances, so there is no single correct workflow. But there are approaches that have proven robust.
Trunk-based development
Trunk-based development (TBD) is the workflow most commonly associated with high-performing engineering teams. The core idea: everyone commits to a single main branch (trunk) frequently — at least once per day.
Features that are not ready for release are hidden behind feature flags, not kept on long-lived branches. Branches, when used at all, are very short-lived (hours to a couple of days at most) and are immediately merged.
Why it works: Frequent merges prevent the divergence that makes large merges painful. The main branch stays close to production. Problems are discovered early.
What it requires: Strong automated testing (you cannot merge to main frequently if broken code causes problems), feature flags for in-progress work, and a team culture that is comfortable with frequent, small commits.
Gitflow
Gitflow defines several long-lived branches:
main: Represents productiondevelop: Integration branch for features in progressfeature/*: Individual feature branches, branched fromdeveloprelease/*: Preparation for a releasehotfix/*: Emergency fixes for production
Changes flow: feature → develop → release → main.
Why it exists: Gitflow was designed for teams with regular scheduled releases (versioned software, apps with release cycles). The release branch allows final stabilization without blocking new feature work.
Where it breaks down: For teams deploying continuously, the overhead of the develop/release structure adds process without benefit. Long-lived feature branches create merge conflicts and delay integration.
Choosing an approach
For continuous delivery teams (SaaS, web services that deploy frequently): Trunk-based development with feature flags is almost always the right choice. Short-lived branches with PRs, merged daily.
For teams with fixed release cycles (mobile apps, client-installed software, APIs with versioned contracts): A simpler version of Gitflow — main + release branches, without the develop branch — often makes more sense.
For open source projects: A main branch with contributor forks and PRs is standard. Long-lived branches may exist for major version maintenance.
Pull requests as a review gate
Regardless of workflow, most teams use pull requests (PRs) as a review mechanism before merging to main. A good PR:
- Is small — easier to review thoroughly, less likely to conflict
- Has a clear description of what and why
- Includes tests for new behavior
- Is linked to a ticket or issue for context
The review is about the code, not the person. Focus on correctness, edge cases, and maintainability — not style (that's what linters are for).
Commit message conventions
Commit messages are documentation. Future developers (including yourself) will use them to understand why changes were made.
Conventional Commits is a widely adopted specification:
type(scope): description
feat(auth): add OAuth2 login support
fix(api): correct pagination off-by-one error
docs: update API reference for rate limits
refactor(db): extract query builder into separate module
Types: feat, fix, docs, refactor, test, chore, perf
This convention enables automated changelog generation and makes the history scannable.
Managing merge conflicts
Merge conflicts are inevitable on teams. Strategies to reduce them:
- Small, frequent commits: Reduces divergence
- Clear ownership: Minimize situations where multiple developers modify the same files
- Componentization: Well-separated modules conflict less than a large monolith
When conflicts occur, resolve them carefully — understand both changes before deciding how to merge them.
Rewriting history
git rebase and git commit --amend rewrite history. Rules:
- On local branches you have not pushed: Rebase freely to clean up history
- On pushed branches: Rebase only with team agreement — it rewrites the history that others may have pulled
- On shared/main branches: Never force-push
Interactive rebase (git rebase -i) is valuable for cleaning up a messy local branch before submitting a PR — squashing work-in-progress commits, reordering for clarity.
Summary
Trunk-based development with short-lived branches is the most effective workflow for teams deploying frequently. Gitflow is appropriate for teams with fixed release cycles. Pull requests provide a review gate and should be kept small. Conventional commits make the history searchable and enable automation. Rebase local branches freely; be careful about rewriting shared history.