The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# PODNAME: TUTORIAL
# ABSTRACT: Gentle tutorial for Git::Hooks users

__END__

=pod

=encoding UTF-8

=head1 NAME

TUTORIAL - Gentle tutorial for Git::Hooks users

=head1 VERSION

version 1.6.9

=head1 USER TUTORIAL

As a Git user you may be interested in enabling some hooks for your
local Git repositories. In particular, you may be interested in
guaranteeing that the same policies that are being enforced by the
remote repositories you push to are enforced earlier when you commit
locally, so that you can avoid an onerous round trip to the common
repository.

=head2 User Driver Script

Git::Hooks only need a single script to drive all hooks implemented by
yourself or by the plugins you enable. If you do not need to create
your own hooks, but want to use just the ones that come with
Git::Hooks plugins, you can use a shared script like this for all your
local repositories:

    #!/usr/bin/env perl
    use Git::Hooks;
    run_hook($0, @ARGV);

As a user, I save this script as F<~/bin/githooks.pl> under my $HOME
directory. Just do not forget to make it executable!

If you invoke the driver script directly from the inside of a Git repository
it should do nothing but exit normally:

    $ cd /my/git/repo
    $ ~/bin/githooks.pl
    $ echo $?

If you invoke it from the outside though, it should die:

    $ cd ..
    $ ~/bin/githooks.pl
    fatal: Not a git repository: . at /usr/share/perl5/Git.pm line 210.

=head2 User Hook Links

Now you must create symbolic links under the F<.git/hooks>
directory of your repositories pointing to the common script. So, for
example, if you want to enable some C<pre-commit> and some
C<commit-msg> hooks, you would do this:

    $ cd .../.git/hooks
    $ ln -s ~/bin/githooks.pl pre-commit
    $ ln -s ~/bin/githooks.pl commit-msg
    $ ln -s ~/bin/githooks.pl pre-rebase

=head3 Automating the creation of links

However, doing it manually for every repository is cumbersome and
prone to mistakes and neglect. Fortunately, there is a better way. In
order to make it easy to setup your hooks, it's useful to create a
repository template for Git to use when you perform a C<git init> or a
C<git clone>.

In Ubuntu, Git's standard repository template resides in
F</usr/share/git-core/templates>. If you can't find it there, read the
C<TEMPLATE DIRECTORY> section of the C<git help init> manual to see
where is your Git's default template directory.

You may customize one for you like this:

    $ cp -a /usr/share/git-core/templates ~/.git-templates
    $ cd ~/.git-templates/hooks
    $ rm *
    $ for i in commit-msg post-commit pre-commit pre-rebase
    > do ln -s ~/bin/githooks.pl $i
    > done

These commands copy the default template directory to
F<~/.git-template> (you may choose another directory), removes all
sample hooks and creates symbolic links to the Git::Hooks driver
script which we created above for four hooks: C<commit-msg>,
C<post-commit>, C<pre-commit>, and C<pre-rebase>. These are all the
hooks I'm interested in locally. If you're setting this up for a Git server
you'll want to create links for other hooks, such as C<pre-receive> or
C<update>.

You must tell Git to use your repository template instead of its
default. The best way to do it is to configure it globally like this:

    $ git config --global init.templatedir ~/.git-templates

Now, whenever you C<git init> or C<git clone> a new repository, it
will automatically be configured to use Git::Hooks.

=head2 User Configuration

By default Git::Hooks does nothing. At the very least, it must be
configured to enable some plugins and configure them to your
taste. You should read the plugins's documentation to understand them
and decide which ones you would like to enable globally and which ones
you would like to enable locally for particular repositories.

Here I show my personal preferences. You are encouraged to make your
own variations.

This is what I have in my global Git configuration (F<~/.gitconfig>):

    [githooks]
            plugin = CheckLog
            plugin = CheckRewrite
            abort-commit = 0
    [githooks "checklog"]
            title-max-width = 62
    [githooks "checkjira"]
            jiraurl  = https://jira.cpqd.com.br
            jirauser = gustavo
            jirapass = a-very-large-and-difficult-to-crack-password
            matchlog = (?s)^\\[([^]]+)\\]

The only plugins I want enabled for every repository are C<CheckLog>
and C<CheckRewrite>. The latter is simple, as it doesn't require any
configuration whatsoever. With it I feel more confident to perform
C<git commit --amend> and C<git rebase> commands knowing that I'm
going to be notified in case I'm doing anything dangerous.

The C<CheckLog> is also useful to guarantee that I'm not deviating
from the common Git policies regarding the commit messages. The only
thing I change from the defaults is the C<title-max-width>, because I
think 50 characters is very constraining.

