Back to articles

Understanding Git Branching for Team Development

A practical guide to using main, release/staging, and feature branches so a team can work together without breaking production.

By: Irfan Syafi | Date: June 10, 2026 | Tags: Git, Branching, Workflow, Collaboration

Introduction

When several people work on the same codebase, a common question comes up early: where does everyone's code live, and how do you add yours without overwriting someone else's work? A branching strategy answers this. It is simply an agreed set of rules for where code lives and how it moves toward production.

In this article I want to explain the strategy I use day to day, built around three kinds of branches: main, release/staging, and feature branches. I will also cover how Git keeps the copy on your machine in sync with the one on the server, how commit history is preserved when you branch, and the specific situation in which a merge conflict occurs. I have included a few diagrams to make each idea easier to picture.

Two Repositories: Local and Remote

The first concept to be clear about is that Git almost always involves two copies of the same project:

  • Local repository: the clone on your own machine. This is where you make commits, quickly and even while offline. No one else can see them yet.
  • Remote repository: the shared copy hosted on a server such as GitHub, GitLab, or a self-hosted Git server. This is the common ground that everyone on the team pulls from and pushes to.

Four commands move commits between the two. git push sends your local commits up to the remote, git fetch downloads new work without touching your files, git pull downloads and merges that work into your branch, and git clone is run once at the start to create the local copy.

Local Repository (your machine) commits + full history Remote Repository (GitHub / GitLab / server) shared by the whole team git push git fetch / git pull
Commits live in both repositories. You push to share your work and pull to receive everyone else's.
Why synchronise at all? Because each person holds a full local copy and everyone pushes to one shared remote, several people can develop different things at the same time. Reconciling those parallel changes back into a single history is exactly what Git is built to do.

The Role of Each Branch

