Bare repositories in Git contain the revision history and code without a working directory for editing files. They act as remote repositories for teams to share and sync code. While you cannot commit directly to a bare repo, you can push branches from local clones into the central bare repository.
This comprehensive guide demonstrates how to create, clone, push to, merge and manage branches in a bare Git repo for streamlined collaboration.
Understanding Bare Repositories in Git
A bare repository in Git contains only the .git
revision history folder and serves as a remote origin for clones. Bare repos do not have a working tree for making edits as they store code share it across the team.
Some key properties of bare repositories include:
- The repository contents live directly in the
.git
folder rather than a nested structure - No checked out copy of files to work on (no working tree)
- Shared remotely using SSH or HTTP protocols
- Acts as central repository for others to push to
Bare repositories optimize repositories for collaboration and sharing code while maintaining high performance. Teams use them to establish one true upstream source rather than individual copies.
Use Cases for Bare Repositories
Common scenarios where you would set up a bare repository include:
Central team repository: Have a canonical location containing the source code for the entire codebase that all team members access. Allows seamlessly sharing changes.
Pipeline artifacts: Store output artifacts from CI/CD pipelines like logs, build outputs, test results in a bare repo for persistence.
Vendor repositories: Maintain read-only repositories containing vendor code or shared libraries.
Backup code: Keep safe backups of complete codebases in a bare repo that contains the entire snapshot.
Compared to non-bare clones, bare repositories focus entirely on collaboration and sharing rather than individual editing workflows. Next, we see how to model a team workflow using branches with a bare origin repo.
Step 1 – Initialize Central Bare Repository
First, set up a central Git repository to act as the single source of truth using the --bare
flag:
$ mkdir team-repo.git
$ cd team-repo.git
$ git init --bare
Initialized empty Git repository in /path/to/team-repo.git/
This initializes an empty bare repo that acts as a remote origin for the entire team.
With the bare repository ready, we can now clone it and start pushing changes.
Step 2 – Clone the Bare Repository
Next, team members can create a local clone of this bare repo to develop features in:
# Developer A
$ git clone team@host:/path/to/team-repo.git my-local-clone
# Developer B
$ git clone team@host:/path/to/team-repo.git dev-b-clone
This pulls down a full copy of the repository they can modify. The working tree is kept isolated per developer for private development.
Now developers have local clones linked to the shared bare repository.
Step 3 – Create and Push Branches
Within their clones, developers can create branches to collaborate on features:
# Developer A
$ git checkout -b new-api
Switched to a new branch ‘new-api‘
# Develop the API
$ touch app/api.js
$ git add .
$ git commit -m "Add API endpoint"
$ git push origin new-api
Total 0 (delta 0), reused 0 (delta 0)
To /path/to/team-repo.git
* [new branch] new-api -> new-api
This commits the API to a dedicated branch and makes it accessible to others by pushing to origin
. The bare repository now contains this branch.
Similarly, other team members clone, branch and push commits:
# Developer B
$ git checkout -b auth
$ touch auth.py
$ git add .
$ git commit -m "Add auth module"
$ git push -u origin auth
This way everyone contributes independently while seamlessly exchanging changes through the bare central repository.
Step 4 – Merge Branches
As branches grow independently, they can be merged to bring their changes together:
# Locally merge branches
$ git checkout master
$ git merge new-api
# Push merged branch
$ git push origin master
By merging locally and pushing, you cleanly fast-forward the central master
branch. The bare repo stays up-to-date reflecting the consolidated code across branches.
Step 5 – Manage Long-Running Branches
For use cases like long-running releases, you can manage branches directly in the bare repo using Git tools like push --set-upstream origin
:
# Create and set up new branch
$ git push --set-upstream origin release/fall
Total 0 (delta 0), reused 0 (delta 0)
To /path/to/team-repo.git
* [new branch] release/fall -> release/fall
Branch ‘release/fall‘ set up to track remote branch ‘release/fall‘ from ‘origin‘.
# Pull latest code into branch
$ git pull
Already up to date.
# Rebase onto master
$ git rebase master
First, rewinding head to replay your work on top of it...
This way long-running release branches can serve their lifecycles independently without mixing up code streams.
Recommended Practices With Branches
Follow these best practices while using branches with bare repositories:
- Keep short-lived local branches for isolated features
- Rebase frequently to avoid messy merge commits
- Push branches early to enable collaboration
- Prune stale branches after pull requests are merged
- Protect critical branches like
staging
using permissions
This helps scale branching strategies from small teams to large products with hundreds of parallel streams.
Troubleshooting Branch Errors
Some common issues when managing branches include:
Failed pushes: Fix by pulling remote changes first before pushing local commits.
Merge conflicts: Rebase branch to replay commits cleanly instead of merge commits.
Diverged branches: Bring them to parity by rebasing interactively and fixing issues.
Security issues: Use protected branches with strict controls on collaborators.
With discipline around scaling branches, you can sustain rapid development across products and dependencies.
The Essence of Distributed Branching
Fundamentally, distributed version control systems like Git enable lightweight branch management laying the foundation for DevOps and modern engineering:
Innovation velocity – Engineers can build new capabilities without disrupting others leading to faster experimentation.
Ownership – Branches with clear owners and scope lower coordination bandwidth across teams.
Task isolation – By separating tasks into branches, engineers avoid stepping on each other‘s toes.
Refactoring safety – Big refactors can bake in isolation lowering risk for the mainline code.
Wrapping Up
Bare repositories provide the base for branching strategies by acting as central hubs for dispersed teams to interoperate. This guide walked through a team workflow using a shared bare origin repo for taking branches from local clones then pushing back.
We covered scenarios from short feature branches to long-running releases and troubleshooting merge issues. With a strong foundation using bare repos, developers stay in sync while working on isolated branches in parallel.
The principles explored apply equally to small startups seeding ideas to large enterprises running thousand-node clusters – unlocking scale while sustaining productivity.
Hopefully this gives you the tools to design fluid branching mechanisms customized to your systems and team culture. The open source movement thrives thanks to distributed workflows and meritocratic ideas we have covered.
In closing, I welcome suggestions to keep improving this article based on your experience levels adopting distributed version control with Git. The iterative branch, commit and merge cycle leads to the best results.