line34
Coding, Scripting, Administration

Distributed Git

Git is decentralized. Sometimes that makes a difference.

Git is a distributed version control system. Usually that doesn’t matter too much to me. I pull from and push to some repository on github or gitlab or wherever, and that’s that. But once in a while it comes in handy that you can not only push to or pull from a central repository but to and from any clone of that repository.

Push Out

Usually I commit on my local computer and push to a common repository. But sometimes I make a fix on a test server and commit there. However, pushing from there sometimes fails:

s-customer@customer50$ git push
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

Maybe the user on the test server actually doesn’t have permission to push, which may be sensible from a security standpoint. One option to work around this could be to use ssh agent key-forwarding. However, that has some security implications (see the ssh man page). Luckily, there is another option: I can first pull the commit from the test server to my local checkout:

reinhardt@joy:$ git pull customer50:/srv/s-customer/customer
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), 445 bytes | 148.00 KiB/s, done.
From customer50:/srv/s-customer/customer
 * branch            HEAD       -> FETCH_HEAD
Updating 8f6be30..e8aa597
Fast-forward
 scripts/generate_pdfs.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

That’s nice, because I want that commit on my local machine anyway. And now I can push from there to github as usual.

Pull In

Or the other way around: I may want to update a checkout on a server that for some reason cannot pull from github (maybe an over-cautious firewall rule). That’s a tad trickier - to pull from my local checkout I would have to make a connection from the server to my local machine, but my local computer is not directly reachable from the internet (again, security reasons). I also can’t push from local to the checkout on the server without risking messing up the HEAD, because a branch is checked out on the server:

reinhardt@joy:$ git push customer50:/srv/s-customer/customer/
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 773 bytes | 386.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
remote: error: refusing to update checked out branch: refs/heads/generate-pdfs
remote: error: By default, updating the current branch in a non-bare repository
remote: is denied, because it will make the index and work tree inconsistent
remote: with what you pushed, and will require 'git reset --hard' to match
remote: the work tree to HEAD.
remote:
remote: You can set the 'receive.denyCurrentBranch' configuration variable
remote: to 'ignore' or 'warn' in the remote repository to allow pushing into
remote: its current branch; however, this is not recommended unless you
remote: arranged to update its work tree to match what you pushed in some
remote: other way.
remote:
remote: To squelch this message and still keep the default behaviour, set
remote: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To customer50:/srv/s-customer/customer/
 ! [remote rejected] generate-pdfs -> generate-pdfs (branch is currently checked out)
error: failed to push some refs to 'customer50:/srv/s-customer/customer/'

It may work to tweak the receive.denyCurrentBranch option (see git config). Or I can make a bare clone of the repository.

A bare repository is basically a clone that doesn’t have a working tree (see also git clone). All the repositories on github, gitlab, etc. are usually bare, so you can push to them without this issue.

Passing the --bare option to git clone creates a bare repository:

reinhard@customer50$ cd /tmp

reinhard@customer50$ git clone --bare /srv/s-customer/customer
Cloning into bare repository 'customer.git'...
done.

I can push there from my local computer:

reinhardt@joy:$ git push customer50:/tmp/customer.git/
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 773 bytes | 773.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
To customer50:/tmp/customer.git/
   e8aa597..46e8c3c  generate-pdfs -> generate-pdfs

Then there’s one more step left: Pull from the bare repository to the actual checkout on the server:

s-customer@customer50$ git pull /tmp/customer.git/
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), 753 bytes | 753.00 KiB/s, done.
From /tmp/customer
 * branch            HEAD       -> FETCH_HEAD
Updating e8aa597..46e8c3c
Fast-forward
 scripts/generate_pdfs.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

And now the server is up-to-date.

If you’re not doing this kind of thing often, go and play around a little. Make a few clones, bare or non-bare, on some servers, and push and pull around. It’s fun, and it can come in handy in certain situations.

27th March 2024Filed under: git   version control