From ed85bf3c0856fb6ae1166799817ddf0db3ed0517 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sat, 25 Feb 2012 19:59:49 +0530 Subject: [PATCH] vref: docs --- doc/virtual-refs.mkd | 222 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 doc/virtual-refs.mkd diff --git a/doc/virtual-refs.mkd b/doc/virtual-refs.mkd new file mode 100644 index 0000000..44f618f --- /dev/null +++ b/doc/virtual-refs.mkd @@ -0,0 +1,222 @@ +# virtual refs + +The traditional method of adding additional checks to gitolite was to use [hook +chaining][hookchaining], which basically means you put your code in a new +hook called `update.secondary`. + +But this is not ideal -- it runs for all repos and all users, which you may +not want. + +Here's a teaser example for a better way. Copy `contrib/VREF/gl-VREF-COUNT` +to `src`, install/upgrade gitolite, then add this to your admin conf: + + repo r1 + RW+ = lead_dev dev2 dev3 + - VREF/COUNT/9 = dev2 dev3 + - VREF/COUNT/3/NEWFILES = dev2 dev3 + +Now dev2 and dev3 cannot push changes that affect more than 9 files at a time, +nor those that have more than 3 new files. It doesn't affect any other repo, +nor does it affect the lead developer. + +---- + +[[TOC]] + +---- + +## rule matching recap + +You won't get any joy out of this if you don't understand at least +[refex][]es, [deny][] rules, and [NAME][]-based restrictions. + +And here's an important **warning**. Recall the "summary" from [this][aac] +document: + +> The first matching refex that has the permission you're looking for (`W` +> or `+`) **or a minus (`-`)**, results in success **or failure, +> respectively**. A fallthrough **also** results in failure. + +Note the last sentence in that summary. **That sentence does NOT apply** to +virtual refs; a fallthru results in success here. You'll see why this is more +convenient as you read on. + +---- + +## what is a virtual ref + +A ref like `refs/heads/master` is the main property of a push that gitolite +uses to make its yes/no decision. I call this a "real" ref. + +Any *other* property of the push that you want to use to help in the decision +is therefore a *virtual* ref. This could be a property that git knows about, +like in the example above, or comes from outside git like, say, the current +time; see examples section later for some ideas. + +(To be honest, [NAME][]-based restrictions should also be called virtual refs, +but they've been in gitolite forever so they're grandfathered in, and the +material in this document does not apply to them). + +## fallthru is success here + +Notice that you didn't need to add an `RW+ VREF/...` rule for user `lead_dev` +in our example. This is the opposite of what we do in a similar situation +[here][NAME]. This section explains why. + +**Virtual refs are best used as additional "deny" rules**, using extra checks +that core gitolite cannot perform. + +Making fallthru be a "fail" forces you to add rules for all users, instead of +just the ones who should have those extra checks. Worse, since every virtual +ref involves calling an external program, many of these calls may be wasted. + +(Yes, I should have used this logic for [NAME][] also. What can I say -- I am +older and wiser now. Sadly, we can't change [NAME][] without breaking a lot +of existing configs, so it stays like a real ref -- fallthru is failure). + +## how it works -- overview + +Briefly, a refex starting with `VREF/FOO` triggers a call to a program called +`gl-VREF-FOO` in `$GL_BINDIR`. + +That program is expected to print zero or more lines to its STDOUT; each line +is taken by gitolite as a new "ref" to be matched against all the refexes for +this user in the config. Including, the refex that caused the vref call, of +course. + +Normally, you send back the refex itself, if the test determines that the rule +should be matched, otherwise nothing. So, in our example, we print +`VREF/COUNT/9` if the count was indeed greater than 9. Otherwise we just +exit. + +## how it works -- details + + * the VREF code is only called if there are any VREF rules for the user, + which means when the lead developer pushes, the VREF is not called at all. + + * when dev2 or dev3 push, gitolite first checks the real ref + (`ref/heads/master` or whatever), then any [NAME][] rules. So far this is + normal processing. + + After this it looks at VREF rules, and calls an external program for every + one it finds. Specifically, in a line like + + - VREF/COUNT/3/NEWFILES = user + + COUNT is the vref name, so the program called is + `$GL_BINDIR/gl-VREF-COUNT`. + + The program is passed **nine arguments** in this case (see next section + for details). + + * the script can print anything it wants to STDOUT; the first word in each + such line will be treated as a virtual ref to be matched against all the + rules, while the rest, if any, is a message to be added to the standard + "...DENIED..." message that gitolite prints if that refex matches. + + Usually it only makes sense to either + + * print nothing -- if you don't want the rule that triggered it to match + (ie., whatever condition being tested was not violated; like if the + count of changed files did not exceed 9, in our earlier example) + * print the refex itself (plus an optional message), so that it matches + the line which invoked it + +### arguments passed to the vref code + + * arguments **1, 2, 3**: the 'ref', 'oldsha', and 'newsha' that git passed + to the update hook (see 'man githooks') + + * arguments **4 and 5**: the 'oldtree' and 'newtree' SHAs. These are the + same as the oldsha and newsha values, except if one of them is all-0. + (indicating a ref creation or deletion). In that case the corresponding + 'tree' SHA is set (by gitolite, as a courtesy) to the special SHA + `4b825dc642cb6eb9a060e54bf8d69288fbee4904`, which is the hash of an empty + tree. + + (None of these shenanigans would have been needed if `git diff $oldsha + $newsha` would not error out when passed an all-0 SHA.) + + * argument **6**: the attempted access flag. Typically `W` or `+`, but + could also be `C`, `D`, or any of these 4 followed by `M`. If you have to + ask what they mean, you haven't read enough gitolite documentation to be + able to make virtual refs work. + + * argument **7**: is the entire refex; in our example + `VREF/COUNT/3/NEWFILES`. + + * arguments **8 onward**: are the split out (by `/`) portions of the refex, + excluding the first two components. In our example they would be `3` + followed by `NEWFILES`. + +Yes, argument 7 is redundant if you have 8 and 9. It's meant to make it easy +to write vref scripts in any language. See script examples in source. + +## what (else) can the vref code pass back + +Actually, the vref code can pass anything back; each line in its output will +be matched against all the rules as usual (with the exception that fallthru is +not failure). + +For example, you could have a ruleset like this: + + repo r1 + # ... normal rules ... + + - VREF/TIME/WEEKEND = @interns + - VREF/TIME/WEEKNIGHT = @interns + - VREF/TIME/HOLIDAY = @interns + +and you could write the TIME vref code (gl-VREF-TIME) to passback any or all +of the times that match. Then if an intern tried to access the system, each +rule would trigger a call to gl-VREF-TIME. + +The script should send back any of the applicable times (even more than one, +or none at all, as the case may be). So even if it was invoked using the +first rule, it might pass back (to gitolite) a virtual ref saying +'VREF/TIME/HOLIDAY', which would promptly cause the request to be denied. + +## other possible examples + +I use these. Don't analyse the numbers -- I fully expect to tune them as time +passes; the idea is the main thing. + + * if a dev pushes more than 2 *new* files, the top commit needs to have a + signed-off by line in its commit message. For example if he has 4 new + files this text should be: + + 4 new files signed-off by: + + The config entry for this is this (NO_SIGNOFF applies only to, and thus + implies, NEWFILES). This applies to everyone except me ;-) + + RW+ VREF/COUNT/2/NO_SIGNOFF = sitaram + - VREF/COUNT/2/NO_SIGNOFF = @all + + Notice how the refex in both cases is *exactly* the same. If you make it + different (even change the number on my access line), things won't work. + + * junior devs can't push more than 10 new files, even with a signed-off by + line: + + - VREF/COUNT/10/NEWFILES = @junior_devs + + * we also need to catch auto-generated files that have filename extensions + that cannot be ".ignore" + + - VREF/FILETYPE/AUTOGENERATED = @all + +Here are some more ideas: + + * number of commits (`git rev-list --count $old $new`) + * number of binary files in commit (currently I only know to count + occurrences of ` Bin ` in the output of `git diff --stat` + * number of *new* binary files (count ` Bin 0 ->` in `git diff --stat` + output) + * time of day/day of week (see example snippet somewhere above) + * IP address + +Note that pretty much anything that involves `$oldsha..$newsha` will have to +deal with the issue that when you push a new tag or branch, the "old" part +is all 0's, and unless you consider `--all` existing branches and tags it +becomes meaningless in terms of "number of new files" etc.