vref: docs
This commit is contained in:
parent
e1a78fdbbc
commit
ed85bf3c08
222
doc/virtual-refs.mkd
Normal file
222
doc/virtual-refs.mkd
Normal file
|
@ -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: <top commit author's email>
|
||||||
|
|
||||||
|
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.
|
Loading…
Reference in a new issue