283 lines
11 KiB
Markdown
283 lines
11 KiB
Markdown
# F=vref 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.
|
|
|
|
And here's one you can use right away to catch duplicate pubkeys in the admin
|
|
repo; just copy `contrib/VREF/gl-VREF-DUPKEYS` to `src` and upgrade gitolite,
|
|
then add this to conf/gitolite.conf:
|
|
|
|
repo gitolite-admin
|
|
# ... normal rules ...
|
|
- VREF/DUPKEYS = @all
|
|
|
|
(If you've been using the existing `contrib/update.detect-dup-pubkeys` as a
|
|
secondary update hook till now, you may want to switch to this method.)
|
|
|
|
----
|
|
|
|
[[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.
|
|
|
|
There's another advantage to doing it this way: a VREF can choose to simply
|
|
die if things look bad, and it will have the same effect, assuming you used
|
|
the VREF only in [deny][] rules.
|
|
|
|
This in turn means any existing update hook can be used as a VREF *as-is*, as
|
|
long as it (a) prints nothing on success and (b) dies on failure. See the
|
|
email-check and dupkeys examples later.
|
|
|
|
(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.
|
|
|
|
## VREFs shipped in contrib
|
|
|
|
### number of new files
|
|
|
|
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 below (`NO_SIGNOFF` applies only to, and thus
|
|
implies, `NEWFILES`):
|
|
|
|
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
|
|
|
|
### advanced filetype detection
|
|
|
|
Note: this is more for illustration than use; it's rather specific to one of
|
|
the projects I manage but the idea is the important thing.
|
|
|
|
Sometimes a file has a standard extension (that cannot be 'gitignore'd), but
|
|
it is actually automatically generated. Here's one way to catch it:
|
|
|
|
- VREF/FILETYPE/AUTOGENERATED = @all
|
|
|
|
You can look at `contrib/VREF/gl-VREF-FILETYPE` to see how it handles the
|
|
'AUTOGENERATED' option. You could also have a more generic option, like
|
|
perhaps BINARY, and handle that in the FILETYPE vref too.
|
|
|
|
### checking author email
|
|
|
|
Some people want to ensure that "you can only push your own commits".
|
|
|
|
If you force it on everyone, this is a very silly idea (see "Philosophical
|
|
Notes" section of `contrib/VREF/gl-VREF-EMAIL_CHECK`).
|
|
|
|
But there may be value in enforcing it just for the junior developers.
|
|
|
|
The neat thing is that the existing `contrib/update.email-check` was just
|
|
copied to `contrib/VREF/gl-VREF-EMAIL_CHECK` and it works, because VREFs get
|
|
the same first 3 arguments and those are all that it cares about. (Note: you
|
|
have to change one subroutine in that script if you want to use it)
|
|
|
|
### catching duplicate pubkeys
|
|
|
|
We covered this as a teaser example at the start.
|
|
|
|
## other ideas -- code welcome!
|
|
|
|
### "no non-merge first-parents"
|
|
|
|
Shruggar on #gitolite wanted this. Possible code to implement it would be
|
|
something like this (untested)
|
|
|
|
[ -z "$(git rev-list --first-parent --no-merges $2..$3)" ]
|
|
|
|
This can be implemented using `contrib/VREF/gl-VREF-MERGE_CHECK` as a model.
|
|
That script does what the 'in core' feature called [merge check][mergecheck]
|
|
does, although the syntax to be used in conf/gitolite will be quite different.
|
|
|
|
### other ideas for VREFs
|
|
|
|
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
|
|
* phase of the moon
|
|
|
|
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.
|