gitolite/doc/3-faq-tips-etc.mkd

304 lines
13 KiB
Markdown
Raw Normal View History

2009-08-27 11:54:23 +02:00
# assorted faqs, tips, and notes on gitolite
In this document:
* common errors and mistakes
* git version dependency
* other errors, warnings, notes...
* differences from gitosis
* two levels of access rights checking
* error checking the config file
* built-in logging
* one user, many keys
* who am I?
2009-09-16 16:22:03 +02:00
* other cool things
* developer specific branches
* design choices
* why we don't do "excludes"
### common errors and mistakes
* forgetting to suffix `.git` to the end of the reponame in the `git clone`.
This suffix is *not* used in the gitolite config file for the sake of
clarity and cleaner syntax, but don't let that fool you. It's a
convention in the git world that **bare repos** end with `.git`.
* adding `repositories/` at the start of the repo name in the `git clone`.
This error is typically made by the *admin* himself -- because he knows
what `$REPO_BASE` is set to and thinks he has to provide that prefix on
the client side also :-) In fact gitolite prepends `$REPO_BASE` when it
is required anyway, so you shouldn't do the same thing!
### git version dependency
Here's a workaround for a version dependency that the normal flow of gitolite
has.
When you edit your config file to create a new repo, and run
`src/gl-compile-conf`, gitolite creates an empty, bare repo for you.
Normally, you're expected to clone this on the client side, and start working
-- make your first commit(s), then push, etc.
However, cloning an empty repo requires a server side git version that is at
least 1.6.2. Gitolite detects this when creating a repo, and warns you.
The workaround is to use the older (gitosis-style) method on the client:
create an empty repo locally, make a commit or two, set an "origin" remote,
and then push. Something like:
mkdir my-new-project
cd my-new-project
git init
git commit --allow-empty -m 'Initial repository'
# or, if your client side git is too old for --allow-empty, just make some
# files, "git add" them, then "git commit"
git remote add origin git@gitolite-server:my-new-project.git
git push origin master:master
Once this is done, the repo is available for cloning by anyone else in the
normal way, since it's not empty anymore.
### other errors, warnings, notes...
2009-08-27 11:54:23 +02:00
* cloning an empty repo is only possible with clients greater than 1.6.2.
So at least one of your clients needs to have a recent git. Once at least
2009-09-21 11:11:37 +02:00
one commit has been made, older clients can also use it
2009-08-27 11:54:23 +02:00
* when you clone an empty repo, git seems to complain about the remote
hanging up or something. I have no idea what that is, but it doesn't seem
to hurt anything. This happens even in normal git, not just gitolite.
[Update 2009-09-14; this has been fixed in git 1.6.4.3]
2009-08-27 11:54:23 +02:00
* once in a while, if you're feeling particularly BOFH-ish, take a look at
`$GL_ADMINDIR/log` :-)
2009-09-01 16:22:06 +02:00
* if you specify a repo that is not at the top level `$REPO_BASE`, be sure
to manually create the intermediate directories first. For instance if
you specify a new repo called "a/b/c" to the config file and "compile",
the "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has
already been created
2009-09-15 08:34:15 +02:00
* if you run `git init` inside `$GL_ADMINDIR` (that is, make it a normal,
non-bare, repo), then, everytime you "compile" (run
`src/gl-compile-conf`), any changes to `conf` and `keydir` will
automatically be committed. This is a simple safety net in case you
accidentally delete the whole config or something. Also see
[4-push-to-admin.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/4-push-to-admin.mkd)
2009-09-21 11:11:37 +02:00
if you really know what you're doing and want "push to admin"
* gitweb not able to read your repos? You can change the umask for newly
created repos to something more relaxed -- see the `~/.gitolite.rc` file
2009-09-15 08:34:15 +02:00
### differences from gitosis
Apart from the big ones listed in the top level README, and subjective ones
like "better config file format", there are some small, but significant and
concrete, differences from gitosis.
#### two levels of access rights checking
Gitolite has two levels of access checks. The **first check** is what I will
call the **pre-git** level (this is the only check that gitosis has). At this
stage, the `gl-auth-command` has been invoked by `sshd`, and it knows just
three things:
* who,
* what repository, and
* what type of access (R or W)
Note that at this point no git program has entered the picture, and we have no
way of knowing what **ref** (branch, tag, etc) he is trying to update, even if
it is a "write" operation.
For a "read" operation to pass this check, the username (or `@all`) must be
mentioned on some line in the config for this repo.
For a "write" operation, there is an additional restriction: lines specifying
only `R` (read access) don't count. *The user must have write access to
**some** ref in the repo in order to pass this stage!*
The **second check** is via a git `update hook`. This check only happens for
write operations. By this time we know what "ref" he is trying to update, as
well as the old and the new SHAs of that ref (by which we can also deduce
2009-09-16 16:22:03 +02:00
whether it's a rewind or not). This is where the "per-branch" permissions
come into play.
Each refex that allows `W` access (or `+` if this is a rewind) for *this*
user, on *this* repo, is matched against the actual refname being updated. If
any of the refexes match, the push succeeds. If none of them match, it fails.
#### error checking the config file
gitosis does not do any. I just found out that if you mis-spell `members` as
`member`, gitosis will silently ignore it, and leave you wondering why access
was denied.
In gitolite, you have to "compile" the config file first (this step takes the
place of the commit+push in gitosis), and keyword typos *are* caught so you
know right away.
#### built-in logging
...just in case of emergency :-)
Let's say you gave a dev the right to rewind a branch and he went and rewound
it all the way, or pushed something drastically different on it. Now you need
to recover the commit that got wiped out.
If you'd remembered to `git config core.logAllRefUpdates` for that repo, or
globally, you'd be fine -- the reflog will tell you. Otherwise you'd be left
grubbing around in `git fsck --unreachable` a bit :-(
And even if you recover the correct commit, you'll never know *who* did it --
not unless you add a one-line patch to gitosis, plus a `post-receive` hook to
every repository.
With gitolite, there's a log file in `$GL_ADMINDIR` that contains lines like
this [I have abbreviated the SHAs for brevity in this document; the actual log
file will have all 40 characters]:
+: username reponame refs/heads/branchname d0188d1 c5c00b6
The "+" at the start indicates a non-fast forward update, in this case from
d0188d1 to c5c00b6 So d0188d1 is the one to restore! Can it get easier?
2009-08-27 11:54:23 +02:00
#### one user, many keys
I have a laptop and a desktop I need to access the server from. I have
different private keys on them, but as far as gitolite is concerned both of
them should be treated as "sitaram". How does this work?
In gitosis, the admin creates a single "sitaram.pub" containing one line for
each of my pubkeys. In gitolite, we keep them separate: "sitaram@laptop.pub"
and "sitaram@desktop.pub". The part before the "@" is the username, so
gitolite knows these two keys belong to the same person.
I think this is easier to maintain if you have to delete or change one of
those keys.
#### who am I?
As a developer, I send a file called `id_rsa.pub` to the gitolite admin. He
would rename it to "sitaram.pub" and put it in the key directory. Then he'd
add "sitaram" to the config file for the repos which I have access to.
But he could have called me "foobar" instead of "sitaram" -- as long as he
uses it consistently, it'll all work the same and look the same to me, because
the public key is all that matters.
So do I have no reason to know what the admin named me? Well -- maybe (see
next section for one possible use). Anyway how do I find out?
In gitolite, it's simple: just ask nicely :-)
$ ssh git@my.gitolite.server
PTY allocation request failed on channel 0
no SSH_ORIGINAL_COMMAND? I'm not a shell, sitaram!
2009-09-16 16:22:03 +02:00
### other cool things
#### developer specific branches
So I know what gitolite calls me. Big deal... who cares?
Here is an idea: give every developer a personal "scratch" namespace within
which she can create, rewind, or delete any branch. For example, I would own
anything under
$PERSONAL_BRANCH_PREFIX/sitaram/
The admin could set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate
this to all users. It could be something like `refs/heads/personal`, which
means all such branches will show up in `git branch` lookups and `git clone`
will fetch them. Or he could use, say, `refs/personal`, which means it won't
show up in any normal "branch-y" commands and stuff, and generally be much
less noisy.
2009-08-27 11:54:23 +02:00
Yes, I know git is all about allowing private branches, but in a corporate
environment it's not always possible to pull from a co-worker, for the same
reasons you don't have anonymous access (like the git:// protocol). A normal
developer workstation cannot do authentication, so how would they know who's
pulling? This is a perfect way to share code *without* cluttering the global
namespace, and each developer controls his/her own set of branches!
2009-08-27 11:54:23 +02:00
The amount of code needed? *One line!* I'll spend about 3x more on declaring
and initialising the new variable, and 30x more on documenting it :-)
2009-08-27 11:54:23 +02:00
**Note that a user who has NO write access cannot have personal branches**; if
you read the section (above) on "two levels of access rights checking" you'll
understand why.
For instance, in the following example, `user3` cannot push to any
`refs/heads/personal/user3/*` branches because the first level check stops him
cold:
# assume $PERSONAL = 'refs/heads/personal' in ~/.gitolite.rc
repo myrepo
RW+ master = sitaram
RW+ release = qa_guy
RW = user1 user2
R = user3
If we relax that check, *any* access becomes *write* access. Yes it will be
caught later, by the hook, but it's good practice to catch things in multiple
places.
If you want `user3` to have his own personal branch, but without write access
to any of the "real" branches (like "master", "release", etc.), just use a
dummy branch. Choose a name that will never exist in practice, or even if
someone creates it, we don't care. For example, this will get him past the
first check:
RW dummy = user3
2009-08-27 11:54:23 +02:00
Just don't *show* the user this config file; it might sound insulting :-)
### design choices
#### why we don't do "excludes"
I found an error in the example conf file. This snippet *seems* to say that
"bruce" can write versioned tags (`refs/tags/v[0-9].*`), but the other
staffers can't:
@staff = bruce whitfield martin
[... and later ...]
RW refs/tags/v[0-9].* = bruce
RW refs/tags = @staff
But that's not how the matching works. As long as any refex matches the
refname being updated, it's a "yes". So the second refex lets anyone on
`@staff` create versioned tags, not just Bruce.
One way to fix this is to allow "excludes" -- some changes in syntax, combined
with a rigorous, ordered, interpretation would do it.
But if you're ever played with squid ACLs, or the include/exclude rules for
rsync, or rdiff-backup, or even git's own ignore mechanism, you'll see why I
won't do this. It bloats the code and the docs, and, despite all the docs,
*still* confuses people, which may then *reduce* security!
Squid, rsync, gitignore, and all *need* the feature and so tolerate all this;
but we don't need it. All we need to do is make the refexes *disjoint* in
what they match (i.e., ensure that no refname can be matched by more than one
refex):
RW refs/tags/v[0-9].* = bruce
RW refs/tags/staff/ = @staff
In general, you probably want to control the refnames writable by devs anyway,
if at least to maintain some sanity, so being forced to make the refexes
disjoint is not a big problem. Here's an example: only the `project_lead` can
make arbitrarily named refs, while the rest have to stay within their assigned
namespaces:
RW+ = project_lead
RW refs/tags/qa/ = @qa_team
RW bugID/ = @dev_team
RW trac/ = @dev_team
The lack of overlap between refexes ensures ***no confusion*** in specifying,
understanding, and ***auditing***, what is allowed and what is not.
And in security, "no confusion" is a good thing :-)