# PODNAME: Git::Hooks Tutorial
# ABSTRACT: Gentle introduction to Git::Hooks

__END__

=pod

=encoding UTF-8

=head1 NAME

Git::Hooks Tutorial - Gentle introduction to Git::Hooks

=head1 VERSION

version 3.1.1

=head1 NAME

Tutorial - Gentle introduction to Git::Hooks users and Git administrators

=head1 INTRODUCTION

This document is intended to make it easy to start using the Git::Hooks
framework as fast as possible, with a minimum of set up. There are major
sections for Git users, administrators, and hook developers. After setting it up
with these instructions you're ready to go.

=head1 TUTORIAL FOR GIT USERS

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<$HOME/bin/githooks.pl> and 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
    $ $HOME/bin/githooks.pl
    $ echo $?
    0

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

    $ cd ..
    $ $HOME/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 /my/git/repo/.git/hooks
    $ ln -s $HOME/bin/githooks.pl prepare-commit-msg
    $ ln -s $HOME/bin/githooks.pl commit-msg
    $ ln -s $HOME/bin/githooks.pl pre-commit
    $ ln -s $HOME/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 Linux, 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 $HOME/.git-templates
    $ cd $HOME/.git-templates/hooks
    $ rm *
    $ for i in prepare-commit-msg commit-msg post-commit pre-commit pre-rebase
    > do ln -s $HOME/bin/githooks.pl $i
    > done

These commands copy the default template directory to F<$HOME/.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 $HOME/.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<$HOME/.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"]
            jql = 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<jql> filter makes it accept only issues of the CDS JIRA project
for this particular repository.

=head3 Disabling plugins locally

The Git configuration follows a hierarchy, reading first the system
configuration (F</etc/gitconfig>), then the global configuration
(F<$HOME/.gitconfig>), and then the local configuration (F<$GIT_DIR/config>). If
you have some plugins enabled globally you may disable then locally by putting
the following in the F<.git/config> of a particular repository:

    [githooks]
            disable = CheckJira

=head3 Disabling plugins temporarily

If you prefer the default behavior 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 TUTORIAL FOR GIT ADMINISTRATORS

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

For server hooks, it's advisable to enable logging. So, we increment the driver
script described L<above|/USER DRIVER SCRIPT> for user repositories with a line
enabling log to a file:

    #!/usr/bin/env perl
    use Log::Any::Adapter (File => '/var/log/githooks.log', log_level => 'info');
    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<$HOME/.gitconfig> file at the HOME of the user running Git. This is an example
using some of the available plugins:

    [githooks]
            plugin = CheckCommit
            plugin = CheckJira
            plugin = CheckLog
            admin = gustavo
    [githooks "checkcommit"]
            email-valid = 1
    [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<CheckCommit>, 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.checkcommit> enables the C<email-valid> check to guarantee that
authors and committers use sane email addresses in their commits.

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 = CheckReference
            groups = integrators = tiago juliana
    [githooks "checkreference"]
            acl = deny  CRUD ^refs/
            acl = allow   U  ^refs/heads/
            acl = allow CRUD ^refs/heads/user/{USER}/
            acl = allow CRUD ^refs/                   by @integrators

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

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

The githooks.checkreference.acl options are used to restrict who can do what to
which reference in the remote repository during a git-push. The rules are
processed in reverse order and are (almost) self explanatory. The four acls
above mean that:

=over

=item * The users in the C<integrators> group can (C) create, (R) rewrite, (U)
update, and (D) delete any branch or tag.

=item * Any user can create, rewrite, update, and delete any branch prefixed with
F<user/{USER}>, where C<{USER}> is replaced by the username she used to
authenticate during the git-push.

=item * Any user can update any branch.

=item * No other change is allowed. (The first acl is important because any
action not matching any acl is allowed by default.)

=back

=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<CheckReference> 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 TUTORIAL FOR GERRIT ADMINITRATORS

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|https://github.com/gnustavo/Git-Hooks/wiki/Tutorial-for-Git-administrators#server-driver-script>.

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 owners 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
            votes-to-approve = Verification+1
            votes-to-reject = Verification-1

The three options C<url>, C<username>, and C<password> tell 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 to approve and to reject a
change using the options C<votes-to-approve> and C<votes-to-reject>. In the
example above you tell Git::Hooks to cast a +1 in the C<Verification> label to
approve the change and to cast a -1 in the same label to reject it. You may cast
multiple votes in multiple labels by separating the vote specifications with
commas.

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 TUTORIAL FOR BITBUCKET ADMINISTRATORS

L<Bitbucket Server|https://www.atlassian.com/software/bitbucket/server> is a
proprietary Git server made by Atlassian. It doesn't support the standard Git
hooks natively. Instead, it supports its L<own
hooks|https://confluence.atlassian.com/bitbucketserver/using-repository-hooks-776639836.html>
which are L<implemented in Java as
plugins|https://confluence.atlassian.com/bitbucketserverkb/how-to-create-a-simple-hook-in-bitbucket-server-779171711.html>.

The commercial plugin L<External
Hooks|https://marketplace.atlassian.com/plugins/com.ngs.stash.externalhooks.external-hooks/server/overview>
allows you to use standard C<pre-receive> and C<post-receive> Git hooks with
Bitbucket.

Follow the plugin installation and L<configuration
instructions|https://github.com/reconquest/atlassian-external-hooks/wiki/Configuration>
to enable it for particular repositories. The plugin must be configured with an
I<executable>, which can be our usual C<githooks.pl> script.

Bitbucket git repositories are kept in the server but are not easy to locate,
because they're named after a numeric ID. In order to locate the repository
directory in the server, go to its I<repository details> page in the
I<repository settings> section in Bitbucket's web interface. The page shows the
repository's B<location on disk> where you find the F<config> file which you
must edit to configure Git::Hooks plugins.

=head1 TUTORIAL FOR HOOK DEVELOPERS

I'm sorry but there is no gentle introduction to this. ;-)

If you want to develop your own hooks or plugins, please read the detailed
documentation for L<Git::Hooks/Implementing Hooks> and L<Git::Hooks/Implementing
Plugins>. Then, go ahead and read the code of the plugins which come with the
distribution. Most probably you can start by copying the overall structure of
one of them as a starting point for your own plugin.

=head1 AUTHOR

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

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2021 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
