Feature branches are the backbone of collaborative Git workflows. They let you develop new functionality, fix bugs, or experiment – all without touching the main branch. Every change stays isolated until it is reviewed, tested, and merged.
This guide covers the full feature branch workflow from creation to merge and cleanup. Whether your team follows GitHub Flow, Git Flow, or trunk-based development, the core branching mechanics are the same. We also compare merge strategies (merge commit vs rebase vs squash) and walk through conflict resolution. For a deeper look at Git terminology like PRs, MRs, tags, and branches, check our reference guide.
Prerequisites
Before starting, make sure you have the following in place:
- Git 2.x installed on your workstation – see our guide on installing Git on RHEL / Rocky Linux / AlmaLinux
- A remote repository on GitHub, GitLab, or Bitbucket (or any Git hosting platform)
- Basic familiarity with Git commands (clone, commit, push)
- SSH key or HTTPS credentials configured for pushing to the remote
Step 1: Create a Feature Branch
Every new piece of work starts with a fresh branch off the main branch. This keeps your changes isolated and makes code review straightforward. First, make sure your local main branch is up to date with the remote.
git checkout main
git pull origin main
Now create and switch to a new feature branch. Use a descriptive name that makes the purpose clear at a glance.
git checkout -b feature/add-user-authentication
This creates the branch and switches to it in one step. You can verify which branch you are on with:
git branch
The active branch is marked with an asterisk:
main
* feature/add-user-authentication
Branch naming conventions – pick a pattern your team agrees on and stick with it:
feature/– new functionality (feature/add-oauth-login)bugfix/– bug fixes (bugfix/fix-null-pointer-on-submit)hotfix/– urgent production fixes (hotfix/patch-sql-injection)chore/– maintenance tasks (chore/update-dependencies)
Step 2: Make Changes and Commit
With the feature branch active, make your code changes as usual. The key difference is that all commits stay on this branch and do not affect main. Stage your modified files and commit with a clear message.
git add src/auth/login.py src/auth/middleware.py
git commit -m "Add JWT-based login endpoint and auth middleware"
Write commits that describe what changed and why, not just “fixed stuff.” Small, focused commits are easier to review and revert if something breaks. A good practice is to commit early and often rather than accumulating a massive diff.
Continue making changes, staging, and committing as needed. You can check your commit history on the feature branch at any time:
git log --oneline main..HEAD
This shows only the commits on your feature branch that are not on main:
a3f7c2d Add JWT-based login endpoint and auth middleware
b1e9f4a Add user model with password hashing
c8d2a1b Create auth module structure
Step 3: Push Feature Branch to Remote
Once you have one or more commits on the feature branch, push it to the remote repository. This backs up your work and makes the branch visible to teammates.
git push -u origin feature/add-user-authentication
The -u flag sets the upstream tracking branch so future pushes from this branch only need git push without specifying the remote and branch name. After the first push, subsequent pushes are just:
git push
If your team uses a CI/CD pipeline, pushing the branch typically triggers automated tests. Most teams configure pipelines to run on every push to feature branches so issues surface early. If you are running Jenkins, you can set up multi-branch pipelines that automatically detect and build new branches.
Step 4: Create a Pull Request (GitHub / GitLab)
A pull request (PR on GitHub/Bitbucket) or merge request (MR on GitLab) is the formal way to propose merging your feature branch into main. It provides a centralized place for code review, discussion, and CI status checks. For a comprehensive explanation of pull requests and their lifecycle, see the official GitHub documentation.
On GitHub, create a PR from the command line using the GitHub CLI:
gh pr create --base main --head feature/add-user-authentication \
--title "Add user authentication with JWT" \
--body "Implements login endpoint, auth middleware, and user model with password hashing."
Or navigate to your repository on GitHub and click “Compare & pull request” after pushing the branch.
On GitLab, create a merge request via the CLI:
glab mr create --source-branch feature/add-user-authentication \
--target-branch main \
--title "Add user authentication with JWT"
When writing PR descriptions, include the context a reviewer needs – what problem it solves, what approach you took, how to test it, and any areas where you want specific feedback.
Step 5: Code Review Workflow
Code review is where feature branches deliver their real value. The main branch stays protected while the team reviews changes in isolation. A solid review process catches bugs, enforces standards, and spreads knowledge across the team.
For reviewers – check out and test the branch locally before approving:
git fetch origin
git checkout feature/add-user-authentication
Run the test suite and verify the changes work as described in the PR. Leave constructive comments on specific lines in the PR interface.
For authors – respond to review comments by making additional commits on the same branch:
git add src/auth/login.py
git commit -m "Address review: add input validation to login endpoint"
git push
The PR updates automatically with the new commits. Most teams require at least one approval before merging. Branch protection rules on GitHub and GitLab let you enforce this – along with requirements like passing CI checks, no merge conflicts, and signed commits.
Step 6: Merge the Feature Branch
After approval, it is time to merge the feature branch into main. Git offers three main merge strategies, each with different trade-offs for your commit history.
Merge Commit (Default)
Creates a merge commit that preserves the full branch history. All individual commits from the feature branch appear in main along with the merge commit itself.
git checkout main
git merge feature/add-user-authentication
Best for: teams that want a complete audit trail of every commit on every branch.
Rebase and Merge
Replays each feature branch commit on top of main, creating a linear history with no merge commits. This produces the cleanest git log output.
git checkout feature/add-user-authentication
git rebase main
git checkout main
git merge feature/add-user-authentication
Best for: teams that prefer a clean, linear commit history. Avoid rebasing branches that others are actively working on – it rewrites commit hashes and causes conflicts for collaborators.
Squash and Merge
Combines all feature branch commits into a single commit on main. The branch history is collapsed into one clean commit.
git checkout main
git merge --squash feature/add-user-authentication
git commit -m "Add user authentication with JWT (#42)"
Best for: teams that want each feature represented as a single commit on main. Works well when feature branches have messy “WIP” commits that do not add value to the main branch history.
Step 7: Delete Merged Branches
Once a feature branch is merged, delete it to keep your repository clean. Stale branches accumulate fast and make it hard to find active work.
Delete the local branch:
git branch -d feature/add-user-authentication
The -d flag only deletes the branch if it has been fully merged. Use -D to force-delete an unmerged branch (use with caution).
Delete the remote branch:
git push origin --delete feature/add-user-authentication
To clean up stale remote tracking references on your local machine:
git fetch --prune
Most hosting platforms (GitHub, GitLab) have an option to automatically delete branches after merge. Enable it in your repository settings to reduce manual cleanup.
Step 8: Handle Merge Conflicts
Merge conflicts happen when two branches modify the same lines in the same file. Git cannot automatically decide which version to keep, so you resolve it manually. This is a normal part of working with feature branches.
When a merge or rebase hits a conflict, Git marks the affected files. Check which files need attention:
git status
Files with conflicts appear under “Unmerged paths.” Open each conflicted file and look for the conflict markers:
<<<<<<< HEAD
def authenticate(token):
return verify_jwt(token, secret=settings.JWT_SECRET)
=======
def authenticate(token):
return verify_jwt(token, algorithm="HS256")
>>>>>>> feature/add-user-authentication
The section between <<<<<<< HEAD and ======= is the main branch version. The section between ======= and >>>>>>> is the feature branch version. Edit the file to keep the correct code and remove the conflict markers.
After resolving all conflicts, stage the files and complete the merge:
git add src/auth/login.py
git commit -m "Resolve merge conflict in authentication function"
Reducing conflict frequency – regularly pull updates from main into your feature branch to stay in sync:
git checkout feature/add-user-authentication
git merge main
This catches conflicts early when they are small, rather than discovering a massive conflict at merge time.
Step 9: Git Flow vs GitHub Flow vs Trunk-Based Development
Feature branches are used in every major Git workflow, but the rules around them differ. Choosing the right workflow depends on your team size, release cadence, and deployment process.
Git Flow
Uses long-lived branches: main, develop, feature/*, release/*, and hotfix/*. Features branch off develop, not main. Releases get their own branch for stabilization. Good for software with versioned releases (desktop apps, libraries). Overkill for web apps with continuous deployment.
GitHub Flow
Simpler model with just main and feature branches. Every feature branch merges directly into main via a PR. Main is always deployable. Good for web apps and services with continuous deployment. Most teams find this workflow strikes the right balance between simplicity and safety.
Trunk-Based Development
Developers commit directly to main (the “trunk”) in small, frequent increments. Feature branches exist but are extremely short-lived – usually merged within hours, not days. Feature flags control whether incomplete code is active in production. Demands strong CI/CD, automated testing, and disciplined team practices. Used by Google, Meta, and other large engineering organizations. If your team follows GitOps practices, trunk-based development pairs naturally with automated deployment pipelines.
Git Branch Command Reference
This table covers the most common Git commands for working with feature branches. Keep it handy as a quick reference.
| Command | Description |
|---|---|
git branch | List local branches |
git branch -a | List local and remote branches |
git checkout -b branch-name | Create and switch to a new branch |
git switch -c branch-name | Create and switch (modern syntax, Git 2.23+) |
git checkout branch-name | Switch to an existing branch |
git push -u origin branch-name | Push branch and set upstream tracking |
git merge branch-name | Merge branch into current branch |
git merge --squash branch-name | Squash all commits into one before merging |
git rebase main | Rebase current branch onto main |
git branch -d branch-name | Delete merged local branch |
git branch -D branch-name | Force-delete local branch (even if unmerged) |
git push origin --delete branch-name | Delete remote branch |
git fetch --prune | Remove stale remote tracking references |
git log --oneline main..HEAD | Show commits on current branch not in main |
git stash | Temporarily save uncommitted changes |
git stash pop | Restore stashed changes |
Conclusion
Feature branches keep your main branch stable while giving developers freedom to build, experiment, and iterate. The workflow is the same regardless of platform – create a branch, commit your work, push, open a PR, get it reviewed, and merge. For production environments, combine branch protection rules with automated CI/CD to ensure nothing hits main without passing tests and peer review.