Git Workflows — Gitflow, Trunk-based & Team Patterns
Why Workflows Matter
By now you know every Git command you need. But knowing commands is not the same as knowing how to use them effectively in a team. A workflow defines:
- What branches exist and what their purpose is
- Where completed work lands (and through what process)
- When and how to tag releases
- What rules govern commit messages
- How to handle hotfixes, releases, and long-running features
A good workflow reduces the cognitive overhead of collaboration, keeps the main branch deployable, and makes your history readable to humans and automated tools alike.
This lesson covers the three workflows used by the majority of professional engineering teams, then covers the supporting practices that make any workflow healthier.
Workflow 1: Gitflow
Gitflow was described by Vincent Driessen in 2010 and became widely adopted over the following decade. It is most appropriate for projects with scheduled releases and the need to maintain multiple active versions.
The Branch Structure
Gitflow defines five types of branches:
main (or master) — Contains production-ready code. Every commit on main is a release. Tagged with version numbers.
develop — The integration branch. Features are merged here and tested together. Reflects the next release in development.
feature/* — One branch per feature. Branched from develop, merged back to develop. Named feature/user-auth, feature/payment-api, etc.
release/* — Created from develop when a release is being prepared. Only bug fixes go here (no new features). When ready, merged to both main (tagged) and develop.
hotfix/* — Created from main to fix critical production bugs. Merged to both main (tagged with a patch version) and develop (or current release branch).
Gitflow in Practice
When to Use Gitflow
Gitflow works well when:
- You have scheduled, versioned releases (e.g., quarterly releases, mobile apps)
- You need to maintain multiple active versions simultaneously
- You have a QA or release stabilization phase before shipping
Gitflow works poorly when:
- You deploy continuously (multiple times per day)
- Your team is small (2-5 developers) — the overhead of
developandreleasebranches adds complexity without proportional value - You do not maintain multiple versions — the release branch machinery becomes pointless
Workflow 2: GitHub Flow
GitHub Flow was described by Scott Chacon in 2011 and is the default workflow for most teams using GitHub. It is intentionally simple.
The Rules
mainis always deployable. Every commit onmainworks and can be shipped.- Create a descriptively named branch for every feature, fix, or change.
- Push to the branch regularly. Create a Pull Request early (even as a "draft" PR for work in progress).
- Ask for review and discuss. The PR is the code review mechanism.
- Merge after CI passes and the PR is approved.
- Deploy immediately after merging. (Or: merging to main triggers automatic deployment.)
The Pull Request Workflow
Merge Strategies for PRs
When merging a PR, you have three options on GitHub/GitLab:
Merge commit (git merge --no-ff): Preserves the full branch history and creates a merge commit. Every commit from the feature branch appears in main's history. Best when the individual commits on the branch are meaningful.
Squash and merge: Combines all commits from the branch into a single commit on main. Produces the cleanest main history. Individual "WIP" and "fix typo" commits disappear — only the final, curated commit lands on main. Best for teams that want main to read as a clean changelog.
Rebase and merge: Replays the branch's commits linearly onto main without a merge commit. Produces a linear history. Each commit from the branch lands individually on main (unlike squash).
Most teams using GitHub Flow use squash and merge: they encourage messy commits during development (no pressure to keep every WIP commit clean) and end up with a clean, readable main history.
When to Use GitHub Flow
GitHub Flow works well when:
- You deploy continuously or frequently
- You have a single production version at any time
- Your team values simplicity over ceremony
- You use a CI/CD pipeline (GitHub Actions, CircleCI, etc.)
GitHub Flow is the recommended starting point for new teams. You can always add complexity (release branches, etc.) if you genuinely need it.
Workflow 3: Trunk-Based Development
Trunk-based development (TBD) is the practice of merging to main (the "trunk") as frequently as possible — ideally multiple times per day per developer. It is the approach used by large tech companies (Google, Facebook, Netflix) for their core codebases.
Core Principles
- One main branch (the trunk). No long-lived feature branches.
- Commit to trunk at least once per day (or multiple times).
- Feature flags hide incomplete features from users until they are ready.
- Short-lived branches (if used at all) exist for hours or a day, not weeks.
Feature Flags
The key enabler for TBD is feature flags (also called feature toggles). Incomplete or experimental code is committed to main behind a flag that is off in production:
The flag is in configuration (an environment variable, a config file, or a feature flag service). Developers can test with the flag enabled; users see the old behavior. When the feature is ready, flip the flag in production. No deployment needed for the feature itself.
Short-Lived Feature Branches in TBD
Some teams practicing TBD use very short-lived feature branches (sometimes called "branch for review") — they branch, develop for at most a day, open a PR, get it reviewed, and merge. The key difference from GitHub Flow is the timescale: hours or one day, not days or weeks.
When to Use Trunk-Based Development
TBD is best when:
- You have a strong CI/CD pipeline that gates merges on tests passing
- Your team is disciplined about small, focused commits
- You have or can build a feature flagging system
- You deploy to production multiple times per day
TBD is hard when:
- Your tests are slow (a 45-minute test suite makes frequent integration painful)
- You do not have a feature flag system and features take weeks to build
- Your team is not yet disciplined about keeping
maingreen
Commit Message Conventions: Conventional Commits
Conventional Commits is a specification for human- and machine-readable commit messages. It enables automated changelog generation, automatic semantic version bumping, and better git log readability.
The Format
Commit Types
| Type | When to Use |
|------|-------------|
| feat | A new feature (triggers MINOR version bump) |
| fix | A bug fix (triggers PATCH version bump) |
| docs | Documentation changes only |
| style | Code formatting, no logic changes |
| refactor | Code restructuring, no behavior change |
| perf | Performance improvements |
| test | Adding or fixing tests |
| build | Build system or dependency changes |
| ci | CI/CD configuration changes |
| chore | Maintenance tasks, dependency updates |
| revert | Reverts a previous commit |
Breaking changes are denoted with a ! after the type/scope or with a BREAKING CHANGE: footer (triggers MAJOR version bump):
or:
Examples
Tools That Use Conventional Commits
- semantic-release: Automatically determines the next version number and generates changelogs
- commitlint: Lints commit messages in CI to enforce the convention
- standard-version: Automates version bumping and changelog generation
- Changelog generators: Many CI/CD tools can auto-generate changelogs from Conventional Commits
Git Hooks: Automating Quality Gates
Git hooks are scripts that run automatically at specific points in Git's workflow. They live in .git/hooks/ and are executed by Git when the triggering event occurs.
Hooks are local to each developer's machine and are not committed to the repository by default (since .git/ is not tracked). To share hooks with your team, use a tool like Husky (for Node.js projects) or store hook scripts in a directory and ask developers to symlink them.
Client-Side Hooks
pre-commit: Runs before the commit message is entered. Common uses: run tests, run linting, check for sensitive data.
Make it executable: chmod +x .git/hooks/pre-commit
commit-msg: Runs after the commit message is written. Used to validate commit message format.
pre-push: Runs before git push. Common uses: run the full test suite, prevent pushing to certain branches.
prepare-commit-msg: Runs before the commit message editor opens. Common use: pre-fill the commit message template.
Sharing Hooks with Your Team
Since .git/hooks/ is not committed, use one of these approaches:
Monorepo vs Polyrepo
How you organize code across repositories is an architectural decision with Git implications.
Polyrepo (Multiple Repositories)
Each project, service, or library has its own repository.
Advantages:
- Clear ownership and access control per team
- Simpler Git history per repository
- Teams can move independently
- Smaller repositories are faster to clone
Disadvantages:
- Cross-repository changes require coordinated PRs
- Dependency management between packages is complex
- Harder to see the impact of changes across the system
- Code sharing requires publishing packages
Monorepo (Single Repository)
All related projects live in one repository.
Advantages:
- Atomic cross-package changes in a single commit
- Unified CI/CD pipeline and tooling
- Easier code sharing (no publishing overhead)
- Single source of truth for all related code
Disadvantages:
- Requires tooling (Turborepo, Nx, Bazel) for selective builds/tests
- Git operations can be slow on very large monorepos (thousands of files)
- Access control is coarser (harder to restrict access to specific parts)
Practical Guidance
For small teams (1-10 developers) building a cohesive system: monorepo is simpler. The coordination overhead of polyrepo often outweighs the benefits at small scale.
For large organizations with independent teams: polyrepo per team/service often works better, with a shared internal package registry for common libraries.
Many companies have separate strategies for different concerns: a monorepo for all services (backend, frontend, infrastructure), plus separate repos for open-source components they maintain publicly.
Team Rules for a Healthy Git History
Here are the rules that most successful engineering teams converge on:
Protected Branches
Mandatory Code Review
Every commit to main goes through a PR with at least one approval. This is not about distrust — it is about catching mistakes, sharing knowledge, and maintaining consistent standards.
CI Must Pass Before Merge
Tests, linting, type checking — all must pass on the feature branch before merge. This keeps main green. A broken main is the number one productivity killer in collaborative development.
Small PRs
PRs under 400 lines of diff get reviewed faster and more thoroughly. Large PRs (1000+ lines) get rubber-stamped because reviewers cannot process them. Break large changes into a sequence of smaller, logically independent PRs.
Descriptive PR Descriptions
Every PR should have:
- A summary of what it does (and why — not just what, which is visible in the diff)
- A link to the issue or ticket it addresses
- Test instructions if manual testing is needed
- Screenshots for UI changes
Choosing the Right Workflow
Use this decision guide:
A Practical Git Policy Template
Here is a starting policy you can adapt for your team:
Practical Exercises
Exercise 1: Simulate GitHub Flow
Exercise 2: Set Up commit-msg Hook
Exercise 3: Practice Gitflow
Challenge: Document Your Workflow
Write a CONTRIBUTING.md file for the taskr project that documents:
- How to set up the development environment
- The branching strategy
- Commit message conventions
- PR process and requirements
- How to create a release
This file will serve as the Git policy for anyone contributing to taskr.
Course Summary
You have now covered the complete Git skill stack, from first principles to professional practice.
Foundation (Lessons 1-3): You understand what Git is and why it exists, how it stores data as snapshots in a content-addressable object database, how to create repositories and make commits that tell a meaningful story, and how branches are simply lightweight pointers that enable parallel development at no cost.
Power Tools (Lessons 4-7): You can confidently undo any kind of mistake — from unstaged changes to shared history — using the right tool for each context. You can collaborate with remote repositories, push and pull changes, and authenticate securely. You can resolve merge conflicts without panic, reading the markers and making informed decisions. You can rewrite history with rebase and interactive rebase to produce clean, professional commits, and apply specific changes with cherry-pick.
Professional Layer (Lessons 8-10): You can mark releases with annotated tags and apply semantic versioning. You understand how Git's internals work — the object database, the DAG, branches as files — which makes you a better Git user when things get unusual. And you can design a team workflow, choose between Gitflow, GitHub Flow, and trunk-based development, enforce conventions with hooks, and write a coherent Git policy.
The next step is practice. Take these tools into a real project. Build the habits: descriptive commit messages, short-lived branches, small PRs, regular syncing with main. The theory lives in the lessons; the skill lives in the repetition.
Git is a lifelong tool. The commands you have learned here will serve you for your entire career. Use them well.