With the two-copy idea in place, the next step is branches. A branch is a separate line of work, and in this strategy each one has a clear job:

  • main: the production branch. This is the stable version that real users run, so it should always be ready to deploy. You rarely commit to it directly; code lands here only after it has been tested.
  • release/staging: the testing branch. It holds a version that looks ready but still needs to be verified in a staging environment before any real user sees it. Quality checks and final testing happen here.
  • feature/* (or development): the working branches. Every new feature or fix gets its own branch, such as feature/product-recommendation. This is where the day-to-day work happens, isolated from everyone else's changes.

You create these branches with git checkout -b, which means "create a new branch and switch to it." One detail is easy to overlook at first: the new branch starts from wherever you currently are, so it carries all of that branch's history with it.

# Start from the stable production line
git checkout main

# Create a staging branch off main (for testing)
git checkout -b release/staging

# From staging, create a feature branch for the actual work
git checkout -b feature/product-recommendation
main release release/staging feature/... checkout -b checkout -b merge / PR merge / PR
Work flows down (branch off) to develop in isolation, then back up (merge) once it is verified: feature → release/staging → main.
main — production (stable) release/staging — testing feature — active development

How Commit History Is Preserved

A common question is whether branching off release/staging loses the earlier history. It does not. A branch in Git is a pointer to a commit, and every commit records its parent. So when you run git checkout -b feature/product-recommendation, the new branch points to the same commit that release/staging is on at that moment. It already contains the entire history that led up to that point.

release/staging A B C branch point D E feature Commits A, B, C are shared. New commits D, E build on top.
The feature branch inherits commits A–C the moment it is created; the new commits D and E extend that same chain.

Later, when you merge the feature back into release/staging, Git replays commits D and E on top of staging, so staging then contains A through E. Promote staging into main and the same history flows up again. Nothing is discarded; the history accumulates. You can inspect it at any time with:

git log --oneline --graph --all

When Does a Conflict Happen?

A merge conflict occurs when two branches change the same lines of the same file in different ways, and Git cannot determine which version is correct. If two people edit different files, or even different parts of the same file, Git merges them automatically and you will not notice anything. Conflicts appear only where the changes genuinely overlap.

shared base Dev A edits line 10 → "blue" Dev B edits line 10 → "green" ! CONFLICT same line, two values
Two developers changed the same line from a common base. Git stops and asks a person to decide.

When this happens, Git pauses the merge and marks the file so you can resolve it manually:

<<<<<<< HEAD
color = "blue"      # your change
=======
color = "green"     # the incoming change
>>>>>>> feature/product-recommendation

Edit the file to keep the correct version (or a combination of both), remove the <<<, ===, and >>> markers, then complete the merge:

git add color-config.py
git commit          # completes the merge
Reducing conflicts: pull frequently (before starting work and again before pushing), keep feature branches small and short-lived, and where possible agree to work in different files. Several small merges are far easier to handle than one large merge weeks later.

Putting It Together: Concurrent Development

This is where the strategy pays off. With it in place, a team can work in parallel without stepping on each other. Everyone clones the remote, branches off staging for their task, commits on their own machine, then pushes the branch up for review and merging.

Dev A — Local feature/recommendation commits offline Dev B — Local feature/checkout-ui commits offline Remote Repository main release/staging + everyone's branches push / pull push / pull
Two developers build on their own branches and machines; the remote is the single source of truth that ties their work together.

A typical working session follows these steps:

# 1. Get the latest shared code
git checkout release/staging
git pull origin release/staging

# 2. Branch off for the task
git checkout -b feature/product-recommendation

# 3. Do the work, committing along the way (local only)
git add .
git commit -m "Add product recommendation engine"

# 4. Share it with the team
git push -u origin feature/product-recommendation

# 5. Open a Pull Request to merge into release/staging,
#    let the tests and reviewers check it, resolve any conflicts, then merge.

Once the feature passes its checks on staging, release/staging is merged into main and deployed. Because every change follows the same path, main only ever receives code that has already been tested, which is what keeps it stable.

Conclusion

A branching strategy is less a rule to memorise and more a map of where code is and where it is going. With main for production, release/staging for testing, and short-lived feature branches for the actual work, you get a clean promotion path: feature → staging → main. Git carries the history up that path, stops you only when two changes truly clash, and keeps the local copy in sync with the remote so an entire team can build at the same time.

If you are new to this, try the three-branch flow on your next project, pull often, and keep feature branches small. The workflow becomes second nature with a little practice.

To Conclude: The Basic Workflow

To tie everything together, here is the basic workflow from an empty folder to a pushed branch. These are the exact steps you would follow when connecting a local project to a remote repository for the first time.

  1. Initialise Git in your workspace.
    Inside your project folder, turn it into a Git repository:
    git init
  2. Check your default branch name.
    Once Git is initialised, check whether your local default branch is called master or main:
    git branch
    Older versions of Git default to master, while most remotes (such as GitHub) now default to main. If your remote uses main but your local branch is master, rename your local branch so the two match:
    git branch -m master main
    The -m flag simply moves (renames) the current branch.
  3. Connect the remote with origin.
    Copy the URL of your remote repository, then add it to your local repository:
    git remote add origin <url>
    Here, origin is just the conventional name (an alias) for your remote URL. Instead of typing the full URL every time you push or pull, you refer to it as origin. You could name it anything, but origin is the standard.
  4. Fetch everything and match branches.
    Download all branches and history from the remote so your local repository knows what exists there:
    git fetch --all
    Then check out and align your local branches with the remote ones so you are working on the same starting point:
    git checkout main
    git pull origin main
  5. Make your changes, then stage, commit, and push.
    After editing your files, stage them, record a commit with a clear message, and push to the branch you are working on:
    git add .
    git commit -m "made changes comment"
    git push origin <branch name>
    Replace <branch name> with the branch you intend to update, for example release/staging or your feature branch.

From here, the cycle repeats: branch off, commit your work, push, open a pull request, and merge once it has been reviewed and tested. That loop, combined with the three branch roles above, is the foundation of working on a codebase with other people.

Contact Me

If you have questions or would like to be in touch whether to improve the project (or want to collaborate), feel free to reach out. I'm also open to learn from others.

Ask Me!

Ask anonymously about this article or project  here — powered by my self-hosted Q&A app.
Back to articles