Configuration

The captainhook.json configuration consists of a config block for each hook and an optional config section to tweak some CaptainHook settings.

{
  "commit-msg": {
    // a hook config block
  },
  "pre-commit": {
    // another hook config block
  },
  // ...
}

Hook Config

Each hook configuration consists of two elements, an on/off switch and a list of actions to execute.

'commit-msg': {
  "enabled": true,
  "actions": []
}

By setting enabled to false, CaptainHook will not execute the configured actions even if the hook is triggered by git.

Actions

CLI commands

Actions can be two things, firstly you can run any cli command, as long as it uses exit codes to notify the caller about its success. If you, for example, run PHPUnit and a test fails, it will exit with status code 1, so CaptainHook knows that it should stop the currently running git command with an error.

{
  "pre-commit": {
    "enabled": true,
    "actions": [
      {
        "action": "tools/phpunit.phar --configuration=phpunit.git.xml"
      }
    ]
  }
}

Using the example above in a pre-commit hook will prevent you from committing code that breaks any unit test.

Placeholders

You can use placeholders in CLI actions that will get replaced by the Cap'n before execution. This way you can access the original hook arguments and some other neat stuff.

{
  "pre-commit": {
    "enabled": true,
    "actions": [
      {
        "action": "tools/phpcs.phar --standard=psr12 {$STAGED_FILES|of-type:php}"
      }
    ]
  }
}
Placeholders

Action placeholders work like this:

{$ PLACEHOLDER | OPTION : ARGUMENT }

Placeholder Hook Description
{$ARG} * Access original hook arguments.
Hooks and their arguments:
  • commit-message (MESSAGE_FILE)
  • post-checkout (PREVIOUS_HEAD, NEW_HEAD, MODE)
  • post-commit
  • post-merge (MODE)
  • pre-commit
  • pre-push (TARGET, URL)
  • prepare-commit-msg (MESSAGE_FILE, MODE, HASH)
Examples:
{$ARG|value-of:MESSAGE_FILE}
{$ARG|value-of:PREVIOUS_HEAD|default:HEAD^}
{$CONFIG} * Access config values.
Examples:
{$CONFIG|value-of:git-directory}
{$CONFIG|value-of:custom>>some-key}
{$ENV} * Access environment variables
Examples:
{$ENV|value-of:MY_ENV_VARIABLE}
{$ENV|value-of:MY_ENV_VARIABLE|default:FALSE}
{$STAGED_FILES} pre-commit Gets replaced with a list of staged files
Examples:
{$STAGED_FILES|separated-by: }              // foo/bar/file1 file2.php foo3.php
{$STAGED_FILES|of-type:php|separated-by:, } // file2.php, foo3.php
{$STAGED_FILES|in-dir:foo/bar}              // foo/bar/file1
{$STAGED_FILES|replace:foo|with:bar}        // bar/bar/file1 bar3.php
{$CHANGED_FILES} pre-push, post-merge, post-checkout, post-rewrite Gets replaced with a list of changed files
Examples:
{$CHANGED_FILES|separated-by: }              // foo/bar/file1 file2.php foo3.php
{$CHANGED_FILES|of-type:php|separated-by:, } // file2.php, foo3.php
{$CHANGED_FILES|in-dir:foo/bar}              // foo/bar/file1
{$CHANGED_FILES|replace:foo|with:bar}        // bar/bar/file1 bar3.php
{$BRANCH_FILES} * Gets replaced with a list of files changed within this branch.
Examples:
{$BRANCH_FILES|compared-to:main|separated-by: }               // foo/bar/file1 file2.php foo3.php
{$BRANCH_FILES|compared-to:main||of-type:php|separated-by:, } // file2.php, foo3.php
{$BRANCH_FILES|in-dir:foo/bar}                                // foo/bar/file1

PHP scripts

You can execute static methods and any PHP class implementing an interfaces defined by CaptainHook. The interface you have to implement is \CaptainHook\App\Hook\Action. CaptainHook comes with some built-in, basic validation classes. So let's have a look at how to configure those.

{
  "commit-msg": {
    "enabled": true,
    "actions": [
      {
        "action": "\\Some\\Namespace\\SomeClassThatImplementsTheInterface",
        "options": {
          "someData": "someValue"
        }
      },
      {
        "action": "\\Some\\Namespace\\SomeClass::someMethod",
      }
    ]
  }
}

With the following configuration you can validate your commit messages against a regular expression. If your commit message doesn't match your regular expression the commit fails.

{
  "commit-msg": {
    "enabled": true,
    "actions": [
      {
        "action": "\\CaptainHook\\App\\Hook\\Message\\Action\\Regex",
        "options": {
          "regex": "#.*#"
        }
      }
    ]
  }
}

Here's a list of all available actions.

You can easily build your own action classes. Have a look on how to extend CaptainHook.

Conditions

You can add conditions to an action configuration. This becomes useful for example if you want to run composer install automatically but only if someone changes composer.json or composer.lock.

