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.
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.
push to share your work and pull to receive everyone else's.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/*(ordevelopment): the working branches. Every new feature or fix gets its own branch, such asfeature/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
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.
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.
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
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.
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.
-
Initialise Git in your workspace.
Inside your project folder, turn it into a Git repository:git init -
Check your default branch name.
Once Git is initialised, check whether your local default branch is calledmasterormain:
Older versions of Git default togit branchmaster, while most remotes (such as GitHub) now default tomain. If your remote usesmainbut your local branch ismaster, rename your local branch so the two match:
Thegit branch -m master main-mflag simply moves (renames) the current branch. -
Connect the remote with
origin.
Copy the URL of your remote repository, then add it to your local repository:
Here,git remote add origin <url>originis 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 asorigin. You could name it anything, butoriginis the standard. -
Fetch everything and match branches.
Download all branches and history from the remote so your local repository knows what exists there:
Then check out and align your local branches with the remote ones so you are working on the same starting point:git fetch --allgit checkout main git pull origin main -
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:
Replacegit add . git commit -m "made changes comment" git push origin <branch name><branch name>with the branch you intend to update, for examplerelease/stagingor 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.