Why Git Workflow Matters
Git is used by 97% of developers according to the Stack Overflow Developer Survey, making it the most widely adopted version control system in history. With over 100 million users on GitHub alone and millions more on GitLab and Bitbucket, Git is not just a tool โ it is the shared language of software collaboration. Yet despite its ubiquity, many teams suffer from poorly defined workflows that create friction, slow delivery, and introduce risk.
Without a clear Git workflow, teams encounter predictable problems: merge conflicts that take hours to resolve, feature branches that drift so far from main they become impossible to integrate, deployments that break because untested code was pushed directly, and a commit history so tangled that nobody can understand what changed or why. These are not Git problems โ they are process problems that Git cannot solve on its own.
A well-defined Git workflow eliminates ambiguity. Every developer knows where to branch from, how to name their branches, when to open a pull request, what standards their code must meet before merging, and how conflicts should be resolved. The result is fewer conflicts, faster reviews, safer deployments, and a clean project history that serves as living documentation of how the codebase evolved.
Git Fundamentals Refresher
Before diving into workflow strategies, it is essential to have a solid grasp of Git's core concepts. Git is a distributed version control system, meaning every developer has a complete copy of the repository history on their local machine. This architecture enables offline work, fast operations, and powerful branching โ but it also means understanding the underlying model is critical to using Git effectively.
The following table summarises the eight foundational Git concepts that every developer should understand before adopting a team workflow. Each concept builds on the others, and together they form the vocabulary your team will use daily when discussing code changes, reviews, and releases.
Core Git Concepts
| Concept | What It Does | Common Commands |
|---|---|---|
| Repository | A complete history of your project including all files, branches, and commits. Can be local or remote (hosted on GitHub, GitLab, etc.). | git init, git clone |
| Branch | A lightweight, movable pointer to a commit. Branches allow parallel lines of development without affecting the main codebase. | git branch, git checkout -b, git switch -c |
| Commit | A snapshot of your staged changes at a point in time. Each commit has a unique SHA hash, an author, a timestamp, and a message describing the change. | git add, git commit -m |
| Merge | Combines changes from one branch into another. Creates a merge commit that has two parent commits, preserving the full history of both branches. | git merge |
| Rebase | Replays commits from one branch onto the tip of another, creating a linear history. Rewrites commit hashes, so should not be used on shared branches. | git rebase, git rebase -i |
| Cherry-pick | Applies a single commit from one branch onto another without merging the entire branch. Useful for hotfixes and selective backporting. | git cherry-pick <sha> |
| Stash | Temporarily shelves uncommitted changes so you can switch branches cleanly. Changes can be reapplied later with stash pop or stash apply. | git stash, git stash pop |
| Tag | A permanent marker on a specific commit, typically used to denote release versions. Unlike branches, tags do not move as new commits are added. | git tag v1.0.0, git tag -a |
Commits Are the Unit of Change
Every Git workflow revolves around the commit. A commit is not just a save point โ it is the atomic unit of change that gets reviewed, merged, reverted, cherry-picked, and deployed. Writing good commits with clear messages and focused changes is the single most impactful habit a developer can adopt. A repository with well-crafted commits is easy to review, debug, and maintain; one with sloppy commits becomes a liability over time.
Branching Strategies Compared
A branching strategy defines how your team uses branches to organise work, manage releases, and deploy code to production. There is no single "correct" strategy โ the right choice depends on your team size, release cadence, deployment model, and organisational maturity. The three most widely adopted strategies are GitFlow, GitHub Flow, and Trunk-Based Development.
Each strategy represents a different trade-off between flexibility and simplicity. GitFlow provides structure for teams that ship versioned releases on a fixed schedule. GitHub Flow strips away that complexity for teams that deploy continuously. Trunk-Based Development takes simplicity even further, optimising for speed and continuous integration at the cost of requiring strong engineering discipline.
Branching Strategy Comparison
| Strategy | Branch Structure | Best For |
|---|---|---|
| GitFlow | Long-lived main and develop branches. Feature branches from develop, release branches for stabilisation, hotfix branches from main. Merges flow develop → release → main. | Teams shipping versioned releases (e.g. mobile apps, on-premise software). Ideal team size: 5–20 developers. |
| GitHub Flow | Single main branch that is always deployable. Short-lived feature branches created from main, merged back via pull requests. No develop or release branches. | Teams practising continuous deployment (e.g. SaaS, web applications). Ideal team size: 2–15 developers. |
| Trunk-Based Development | All developers commit to main (trunk) directly or via very short-lived branches (less than 1–2 days). Feature flags control what is visible to users. No long-lived branches. | High-velocity teams with strong CI/CD and automated testing. Ideal team size: any, but requires mature engineering culture. |
Pros and Cons at a Glance
| Strategy | Pros | Cons |
|---|---|---|
| GitFlow | Clear separation of development and release. Supports parallel release preparation. Well-suited for scheduled releases and hotfix workflows. | Complex branch management. Long-lived branches cause painful merges. Overhead is excessive for teams deploying frequently. |
| GitHub Flow | Simple and easy to learn. One main branch reduces confusion. Pull requests enforce code review. Works naturally with CI/CD pipelines. | No built-in release management. Assumes main is always deployable. Requires strong automated testing to maintain confidence. |
| Trunk-Based Development | Minimal merge conflicts due to short-lived branches. Fastest integration cycle. Encourages small, incremental changes and feature flags. | Requires comprehensive test suites. Feature flags add complexity. Developers must be disciplined about keeping trunk green. |
There Is No One-Size-Fits-All Strategy
The most common mistake teams make is adopting a branching strategy because it is popular rather than because it fits their context. GitFlow is overkill for a three-person team deploying a web app daily. Trunk-Based Development can be dangerous for a team without automated tests. Start by understanding your release cadence, team size, and testing maturity, then choose the simplest strategy that meets your needs. You can always evolve your workflow as your team grows.
Writing Good Commit Messages
A commit message is the permanent record of why a change was made. Six months from now, when a developer is investigating a bug, the commit message is often the only context available. Good commit messages make debugging faster, code reviews easier, and automated changelog generation possible. Bad commit messages โ "fix stuff", "WIP", "updates" โ are worse than useless because they create the illusion of documentation without actually providing any.
The Conventional Commits specification has become the industry standard for structuring commit messages. It follows a simple format: type(scope): description. The type indicates what kind of change was made, the optional scope indicates which part of the codebase was affected, and the description explains what was done. This structure enables automated tooling to generate changelogs, determine version bumps, and filter commit history.
Conventional Commit Types
| Type | Description | Example Message |
|---|---|---|
| feat | A new feature visible to the end user. Triggers a minor version bump in semantic versioning. | feat(auth): add two-factor authentication via SMS |
| fix | A bug fix that corrects incorrect behaviour. Triggers a patch version bump in semantic versioning. | fix(cart): resolve race condition when updating quantity |
| docs | Changes to documentation only. No code logic is affected. | docs(api): add rate limiting examples to README |
| style | Code formatting, white-space, semicolons, etc. No logic changes. | style(components): apply Prettier formatting rules |
| refactor | Code restructuring that neither fixes a bug nor adds a feature. Improves readability or maintainability. | refactor(utils): extract date parsing into shared helper |
| test | Adding or updating tests. No production code changes. | test(auth): add integration tests for login flow |
| chore | Maintenance tasks such as dependency updates, build configuration, or CI pipeline changes. | chore(deps): upgrade React from 18.2 to 18.3 |
| perf | Performance improvements that do not change external behaviour. | perf(query): add database index on orders.created_at |
| ci | Changes to continuous integration configuration and scripts. | ci(github): add Node 20 to test matrix |
Source: Conventional Commits 1.0.0 โ conventionalcommits.org
Keep Commits Atomic
An atomic commit contains exactly one logical change. Do not mix a bug fix with a refactor, or a feature with a formatting update. Each commit should be independently reviewable, revertable, and understandable. If you need to undo a change six months from now, an atomic commit can be reverted cleanly with git revert. A commit that bundles multiple unrelated changes forces you to manually unpick the pieces โ a tedious and error-prone process that leads to regressions.
Pull Request Best Practices
Pull requests (PRs) are the primary mechanism for code review in modern Git workflows. A pull request is more than a request to merge code โ it is a structured conversation about a proposed change. Well-crafted PRs accelerate reviews, catch bugs early, share knowledge across the team, and create a searchable record of decisions. Poorly written PRs slow everything down, frustrate reviewers, and let defects slip through to production.
Research from Google and Microsoft shows that smaller pull requests receive faster, higher-quality reviews. A PR with 400 lines of changes takes roughly 60 minutes to review thoughtfully. A PR with 1,000 lines takes disproportionately longer โ and the quality of feedback drops sharply because reviewer fatigue sets in. The following practices will help your team write PRs that are easy to review and safe to merge.
Pull Request Rules for Effective Teams
| Rule | Why It Matters | How to Apply It |
|---|---|---|
| Keep PRs small | Smaller PRs (under 400 lines changed) receive faster, more thorough reviews. Large PRs cause reviewer fatigue and hide bugs. | Break large features into stacked PRs. Ship refactoring separately from feature work. Aim for one logical change per PR. |
| Write descriptive titles | The title appears in merge commits, changelogs, and notification emails. It must communicate the intent at a glance. | Follow the format: type(scope): short description. Be specific: "fix(auth): prevent session expiry during checkout" not "fix bug". |
| Include context in description | Reviewers need to understand the problem, the approach chosen, and any alternatives considered. Without context, reviews become guesswork. | Use a PR template with sections: What changed, Why, How to test, Screenshots. Link to the design document or discussion thread if one exists. |
| Add screenshots for UI changes | Visual changes are nearly impossible to review from code alone. Screenshots prove the change looks correct across viewports and states. | Include before/after screenshots. Show desktop and mobile views. Capture loading, empty, and error states where relevant. |
| Link to issues | Linking the PR to an issue creates traceability from requirement to implementation. It also enables automatic issue closure on merge. | Use keywords like "Closes #123" or "Fixes #456" in the PR description. Most platforms auto-close the linked issue when the PR merges. |
| Self-review before requesting | Catching your own typos, leftover debug code, and accidental file inclusions before asking others saves everyone time and builds trust. | Review your own diff line by line before marking the PR as ready. Remove console.log statements, commented-out code, and unrelated changes. |
| Respond to feedback promptly | Stale PRs with unresolved comments block the reviewer's mental context. The longer a PR sits, the harder it becomes to merge cleanly. | Respond to all comments within one working day. Resolve conversations as you address them. Push follow-up commits rather than force-pushing over reviewed code. |
Use Pull Request Templates
A PR template is a pre-filled description that appears whenever a developer opens a new pull request. It prompts the author to provide context, link issues, describe testing steps, and add screenshots. Templates dramatically improve the quality and consistency of PRs across the team. Most platforms (GitHub, GitLab, Bitbucket) support PR templates โ create one in your repository and require its use. A good template turns PR descriptions from an afterthought into a habit.
Merge Strategies and Conflict Resolution
Once a pull request is approved, the next decision is how to integrate it into the target branch. Git offers three merge strategies, each producing a different commit history. The choice affects readability, bisectability, and the team's ability to understand how the codebase evolved. There is no universally "best" strategy โ the right choice depends on your team's priorities and branching model.
Equally important is knowing how to resolve merge conflicts when they arise. Conflicts are inevitable in any team larger than one, but they do not have to be painful. Understanding why conflicts happen and having a systematic approach to resolving them turns a feared event into a routine part of the workflow.
Merge Strategy Comparison
| Strategy | What It Does | When to Use |
|---|---|---|
| Merge commit | Creates a dedicated merge commit with two parents, preserving the complete branch history. The feature branch's commits appear in the log alongside the merge commit. | When you want a full, unaltered record of how work was done. Good for teams that value traceability and audit trails. Default in most Git GUIs. |
| Squash and merge | Combines all commits from the feature branch into a single commit on the target branch. The individual branch commits are discarded from the main history. | When feature branches contain messy work-in-progress commits. Produces a clean main branch where each commit represents one complete feature or fix. |
| Rebase and merge | Replays the feature branch's commits on top of the target branch, creating new commit hashes. Produces a perfectly linear history with no merge commits. | When the team values a linear, easy-to-read history. Works well with trunk-based development. Requires understanding that commit hashes change. |
Pros and Cons of Each Strategy
| Strategy | Pros | Cons |
|---|---|---|
| Merge commit | Preserves complete history. Non-destructive โ original commits are untouched. Easy to revert an entire feature by reverting the merge commit. | History can become cluttered with merge commits. Branch topology can be hard to follow in large repositories. Bisecting is slightly more complex. |
| Squash and merge | Clean main branch history. Each commit on main is a self-contained change. Hides messy intermediate commits from the permanent record. | Loses granular commit history from the feature branch. Cannot bisect within a squashed commit. Author attribution is reduced to a single commit. |
| Rebase and merge | Perfectly linear history. Easy to read with git log --oneline. Each commit is individually bisectable and revertable. | Rewrites commit hashes, which can confuse collaborators. Should never be used on shared branches. Requires developers to understand rebase mechanics. |
When two developers modify the same lines in the same file, Git cannot automatically determine which change should take precedence โ this is a merge conflict. The following steps provide a systematic approach to resolving conflicts safely.
Conflict Resolution Steps
| Step | Action | Details |
|---|---|---|
| 1 | Pull the latest target branch | Run git fetch origin then git merge origin/main into your feature branch. This surfaces conflicts locally where they are easiest to resolve. |
| 2 | Identify conflicting files | Run git status to see files marked as "both modified". Open each file and look for conflict markers: <<<<<<<, =======, >>>>>>>. |
| 3 | Understand both changes | Read the code above and below the conflict markers. Use git log or the PR description to understand the intent behind each change before deciding how to resolve it. |
| 4 | Resolve the conflict | Edit the file to produce the correct final version. Remove all conflict markers. If unsure, consult the other developer โ do not guess at their intent. |
| 5 | Test the resolution | Run the test suite and verify the application works correctly. Conflict resolution is a common source of subtle bugs that pass a visual check but break behaviour. |
| 6 | Stage and commit | Run git add on resolved files and git commit. The merge commit message should describe the conflict resolution if it involved non-trivial decisions. |
Never Force-Push to Shared Branches
Running git push --force on a shared branch rewrites remote history and destroys other developers' work. If a colleague has based their work on commits you force-push away, their branch becomes incompatible and they must manually reconcile the differences. Use git push --force-with-lease as a safer alternative when you must rewrite history โ it refuses to push if the remote has commits you have not seen. Better yet, avoid rewriting history on any branch that others are working on.
Build Your Development Skills
Ready to take your software development skills further? Our accredited Software Development course covers Git workflows, coding best practices, testing strategies, CI/CD pipelines, and professional development methodology. Build the skills employers are looking for and accelerate your career in tech.
Explore Our Software Development Course