To make sure you catch every change in your repository just use the CaptainHook hook post-change (not an official git hook) which gets triggered by gits post-merge, post-checkout and post-rewrite hooks.

{
  "post-change": {
    "enabled": true,
    "actions": [
      {
        "action": "composer install",
        "options": {},
        "conditions": [
          {
            "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileChanged\\Any",
            "args": [
              ["composer.json", "composer.lock"]
            ]
          }
        ]
      }
    ]
  }
}

You can add multiple conditions and all conditions have to apply to execute the action. But for more complex conditions you can use the LogicConditions to combine multiple conditions by and or or. For a full list of all available Conditions and some example configurations check the Conditions Section.

Include other configuration files

If you have a lot of projects you maybe want to define a set of default hooks for all of them. To maintain such a default configuration CaptainHook version 4.4.0 introduced the possibility to include other CaptainHook configurations like this.

{
  "config": {
    "includes": [
      "some-other-captainhook-config.json"
    ]
  },
  "pre-commit": {
    "enabled": true,
    "actions": [
      {
        "action": "\\CaptainHook\\App\\Hook\\Message\\Action\\Regex",
        "options": {
          "regex": "#.*#"
        }
      }
    ]
  }
}

The idea is to create a separate composer package e.g. my-git-hooks that is structured like this.

my-git-hooks/
├── composer.json
├── msg-validation.json
└── crazy-workflow.json

Inside your composer.json you require all libs and extensions you need to execute the configured actions plus captainhook/captainhook or captainhook/plugin-composer. The only thing you have to do in your projects is to require your my-git-hooks package and add something like this to your local project captainhook.json file.

{
  "config": {
    "includes": [
      "vendor/my-company/my-git-hooks/msg-validation.json",
      "vendor/my-company/my-git-hooks/crazy-workflow.json"
    ]
  }
}

To update the base configuration you create a new version for your my-git-hooks package and run composer update in your projects.

Include Level

By default you can't include configurations in included configurations. But if you really want to shoot yourself in the foot, who am I to judge you. You can increase the maximum levels of inclusion in your project configuration. Be aware, that setting this in any included configuration will have no effect.

{
  "config": {
    "includes-level": 2,
    "includes": [
      "vendor/my-company/my-git-hooks/msg-validation.json",
      "vendor/my-company/my-git-hooks/crazy-workflow.json"
    ]
  }
}

Settings

You can configure some basic CaptainHook behaviour by overwriting the defaults in your configuration file. For example if your .git directory is not located on the same level as your captainhook.json or you like a more verbose output.

{
  "config": {
    "bootstrap": "../vendor/autoload.php",
    "git-directory": "../../.git",
    "fail-on-first-error": false,
    "verbosity": "verbose",
    "ansi-colors": false,
    "includes-level": 2,
    "includes": [],
    "plugins": [],
    "php-path": "/usr/bin/php7.4",
    "custom": {
      "my-custom-index": "my-custom-value"
    },
    "run": {
      "mode": "docker",
      "exec": "docker exec -i CONTAINER_NAME",
      "path": "vendor/bin/captainhook",
      "git": "/docker/.git"
    }
  }
}
Setting Description Default
bootstrap Path to your bootstrap / autoloader file. vendor/autoload.php
git-directory Custom path to your repositories .git directory (relative from the configuration file) .git
fail-on-first-error Stop hook execution on first error (true) or execute all actions and collect all errors (false) true
verbosity Define the output verbosity [quiet|normal|verbose|debug] verbose
ansi-colors Enable or disable ANSI color output true
includes A list of CaptainHook configuration files to include -
includes-level Nesting level of allowed config file inclusion 0
plugins An array of plugins to use when running CaptainHook, optionally including options to pass to the plugin
[
  {
    "plugin": "\\MyName\\GitHook\\MyPlugin",
    "options": {
      "myOption": true
    }
  }
]
-
php-path Path to the PHP executable -
custom List of custom settings you need for replacements or custom actions -
Run Configuration
mode CaptainHook execution mode (shell|php|local|docker) shell
exec Docker command to spin up the container and execute a script -
path Path to the CaptainHook command inside your Docker container -
git Absolute path to the .git directory inside your Docker container -

Overwrite settings

If you have different preferences as your team does and you want to tweak some settings to your needs but you don't want to change the captainhook.json that is under version control, there is a way. All you have to do is to create a captainhook.config.json file right beside your captainhook.json file.

ATTENTION: The file must be named captainhook.config.json and it must be located in the same directory as your captainhook.json configuration file.

In this file you can overwrite all settings to your personal needs. Here's an example captainhook.config.json file.

{
  "verbosity": "quiet",
  "ansi-colors": false,
  "fail-on-first-error": false,
  "run": {
    "mode": "docker",
    "exec": "docker exec -i MY_CONTAINER_NAME",
    "path": "vendor/bin/captainhook",
    "git": "/docker/.git"
  },
  "custom": {
    "some-key": "some value"
  }
}

ATTENTION: Remember to exclude the file from version control ;)