I disable the C<githooks.abort-commit> option so that C<pre-commit>
and C<commit-msg> hooks don't abort the commit in case of
errors. That's because I find it easier to amend the commit than to
remember to recover my carefully crafted commit message from the
F<.git/COMMIT_EDITMSG> file afterwards.

The section C<githooks "checkjira"> contains some global configuration
for the C<CheckJira> plugin, which I enable only for some
repositories. Since the C<CheckJira> plugin has to connect to our JIRA
server, it needs the server URL and some credentials to
authenticate. The C<matchlog> regex makes JIRA issue keys be looked
for only inside a pair of brackets at the beginning of the commit messages's
title line.

I enable other plugins for specific repositories, since they depend on
the context in which they are developed.

At L<CPqD|http://www.cpqd.com.br/> we use
L<JIRA|http://www.atlassian.com/software/jira> and
L<Gerrit|https://code.google.com/p/gerrit/>. So, for my work-related
repositories I have this in their F<.git/config>:

    [githooks]
            plugin = CheckJira
            plugin = GerritChangeId
    [githooks "checkjira"]
            project = CDS

C<GerritChangeId> doesn't require any configuration. It simply inserts
a C<Change-Id> line in the messages of all commits. These are required
by Gerrit.

I use C<CheckJira> to remind me to cite a JIRA issue in every commit
message. The C<project> value makes it accept only issues of the CDS
JIRA project for this particular repository.

=head3 Disabling plugins temporarily

If you prefer the default behaviour of having your C<pre-commit> and
C<commit-msg> abort on errors, it's sometimes useful to disable a plugin
temporarily in order to do a commit that otherwise would be rejected. For
instance, if you enable C<CheckLog>'s spelling checks and it rejects a
commit because you used a cute-but-not-quite-right word in its message you
can disable it for the duration of the commit by defining the environment
variable C<CheckLog> as C<0> like this:

    CheckLog=0 git commit

You can disable any plugin in the same manner. Just define as zero (0) an
environment variable homonymous to the plugin (you can use the plugin module
full name or just its last component, as in the example above) for the
duration of the commit and the plugin will be disabled.

=head1 ADMIN TUTORIAL

As the administrator of a Git server you may be interested in enabling some
hooks for your Git repositories to enforce project policies through source
code verification or access rights.

=head2 Server Driver Script

The same driver script described L<above|/USER DRIVER SCRIPT> for user
repositories can be used for server repositories:

    #!/usr/bin/env perl
    use Git::Hooks;
    run_hook($0, @ARGV);

As a Git administrator, I save it as F</usr/local/bin/githooks.pl> in my Git
server. You may save it elsewhere in the machine your hooks will run. Just
do not forget to make it executable!

=head2 Server Hook Links

As a Git administrator, you would be interested in the back-end hooks. So,
you should create some symbolic links under the F<.git/hooks> directories of
your repositories pointing to the drive script:

    $ cd .../.git/hooks
    $ ln -s /usr/local/bin/githooks.pl pre-receive
    $ ln -s /usr/local/bin/githooks.pl update
    $ ln -s /usr/local/bin/githooks.pl ref-update

Also, read the section about L</Automating the creation of links> to know
to have such links automatically created for you when you initialize or clone
a repository.

=head2 Server Configuration

In your Git server you should insert global configuration in the
F<~/.gitconfig> file at the HOME of the user running Git. This is an
example using some of the available plugins:

    [githooks]
            plugin = CheckJira
            plugin = CheckLog
            admin = gustavo
    [githooks "checkjira"]
            jiraurl  = https://jira.cpqd.com.br
            jirauser = gustavo
            jirapass = a-very-large-and-difficult-to-crack-password
            matchlog = (?s)^\\[([^]]+)\\]
    [githooks "checklog"]
            title-max-width = 62

In the server the C<CheckJira> and C<CheckLog> plugins are enabled for every
repository. The <githooks.checkjira> section specifies the URL and
credentials of the JIRA server as well as where in the commit message the
JIRA references are to be looked for.

The C<githooks.checklog> section specifies a nonstandard value for the
C<title-max-width> option.

As the administrator, I've configured myself (C<githooks.admin = gustavo>)
to be exempt from any checks so that I can brag about my superpowers to my
fellow users. Seriously, though, sometimes it's necessary to be able to
bypass some checks and this is a way to allow some user to do it.

In particular repositories you can make local configurations to complement
or supersede the global configuration. This is an example F<.git/config>
file:

    [githooks]
            disable = CheckJira
            plugin = CheckAcls
            groups = integrators = tiago juliana
    [githooks "checkacls"]
            acl = @integrators CRUD ^refs/(heads|tags)/
            acl = ^.           CRUD ^refs/heads/user/{USER}/'
            acl = ^.           U    ^refs/heads

In this repository the C<CheckJira> plugin is disabled, even though it is
enabled globally.

