Skip to content

Architectural Overview

The internal logic that powers Crisp is divided into three distinct parts:

  1. The reader which is responsible for reading the Git commit message either from STDIN (piped in) or from the $GIT_DIR/COMMIT_EDITMSG file (see the official Git docs).

  2. The parser which is responsible for accepting the inputted commit message and then parsing it into a Go struct for further data processing.

  3. The validator which 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:

  1. Directly passed as a CLI flag,
  2. Read from STDIN where the data is piped into it.
  3. Read the $GIT_DIR/COMMIT_EDITMSG file.

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:

Terminal window
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:

Terminal window
git show --no-patch --format=%B | crisp message --stdin

In 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:

  1. Header — the first line of the commit message, containing the type, optional scope, and description.
  2. Body — optional free-form text separated from the header by a blank line.
  3. 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.

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:

  1. Checks whether the commit type is in accordance to the list of accepted keywords (build, ci, docs, feat, fix, perf, refactor, style, test, chore).

  2. If the optional commit message scope is provided, check whether it is lower-cased and contains valid characters.

  3. Checks whether the commit message subject is provided and does not end with a period (.) nor starts with a capitalised letter.

  4. 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.