A git user may add or modify a remote in their repository via the git remote
subcommand. Most commonly (e.g. in the case the user has cloned from a popular git service such as GitLab or GitHub) git repositories have a remote named origin
which is the default remote operated on. We can add extra URLs that point to other servers hosting a copy of the repository via git remote set-url --add origin <url>
.
Alternatively, one can also manually edit .git/config
in the root of the repository. An example of what may be in the config regarding the remote origin is
[remote "origin"]
url = [email protected]:<user>/<repo_name>.git
fetch = +refs/heads/*:refs/remotes/origin/*
E.g. if we wanted to mirror the above config to GitLab, we would execute git remote set-url --add origin [email protected]:<user>/<repo_name>.git
in the repository. The config would then read similar to:
[remote "origin"]
url = [email protected]:<user>/<repo_name>.git
fetch = +refs/heads/*:refs/remotes/origin/*
url = [email protected]:<user>/<repo_name>.git
and git remote -v
will list the extra url:
$ git remote -v
origin [email protected]:<user>/<repo_name>.git (fetch)
origin [email protected]:<user>/<repo_name>.git (push)
origin [email protected]:<user>/<repo_name>.git (push)
One can easily set a machine on their local network that they have SSH access to as a remote, and consequently push/pull from that machine.
For the following example code blocks, let's assume there is a machine on the LAN which resolves through local DNS with the hostname gitserver
. Furthermore, let's assume there is a user on this system named git
who accepts one of our public keys as an authorized key (and thus, we are able to authenticate and SSH into the system as git
with ssh git@gitserver
).
In order for this to work normally, we must first SSH into the machine and create a repository somewhere. Let's make a repo named example
in /git
. First we must create a directory in /git
that is suffixed with .git
. Then we must initialize this directory as a git repository in order for it to accept our git commands via SSH:
git@gitserver$ mkdir -p /git/example.git
git@gitserver$ cd /git/example.git
git@gitserver$ git init --bare
Initialized empty Git repository in /git/example.git/
Note that --bare
will not create a .git
hidden directory in this directory like git init
would. The git-related files/folders normally in .git
will exist one directory up, in the directory itself. In this case, instead of /git/example.git/.git/config
it will be /git/example.git/config
Now our repository example
on gitserver
is ready to be used as a remote url. Logging out of gitserver
and back on our original machine we can now run:
$ git remote set-url --add origin git@gitserver:/git/example.git
and then if we git push --verbose
we should see something like:
Pushing to github.com:<user>/example.git
To github.com:<user>/example.git
= [up to date] main -> main
updating local tracking ref 'refs/remotes/origin/main'
Everything up-to-date
Pushing to gitlab.com:<user>/example.git
To gitlab.com:<user>/example.git
= [up to date] main -> main
updating local tracking ref 'refs/remotes/origin/main'
Everything up-to-date
Pushing to gitserver:/git/example.git
Enumerating objects: 136, done.
Counting objects: 100% (136/136), done.
Delta compression using up to 4 threads
Compressing objects: 100% (125/125), done.
Writing objects: 100% (136/136), 123.27 MiB | 3.16 MiB/s, done.
Total 136 (delta 7), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (7/7), done.
Note the Pushing to gitserver:/git/example.git
One thing to emphasize here is we would need to do this procedure of git init
ing every repository we would ever want to be git push
ed to our local mirror server. This may be very minor in the grand scheme of things, and you might even write a git alias or something similar which runs the mkdir
and git init
in a brief ssh
invocation. However, it is possible to do this server side and therefore any potential clients (for example, you but on a different machine on your network) will enjoy this automation.
As hopefully made obvious by this point, we're using SSH as the underlying protocol. As such, there are a few mechanisms we can take advantage of to achieve this in a standard POSIX implementation of SSH. Primarily we will use the command="..."
prefix which can be placed in the authorized keys of the server prior to our public key (in our example case, /home/git/.ssh/authorized_keys
). This is the command that is used for any SSH connection to this user (and should end with launching a login shell if you are seeking to not distrub the default behavior, otherwise if not it will disable any interactive shell access to the system).
- Note: not used here, but
~/.ssh/rc
on the server-side user can be used for SSH-only interactive shell environment configuration
More saliently, in ~/.ssh/authorized_keys
of the git
users on gitserver
we should have a line as such which contains our SSH public key contents:
command="/path/to/command" ssh-rsa AAAA...
The specific SSH command script is included as a separate file with this README. It will intercept SSH-invoked calls to git-upload-pack
and git-receive-pack
and check the given filesystem path argument. If the argument is not absolute, it will modify it to point the repository to git
's home directory. Then, it will check if the directory exists--if not, it will create it and run git init --bare
for us. Some important details here are:
- This will only happen if done via an authenticated SSH call, so any risk of malicious actors exploiting this is equal to them exploiting your underlying SSH configuration.
- The command used in
command=...
in authorized_keys should not print anything to stdout or stderr manually. This can and likely will introduce errors in the SSH text protocol being used bygit
, resulting in nogit
actions being possible. The included script makes sure not to print anything unexpected as to cause errors in the protocol.
I have named this command pre-receive-push
to fit the naming conventions of standard git
hooks such as pre-fetch
and pre-receive
.
I have also placed this command in the global git-hook directory as configured in git global config core.hooksPath
(which is /home/git/.hooks/
in my configuration) for the git
user on gitserver
to keep this hook logically grouped with other potential global git hooks. So specifically, the absolute path of this script is /home/git/.hooks/pre-receive-push
with this string configured in /home/git/.ssh/authorized_keys
:
command="/home/git/.hooks/pre-receive-push" ssh-rsa AAAA...
So after creating this script (and ensuring it has execution permissions) and adding it to authorized_keys, if we test pushing a brand new repository named test
from our local machine we should see our git mirror server accept the push without any errors and without requiring us to explicitly SSH and run git init
:
$ cd $(mktemp -d) # make a temporary directory as a workspace
$ git init
Initialized empty Git repository in /tmp/tmp.P2RNFycF2m/.git/
$ git remote add origin git@gitserver:test
$ touch test
$ git add test
$ git commit -m "initial"
[main (root-commit) 04fe861] test
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 test
$ git push --set-upstream origin main
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 206 bytes | 206.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: Reinitialized existing Git repository in /home/git/test/
To gitserver:test
* [new branch] main -> main
Voilà! Now we can conveniently call git remote set-url --add git@gitserver:<repo_name>
in any git repository we wish to mirror on our local server and the server will automatically handle the rest.