Team Workflows: Shared Main and Feature Branches
Git permits a team to share one branch or work on separate branches. These are team policies, not different versions of Git. The decision affects when unfinished code reaches the branch everyone relies on.
One shared branch
Section titled “One shared branch”In a centralized-style workflow, Ahmed, Sara, and Omar each have a local clone and commit directly on main. They all push to the same hosted main.
Ahmed/main ─┐Sara/main ─┼──> GitHub/mainOmar/main ─┘This can be simple for a tiny, closely coordinated exercise. Nobody has to name feature branches. It becomes awkward when developers work independently or when main is expected to remain deployable.
Why a push is rejected
Section titled “Why a push is rejected”Omar and Sara both start from commit C1. Omar pushes O1; Sara creates S1 without fetching Omar’s work:
GitHub main: C1──O1Sara main: C1──S1Sara’s ordinary push cannot fast-forward GitHub’s branch to S1 because that would remove O1 from its reachable tip. Git rejects the non-fast-forward push to protect Omar’s work.
Sara should fetch, inspect, then integrate according to team policy before pushing:
git fetch origingit log --oneline --graph --decorate --allgit switch maingit merge origin/maindotnet testgit push origin mainA merge may produce a conflict, but divergence alone does not guarantee one. A team could also choose a carefully scoped rebase for unpublished local commits; do not force-push shared main to bypass the rejection.
The deeper problem: unfinished work
Section titled “The deeper problem: unfinished work”Ahmed may want help with a half-built feature. Git technically lets him push an unfinished commit to a writable shared main, but that exposes broken code to everyone who updates from it. He is not blocked by Git merely because the feature is incomplete; the team’s stability rule is the issue.
If Sara also has local edits, a pull may refuse when integrating would overwrite them. She should inspect git status, then commit or stash as appropriate before pulling. One shared branch makes this coordination frequent.
Claims such as “you must merge before almost every push” are too strong. You need to integrate remote work when the shared branch has moved beyond your local base; otherwise a normal push can succeed.
Feature branch workflow
Section titled “Feature branch workflow”A better default for concurrent work is one branch per task. Keep main as the shared integration branch and do ordinary development elsewhere:
main ──────────────────────────────────────── stable integration ├── feature/pricing ── Ahmed's work └── feature/navbar ── Sara's workThe branch name describes the change, not necessarily the person. A feature branch can hold unfinished code and be pushed for review without changing main. That does not make it automatically safe for production; it simply isolates its history until integration.
Create and share a branch
Section titled “Create and share a branch”Start from an up-to-date base when possible:
git switch maingit pull --ff-only origin maingit switch -c feature/pricingWork, test, and publish the branch when collaboration is needed:
git add src/PricingService.csgit commit -m "feat: add pricing table"git push -u origin feature/pricingPushing a feature branch leaves hosted main unchanged. A teammate can fetch it and create a local branch that tracks it:
git fetch origingit switch --track origin/feature/pricingIf the local branch already exists, switch to it instead. The exact command can also depend on branch naming and existing local references. Sara can commit fixes on that shared feature branch if the team grants write access, then push it. Ahmed fetches or pulls its updates later. Their unrelated feature branches need not be merged into each other.
Decide when to integrate
Section titled “Decide when to integrate”There are several governance levels, not several Git merge algorithms:
- Merge at will: a developer decides alone. Fine for a disposable or tiny project, risky when production stability matters.
- Agree, then merge locally: teammates review or discuss the change, and an authorized developer merges and pushes
main. - Pull Request: discussion, review, checks, and merge happen through the hosting platform. This is the focus of the next lesson.
The appropriate gate depends on team size, risk, repository permissions, and the importance of main. Even a Pull Request does not guarantee quality unless review and tests are meaningful.
Local integration after agreement
Section titled “Local integration after agreement”Assume Ahmed’s feature is complete and his local feature branch includes the latest reviewed work:
git switch maingit pull --ff-only origin maingit merge feature/pricingdotnet testgit push origin mainGit may fast-forward if main is an ancestor of the feature tip. If both lines moved, it may create a merge commit and possibly stop for conflict resolution. The merge lesson explains those mechanics in detail.
Sara later updates her local integration branch:
git switch maingit pull --ff-only origin mainOnly after the merge reaches the hosted branch do other developers see it through fetch or pull. Local merge, push, teammate pull is the coordination sequence.
After integration
Section titled “After integration”Deleting the completed feature branch is optional cleanup. First verify that the intended work is present on main, that tests pass, and that no one needs the branch for ongoing work.
git branch -d feature/pricinggit push origin --delete feature/pricingThe first command removes the local branch if Git considers it merged. The second deletes the hosted branch if permissions allow. Neither command is required for the merge itself. Be careful with a remotely shared branch: coordinate before removing a name collaborators still use.
What feature branches improve, and what they do not
Section titled “What feature branches improve, and what they do not”- They keep unfinished commits away from
mainuntil intentionally integrated. - They allow teammates to share and review a work-in-progress branch.
- They reduce pressure to reconcile unrelated work directly on shared
mainfor every commit. - They do not eliminate conflicts or guarantee fewer total merges. Long-lived branches may accumulate large integration problems.
- A stable
mainalso requires tests, review, and access policy, not just branch names.
Common mistakes
Section titled “Common mistakes”- Force-pushing after a rejected shared-branch push.
- Saying Git forbids unfinished commits on
main; it is usually a team rule. - Assuming different branch tips always cause a file conflict.
- Creating a feature branch from an outdated base without checking remote work.
- Pushing a feature branch and assuming
mainhas changed. - Deleting a shared branch before confirming nobody still needs it.
Summary
Section titled “Summary”One shared branch is simple but couples everyone’s incomplete work. A feature branch isolates each task, can be published for collaboration, and reaches main only after an intentional merge. Discussed local merges and Pull Requests are different review policies around the same branch integration.
Knowledge check
Section titled “Knowledge check”- Why did Sara’s push fail after Omar pushed first?
- Does Git itself forbid an unfinished feature from being pushed to
main? - What changes on GitHub when you push only
feature/pricing? - What must happen after a local merge before teammates can receive it?
- Why might a long-lived feature branch still become difficult to integrate?