gitrecipes: the Git cookbook

Bare vs. non-bare repositories

So what's that bare/non-bare stuff all about and why is it making your life complicated? Well, lend me five minutes of your time and I'll teach you to love it (or at least I will have tried).

You already know non-bare repositories

A non-bare repository is what you have already worked with: it's a directory with files in it that you work on (we call this the working tree), and a special .git directory that contains git's magic metadata (and all version history).

So far, so good. But what about...

Bare repositories?

Bare simply means "just that .git directory". In other words, we skip the whole working tree business. A bare repository contains all the things you'd normally see in .git, and nothing else. The obvious conclusion is that you can't easily edit files in a bare repository.

How is that useful?

I'm glad you asked! It's for repositories that don't need a working tree. That's most often the case if the repository lives on a server and is pretty much only used for pushing to and fetching/pulling from.

Generally, if you want to host a central repository of some kind, you'll always want to make it a bare one. It's easy to create a bare repository:

git init --bare

You'll often want to use --shared, too, but that's beyond the scope of this article thingy here.

"Okay, but what's that I hear about not pushing into non-bare repositories?"

That's where it gets a bit more involved, I'm afraid. Basically, when you push into a repository that has a working tree, the files in the working tree won't be updated automatically (only their history metadata will). This is a safety precaution of git, since it assumes the files could have been changed... and combining the new changes with the ones in your working tree could lead to conflicts, and you absolutely don't want push to magically create conflicted files in the target repository. And if you actually do want to do that, we'll just call you crazy and burn you at the stake, because we git folks are very mean.

Even if you were okay with an out-of-date working tree, chances are that things will get very confusing. After a push with unchanged working tree, you'll still have the old files in your working tree (and index), so git diff --cached will make it look like you actually edited all the files so they are precisely what they were before the push! Scary.

(Side note: all this applies only if the branch you are pushing to is actually checked out. If it isn't, there's nothing to update anyway.)

"At this point you better present some nifty piece of magic!"

So suppose that you really, really, want to have a remote repository that automatically updates its working tree when pushed to. There is a simple solution for the simple case, when only one person pushes to the repository: just get this post-update hook script (I stole the link from the official git FAQ entry about this very problem), put it into /your/repo/hooks and make it executable. This hook will try hard to update the working tree even in the presence of uncommitted changes (and it works perfectly if there are no changes).

"That script is broken, it says it can't find git-something!"

Easy to fix. Edit the script and replace all occurences of "git-" with "git " (note the space at the end). That's one more replacement than you need but it's simple and gets the job done.

What if multiple users push to that repository, though?

This is where you have to get creative.

If you can simply give all of those users a common system group and assign all of the files to that group, it's simple. Just add something like this to the hook script (after the line that says git-reset --hard HEAD)):

chgrp -R <group> `git rev-parse --show-cdup`
chmod -R g=u `git rev-parse --show-cdup`

In other cases you either need to use POSIX ACLs (which is a quite involved and separate topic, so I won't cover it here) or set up some elaborate scheme where the hook script delegates its work to one fixed user (so the question of permissions is sidestepped).

Is there another solution, perhaps one with tradeoffs?

You could write a cronjob that periodically throws away all changes in that working tree. That way you get rid of permission problems, but that won't cut it if you have changes in the working tree of that repository (because they'll simply vanish), or if you need changes to get into the working tree instantly.

This article was written by Jan Krüger. See history of changes.