The C<CheckAcls> plugin is enabled and configured in the
C<githooks.checkacls> section with three ACLs.

The first ACL allows the two users belonging to the C<integrators> group to
create, rewrite, update, and delete any branch or tag.

The second ACL allows any user to create, rewrite, update, and delete any
branch with a name beginning with C<user/USER>, where USER is the username
with which she authenticated herself with Git. This is useful to allow
developers to backup their own local branches in the server while they
aren't good enough to be shared.

The third ACL allows any user to update any branch, which means, to push to
any branch and have it be fast-forwarded.

=head3 Distributed configuration

By default you only get a single global and one local configuration file for
each repository in the server. Sometimes it's useful to factor out some
configuration in specific files. If you have, say, three development teams
holding their repositories in a single server but each one of them wants
different C<CheckAcls> configuration you may separate these configurations
in three files and include one of them in each repository using Git's
C<include> section. For example, team A's repositories could have this in
their F<.git/config> files:

    [include]
            path = /usr/local/etc/githooks/teamA.acls

Using include files you can manage complex configurations more easily.

=head1 GERRIT TUTORIAL

Gerrit is a Git server but since it uses L<JGit|http://eclipse.org/jgit/>
instead of standard Git, it doesn't support the standard Git hooks. It
supports its L<own
hooks|https://gerrit-review.googlesource.com/Documentation/config-hooks.html>
instead.

Git::Hooks supports just three of the many Gerrit hooks so far:
C<ref-update>, C<patchset-created>, and C<draft-published>. The first one is
much like the standard hooks C<pre-receive> and C<update> in that it can
reject pushes when the commits being pushed don't comply. However, since
Gerrit's revision process takes place before the commits are integrated,
it's more useful to enable just the other two.

First, you have to create the same driver script as L<described for the
server|/Server Driver Script>. There is a catch though. Gerrit undefines the
C<HOME> environment variable when it runs, so that when we invoke the C<git>
command inside one of our hooks it won't be able to read the global
configuration file F<~/.gitconfig>. So, you should define HOME in the script
like this (obviously, using the correct value in the assignment):

    #!/usr/bin/env perl
    BEGIN { $ENV{HOME} = '/home/gerrit' }
    use Git::Hooks;
    run_hook($0, @ARGV);

Then we must create the symlinks from the hook names to the driver
script. However, in Gerrit there's a single F<hooks> directory per server,
instead of one per repository. Normally, when you install Gerrit, the hooks
directory isn't created. It should be created below the Gerrit's site
directory. Create it and the two symlinks like so:

    $ cd .../gerrit-site
    $ mkdir hooks
    $ cd hooks
    $ ln -s /usr/local/bin/githooks.pl patchset-created
    $ ln -s /usr/local/bin/githooks.pl draft-published

The C<patchset-created> hook is invoked when you push a patchset to Gerrit
for revision, but Git::Hooks only enable it for non-draft patchsets, because
draft patchsets can only be reviewed by their onwners and invited
reviewers. The C<draft-published> hook is invoked when you publish a
draft-patchset. Both hooks run asynchronously so that they can't reject the
push. Instead, they review the patchset as a normal reviewer would, casting
a positive or negative vote, depending on the result of the checks made by
the enabled plugins.

All the (standard) Git::Hooks plugins that attach to the C<pre-receive> and
C<update> hooks also attach themselves to both the C<patchset-created> and
the C<draft-published> hooks, so that you can use the L<same configuration
we did above|/Server Configuration>.

You have to do a little extra configuration in the C<githooks.gerrit>
section:

    [githooks "gerrit"]
            url  = https://gerrit.cpqd.com.br
            username = gerrit
            password = a-very-large-and-difficult-to-crack-password
            review-label = Verification
            vote-ok  = +1
            vote-nok = -1

The three options C<url>, C<username>, and C<password> tells where to
connect to Gerrit and with which user's credentials. This is the user that
will appear to be making comments and reviewing the patchsets.

Then you have to tell Git::Hooks how it should vote. First, you tell which
C<label> it should vote in. By default it votes in the C<Code-Review>
label. In the example above I've changed it to vote in the C<Verification>
label instead.

The last two options, C<vote-ok> and C<vote-nok>, tells which number it
should use to vote positively and negatively. +1 and -1 are the defaults,
but you may tell it to use different numbers if you like.

Gerrit has a notion of a hierarchy of repositories (called 'projects' in
Gerrit). Gerrit's own configuration uses this hierarchy so that child
repositories inherit their ancestor's configuration. Git's own configuration
mechanism has no such notion, but you can fake it using the same L<include
mechanism discussed above|/Distributed configuration>. But you have to do it
manually, though.

=head1 AUTHOR

Gustavo L. de M. Chaves <gnustavo@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by CPqD <www.cpqd.com.br>.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut