Architectural Overview
The internal logic that powers Crisp is divided into three distinct parts:
-
The reader which is responsible for reading the Git commit message either from STDIN (piped in) or from the
$GIT_DIR/COMMIT_EDITMSGfile (see the official Git docs). -
The
parserwhich is responsible for accepting the inputted commit message and then parsing it into a Go struct for further data processing. -
The
validatorwhich is responsible for running some validation logic on the parsed data.
Under the hood, all three components work in tandem to lint your Git commit messages. The following diagram provides a better understanding of the underlying logic.
Reader
The reader is responsible for accepting user input through either of the following means:
- Directly passed as a CLI flag,
- Read from STDIN where the data is piped into it.
- Read the
$GIT_DIR/COMMIT_EDITMSGfile.
In other words, Crisp can read a commit message if it is directly passed to it
as an argument of the message command as shown below:
crisp message "$(git show --no-patch --format=%B)"While that is not the recommended approach to lint a commit message, it exists
for quickly validating a single message. The recommended approach to lint a
commit message is to pass the data to Crisp through STDIN. Crisp will read
from STDIN if the --stdin flag is passed to the message command.
Piping a commit message to Crisp is possible like this:
git show --no-patch --format=%B | crisp message --stdinIn case no commit message was piped in to Crisp, the reader will fallback to
reading from the
$GIT_DIR/COMMIT_EDITMSG
file.
The diagram below provides a better understanding of how the reader works behind the scenes.
Parser
The parser is responsible for receiving content from the reader and parsing it into the following components:
- Header — the first line of the commit message, containing the type, optional scope, and description.
- Body — optional free-form text separated from the header by a blank line.
- Footers — optional key-value pairs (e.g.
BREAKING CHANGE,Closes,Fixes,Refs) appearing after the body.
Upon a successful parse, the logic returns a CommitMessage struct for further
processing and validation:
type CommitMessage struct { Type string Scope string Description string Body string Footers map[string]string}Header parsing
The header is parsed using a regular expression that expects the following format:
<TYPE>(<SCOPE>): <DESCRIPTION>The scope is optional. If the header does not match this format, an error is returned immediately with a reference to the Conventional Commits specification.
Body and footer parsing
Lines after the header are processed sequentially. The parser tracks whether it
is inside the footer section using an internal flag. A line is treated as a
footer if it matches a recognised key (BREAKING CHANGE, Closes, Fixes, or
Refs) followed by a colon and a value. Once a footer line is detected, all
subsequent lines are treated as footers. Body lines are accumulated until the
footer section begins.
The diagram below illustrates the full parsing flow:
Validator
After Crisp has parsed and generated a “commit message” object, the validator can receive the said object for further processing and validation. The validator performs the following operations:
-
Checks whether the commit type is in accordance to the list of accepted keywords (
build,ci,docs,feat,fix,perf,refactor,style,test,chore). -
If the optional commit message scope is provided, check whether it is lower-cased and contains valid characters.
-
Checks whether the commit message subject is provided and does not end with a period (
.) nor starts with a capitalised letter. -
Checks whether the length of the commit message does not exceed more than 50 characters long.
On successful validation, Crisp returns an appropriate valid commit message
prompt for the user with an exit status code of 0.