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

arguments passed to the vref code

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:

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.