Published 2025-01-22.
Time to read: 4 minutes.
git
collection.
A bare git repository is a server-side Git repository. Because users do not interact with it directly, no working tree or index is required.
By convention, the name of the directory containing a bare Git repository ends with .git
.
Although this is not required, if a bare Git repository is named this way,
when a user clones the bare Git repository,
the name of the directory containing the cloned repository will not end with .git
.
Experienced Git users expect this behavior,
so it is a good idea to provide a .git
suffix for bare Git repositories.
Bare repositories do not have a working tree, so you cannot add files to them as you would for a normal repository. Instead, you must update a bare repository by pushing to it from a clone of the repository.
Where to Use
Bare Git repositories are often stored on a local area network server.
Bare Git repositories need to be accessible via a supported protocol.
That means the bare Git repository must either be stored on the local machine,
or on a local area network, or accessed remotely via the ssh
or git
protocols.
When to Use
The advantages of bare Git repositories over online hosted repositories are:
- Security can be better than storing files in the cloud.
- Simple setup because there is no Git server.
The git init ‑‑shared Option
The article entitled Shared Directories With POSIX Groups and SGID discusses POSIX groups and the SGID permission bit.
This section uses the git_access
POSIX group created in that article.
The most convenient way to allow others to push to a bare Git repository is to use the git init ‑‑shared
option.
This option causes the SGID bit to be set on the directory holding the Git repository.
The general format is:
$ git --bare --shared=some_value init
The --shared
option can have several values.
The possible values that are relevant to a locally accessible Git LFS from a bare repository are:
all
, everybody
, group
, true
, and world
.
When the ‑‑shared
option is specified,
the config variable core.sharedRepository
is set to 1 for group
sharing,
and is set to 2 for sharing with everybody
.
Otherwise, if the ‑‑shared
option is not specified, Git will use the permissions reported by
umask
.
See the man
page for git-init
for further details.
If you specify ‑‑shared=group
or ‑‑shared=true
,
the Set Group ID (SGID) permission for that directory will be set,
as shown in the highlighted s
permission bit in the code example below.
Note that test_repo.git
is created in the home directory of user mslinn
for this example.
mslinn@gojira ~ mkdir --mode=g+s test_repo.git
mslinn@gojira ~ sudo chgrp git_access test_repo.git
mslinn@gojira ~ ls -ld test_repo.git drwxrwsrwx 2 mslinngit_access
4096 Jan 20 13:51 test_repo.git/
mslinn@gojira ~ git init --bare --shared=group test_repo.git Initialized empty shared Git repository in /home/mslinn/test_repo.git
mslinn@gojira ~ ls -l test_repo.git total 28 -rw-rw-r-- 1 mslinn git_access 126 Jan 20 09:34 config -rw-rw-r-- 1 mslinn git_access 73 Jan 20 09:34 description -rw-rw-r-- 1 mslinn git_access 23 Jan 20 09:34 HEAD drwxrws
r-x 2 mslinn git_access 4096 Jan 20 09:34 hooks/ drwxrws
r-x 2 mslinn git_access 4096 Jan 20 09:34 info/ drwxrws
r-x 4 mslinn git_access 4096 Jan 20 09:34 objects/ drwxrws
r-x 4 mslinn git_access 4096 Jan 20 09:34 refs/
mslinn@gojira ~ cd test_repo.git
mslinn@gojira test_repo.git git config core.sharedRepository 1
Now lets clone the new repo from another computer, a Windows machine with WSL, which has had
/etc/fstab
augmented with the necessary incantation to mount my home directory on gojira
on the WSL file system.
See Mounting Shared Directories on WSL & Ubuntu for background on mounting shared drives on Ubuntu and WSL/Ubuntu. The remainder of this article assumes you have read the background article and it is fresh in your mind. Ubuntu on WSL is just a little bit different from native Ubuntu in how shared directories are mounted. The very small difference is quite important to get right.
mslinn@bear ~ sudo mount /mnt/gojira_mslinn
mslinn@bear ~ ls /mnt/gojira_mslinn/test_repo.git/ HEAD* config* description* hooks/ info/ objects/ refs/
mslinn@bear ~ git clone /mnt/gojira_mslinn/test_repo.git Cloning into 'test_repo'... warning: You appear to have cloned an empty repository. done.
You can open up the permissions such that anyone with access can update the repository.
This is the simplest configuration.
There are three equivalent ways of specifying this value:
‑‑shared=all
, ‑‑shared=world
and
‑‑shared=everybody
.
mslinn@gojira ~ mkdir --mode=g+s test_repo2.git
mslinn@gojira ~ sudo chgrp git_access test_repo2.git
mslinn@gojira ~ git init --bare --shared=everybody test_repo2.git Initialized empty shared Git repository in /home/mslinn/test_repo2.git
mslinn@gojira ~ cd test_repo2.git
mslinn@gojira test_repo.git git config core.sharedRepository 2
We can clone test_repo2.git
from the other computer as before.
The mount at /mnt/gojira_mslinn
is assumed to still be in place.
mslinn@bear ~ ls /mnt/gojira_mslinn/test_repo2.git HEAD* config* description* hooks/ info/ objects/ refs/
mslinn@bear ~ git clone /mnt/gojira_mslinn/test_repo2.git Cloning into 'test_repo2'... warning: You appear to have cloned an empty repository. done.
receive.denyCurrentBranch
You could set the receive.denyCurrentBranch
configuration variable
to ignore
or updateInstead
.
While this does nothing for a null Git LFS server, it eliminates a warning that is meaningless for this configuration.
We will see this in action later.
new_bare_repo Creation Script
The new_bare_repo
script creates a bare Git repository with --shared=all
.
Installation instructions are provided in Git LFS Scripts.
#!/bin/bash function help { if [ "$1" ]; then echo "$1"; fi echo "$(basename "$0") - Create a new bare Git repository. Syntax: $(basename "$0") /path/to/new/repo.git Normally this script would be run on a Git server, because that is where bare Git repositories normally live. A new Git repository will be created in /path/to/new/repo.git, which should not already exist. The SGID permission for the new Git repository will be set for group git_access, which is created if it does not exist. The parent directory (/path/to/new/) will be created if it does not already exist. The name of the repo must not contain spaces. If the specified name does not end with a .git suffix, the suffix is appended. Git configuration parameter 'receive.denyCurrentBranch' is set to ignore. " exit 1 } function configure { git config receive.denyCurrentBranch ignore } function initialize { mkdir --mode=g+s "$REPO_PATH/$REPO_NAME" sudo chgrp git_access $REPO_NAME git init --bare --shared=everybody "$REPO_PATH/$REPO_NAME" } if [ -z "$1" ]; then help; fi if [ "$(getent group admin)" ]; then sudo groupadd git_access fi export REPO_PATH="$( dirname "$1" )" export REPO_NAME="$( basename "$1" )" if [[ "$REPO_NAME" != *.git ]]; then export REPO_NAME="$REPO_NAME.git"; fi if [ -f "$REPO_PATH/$REPO_NAME" ]; then help "Error: '$REPO_PATH/$REPO_NAME' already exists."; fi cd "$REPO_PATH" || exit 1 initialize "$1" cd "$REPO_PATH/$REPO_NAME" || exit 3 configure
This is the help message:
mslinn@gojira ~ $ new_bare_repo new_bare_repo - Create a new bare git repository. Syntax: new_bare_repo /path/to/new/repo A new git repository will be created in /path/to/new/repo, which should not already exist. The parent directory (/path/to/new/) must already exist. The name of the repo must not contain spaces. After creation, a new environment variable will be created in /etc/environment. The name that you specified ('repo' in the above example) will be used for the name of a new environment variable, set to the path that you specified.
Blow-By-Blow Explanation
The following commands make a bare repository in a new directory on
gojira
called $REPO_HOME
.
The repository will be open to all users that can access the server.
You do not need to type all of this out; the next section
shows how to use the above script to accomplish the same objective.
In the following console session, an environment variable is created,
called REPO_HOME
that points to the directory
that will contain the bare git repository.
I placed the definition for REPO_HOME
in the
/etc/environment
directory so it is available to every shell, even cron
jobs.
I used the
sudo tee -a
command
to append to /etc/environment
,
which is owned by root
.
The contents of /etc/environment
are then incorporated into the
console session with the source
command.
mslinn@bear ~ $ ssh gojira
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-35-generic x86_64)
mslinn@gojira ~ $ REPO_HOME=$HOME/eval_repos
mslinn@gojira ~ $ echo "REPO_HOME=$REPO_HOME" | \ sudo tee -a /etc/environment > /dev/null
mslinn@gojira ~ $ REPO_NAME=repo1.git
Now we can create the bare Git repository called $REPO_NAME
in
the $REPO_HOME
directory according to the information in the
previous section.
mslinn@gojira eval_repos $ git init --bare --shared=all "$REPO_HOME/$REPO_NAME" Initialized empty shared git repository in /home/mslinn/eval_repos/repo1.git
mslinn@gojira eval_repos $ mkdir --mode=g+s "$REPO_HOME/$REPO_NAME"
mslinn@gojira eval_repos $ sudo chgrp git_access "$REPO_HOME/$REPO_NAME"
mslinn@gojira ~ $ git init --bare --shared=everybody "$REPO_HOME/$REPO_NAME"
mslinn@gojira ~ $ cd "$REPO_HOME/$REPO_NAME"
mslinn@gojira repo1.git $ git config receive.denyCurrentBranch ignore
Alright, the bare Git repository has been created. Let’s admire our handiwork:
mslinn@gojira bare_repo $ tree -d . ├── branches ├── hooks ├── info ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 9 directories
Using new_bare_repo
Instead of typing out the commands in the previous section,
you can create a bare Git repository by using the new_bare_repo
script:
mslinn@gojira ~ $ mkdir -p ~/eval_repos && cd ~/eval_repos
mslinn@gojira ~ $ new_bare_repo repo1.git Initialized empty shared Git repository in /home/mslinn/eval_repos/repo1.git The new repository contains the following subdirectory tree . ├── hooks ├── info ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags
9 directories All done! The bare repository was created in '/home/mslinn/eval_repos/repo1.git'.
Choosing a (Client-Side) Protocol
The Git remote.origin.url
configuration setting can be set to any suitable protocol,
including ssh
and both flavors of the
local protocol.
While you could theoretically use the http
or https
protocols,
it would be unusual for a webserver to be configured on a local server for this purpose;
however, this would work.
SSH is the most flexible protocol and is well understood.
The local protocol can be expressed two ways.
In the following two examples, /path/to/bare/
is a locally accessible path on a user machine.
This path could originate from a locally mounted file system,
as discussed in Mounting Shared Directories on WSL & Ubuntu.
mslinn@bear ~ $ sudo mount /mnt/gojira_mslinn
mslinn@bear ~ $ ls /mnt/gojira_mslinn/test_repo.git HEAD* config* description* hooks/ info/ objects/ refs/
file:///gojira_mslinn/path/to/bare/repo.git
Note the 3 slashes after file:
.
mslinn@bear ~ $ git clone file:///mnt/gojira_mslinn/test_repo.git test1 Cloning into 'test1'... warning: You appear to have cloned an empty repository.
/gojira_mslinn/path/to/bare/repo.git
mslinn@bear ~ $ git clone /mnt/gojira_mslinn/test_repo.git test2 Cloning into 'test2'... warning: You appear to have cloned an empty repository.
If you are unaware of the restrictions of Git’s local protocol implementation, or the differences between the various flavors of the local protocol, please read The File URI Schema And The Local Protocol. That same article also discusses UNC paths.
The Git repository on the server is protocol-independent. Multiple users can use different protocols to access it.
In contrast, the Git client must establish and save the protocol that will be used by this instance of the Git repository to access a specific remote Git repository.
Cloning On the Same Machine
On my server gojira
, the file system containing the the bare repo is of course already mounted.
The Git repository can be cloned from another local directory on gojira
like this:
mslinn@gojira tmp $ git clone file:///home/mslinn/bare_repo.git Cloning into 'bare_repo'... warning: You appear to have cloned an empty repository.
Also like this:
mslinn@gojira tmp $ git clone /home/mslinn/bare_repo.git Cloning into 'bare_repo'... warning: You appear to have cloned an empty repository.