Configuration
- Looking for:
- Actions
- Conditions
- Placeholders
- CaptainHook Settings
If you look at a captainhook.json configuration, it consists of a config block for each hook and an optional config section to tweak some CaptainHook settings.
{
"config": {
// captainhook settings area
},
"hooks": {
"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
Commands
Actions can be two things, firstly you can run any command, as long as it uses exit codes to notify the caller about its success. If you, for example, run go test 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": [
{
"run": "go test ./.."
}
]
}
}
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 the run section of an action. Placeholders will get replaced by the Cap'n before execution. This way you can access the original hook arguments and some other neat stuff. You can either refer to them by a shorthand{$ARGUMENT} or the standard way {$ARG|value-of:ARGUMENT}.
Shorthand placeholders
Placeholder | Hook | Description |
---|---|---|
{$MESSAGE_FILE} | commit-message | The path to the file containing the commit message. |
{$MESSAGE_FILE} | prepare-commit-message | The path to the file containing the commit message. |
{$MODE} | prepare-commit-message | Source of the commit message e.g. file, template ... |
{$HASH} | prepare-commit-message | Hash of commit e.g. if --amend is used. |
{$TARGET} | pre-push | The push target name. |
{$URL} | pre-push | The target url. |
{$PREVIOUS_HEAD} | post-checkout | Old version hash. |
{$NEW_HEAD} | post-checkout | New version hash (can be the same as the old head). |
{$MODE} | post-checkout | A flag indicating whether the checkout was a branch checkout. |
{$SQUASH} | post-merge | A flag specifying whether or not the merge being done was a squash merge. |
{
"pre-commit": {
"enabled": true,
"actions": [
{
"run": "my-linter {$STAGED_FILES|of-type:go}"
}
]
}
}
Placeholders
Dynamic placeholders work like this:
{$ PLACEHOLDER | OPTION : ARGUMENT }
Placeholder | Hook | Description |
---|---|---|
{$ARG} | * | Access original hook arguments. |
|
||
{$ARG|value-of:MESSAGE_FILE} {$ARG|value-of:PREVIOUS_HEAD|default:HEAD^} |
||
{$CONFIG} | * | Access config values. |
{$CONFIG|value-of:git-directory} {$CONFIG|value-of:some-custom-key|default:YES} |
||
{$ENV} | * | Access environment variables |
{$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 |
{$STAGED_FILES|separated-by: } // foo/bar/file1 file2.html foo3.html {$STAGED_FILES|of-type:html|separated-by:, } // file2.html, foo3.html {$STAGED_FILES|in-dir:foo/bar} // foo/bar/file1 |
||
{$CHANGED_FILES} | pre-push, post-merge, post-checkout, post-rewrite | Gets replaced with a list of changed files |
{$CHANGED_FILES|separated-by: } // foo/bar/file1 file2.html foo3.html {$CHANGED_FILES|of-type:html|separated-by:, } // file2.html, foo3.html {$CHANGED_FILES|in-dir:foo/bar} // foo/bar/file1 |
Internal functionality
Secondly you can execute some CaptainHook functionality. So let's have a look at how to configure this.
{
"commit-msg": {
"actions": [
{
"run": "CaptainHook::Message.ContainsRegex",
"options": {
"regex": ".*"
}
}
]
}
}
With this configuration you can validate your commit messages against a regular expression. If your commit message doesn't match your regular expression your commit fails.
Here's a list of all included actions.
Conditions
You can add conditions to an action configuration. This becomes useful for example if you want to run tasks automatically but only if some conditions apply.
For example to make sure you catch every change of your dependencies in your application you can use the CaptainHook hook post-change (not an official git hook) which gets triggered by gits post-merge, post-checkout and post-rewrite hooks. You can use it to install all required dependencies but only if someone changed the go.mod or go.sum files.
{
"post-change": {
"actions": [
{
"run": "go mod download",
"conditions": [
{
"run": "CaptainHook::FileChanged.Any",
"options": {
"files": ["go.mod", "go.sum"]
}
}
]
}
]
}
}
You can add multiple conditions all conditions have to apply to execute the action. 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 has the possibility to include other CaptainHook configurations like this.
{
"config": {
"includes": [
"some-other-captainhook-config.json"
]
},
"pre-commit": {
"actions": [
{
"run": "CaptainHook::Message.ContainsRegex",
"options": {
"regex": ".*"
}
}
]
}
}
The idea is to create a separate package e.g. my-git-hooks that is structured like this.
my-git-hooks/
├── msg-validation.json
└── crazy-workflow.json
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": [
"my-git-hooks/msg-validation.json",
"my-git-hooks/crazy-workflow.json"
]
}
}
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": [
"my-git-hooks/msg-validation.json",
"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": {
"git-directory": "../../.git",
"fail-on-first-error": false,
"verbosity": "verbose",
"ansi-colors": false,
"includes-level": 2,
"includes": [],
"run-path": "./tools/",
"run-async": true,
"custom": {
"my-custom-index": "my-custom-value"
}
}
}
Setting | Description | Default |
---|---|---|
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 |
run-path | Path to the CaptainHook executable, only necessary if the Cap'n is not in your PATH | - |
run-async | Run actions in parallel | - |
custom | List of custom settings you need for replacements or custom actions | - |
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,
"custom": {
"some-key": "some value"
}
}
ATTENTION: Remember to exclude the file from version control ;)