Skip to content

Guardrails / Collectors

Git repository and commit range ​

The repository, HEAD, the remotes, and every commit between the base ref and HEAD with its message, author, parents, and the files the range changed with the lines it added and removed to each.

Facts keygit
Versionv1
Scriptgit.py
Timeout60 seconds

Inputs ​

InputDescriptionDefaultEnvironment
baseRefRef the collected range starts at. The commits collected are <baseRef>..HEAD.origin/mainGUARDRAIL_INPUT_BASEREF

When a guardrail declares an input with the same name, it passes its value through. That means you set these values in the guardrail's configuration in buildnote.json.

A guardrail asks for these facts by name and reads them back by the same name:

json
{
  "collect": ["git"]
}
python
git = guardrail.facts("git")

Facts ​

These are the fields of the document git collects. In a path, [] means each entry of the list before it, and [path] means a key of the object before it.

FactMeaning
repositoryWhether the CLI is running inside a git repository. Nothing else is collected when it is not.
rootAbsolute path of the working tree root, or null when git could not report it.
head.shaFull SHA of the commit checked out.
head.shortFirst seven characters of that SHA.
head.branchBranch name checked out, or null when HEAD is detached.
head.detachedWhether HEAD is detached rather than on a branch.
head.tagsTags pointing at HEAD.
remotesRemote name to fetch URL, as git remote -v reports them.
dirtyWhether the working tree carries uncommitted changes, or null when git could not report it.
baseRefThe baseRef input, as the guardrail configured it.
resolvedWhether baseRef resolves in this checkout. A shallow clone is the usual reason it does not, and every guardrail over a range skips rather than failing when it is false.
baseShaSHA baseRef resolved to.
commitsEvery commit in <baseRef>..HEAD, newest first. Empty for a range with no commits, which is a pass rather than a skip.
commits[].shaFull SHA of the commit.
commits[].shortFirst seven characters of that SHA.
commits[].parentsSHAs of the commit's parents.
commits[].mergeWhether the commit has more than one parent.
commits[].subjectFirst line of the commit message.
commits[].messageThe whole commit message, trailing newlines removed.
commits[].bodyThe message below the subject line.
commits[].author.nameAuthor name.
commits[].author.emailAuthor email.
commits[].author.dateAuthor date, ISO 8601.
commits[].committer.nameCommitter name.
commits[].committer.emailCommitter email.
commits[].committer.dateCommit date, ISO 8601.
changedFilesPaths <baseRef>...HEAD changed, relative to the working tree root.
changesEvery path in changedFiles with the lines that range added and removed to it. Empty when the range could not be diffed.
changes[].pathPath the change is to, as changedFiles states it. A rename is stated under the path it was renamed to.
changes[].addedLines added to the path, or null for a binary file, whose lines git does not count.
changes[].deletedLines removed from the path, or null for a binary file.
changes[].binaryWhether git read the file as binary, which is why it carries no line counts.

If a collector can't finish, it prints what it gathered so far along with an incomplete key that says why. Facts after the point where it stopped are missing, so a check that depends on them should read incomplete first.

Example facts ​

Here are the facts the git collector gathers from an example project:

json
{
  "repository": true,
  "root": "/home/dana/widget",
  "head": {
    "sha": "b6b5c4e277f7ddb042ec2ce1aa1349fc278d0cdc",
    "short": "b6b5c4e",
    "branch": "bound-the-queue",
    "detached": false,
    "tags": [
      "v1.1.0"
    ]
  },
  "remotes": {
    "origin": "https://github.com/company/widget.git"
  },
  "dirty": false,
  "baseRef": "main",
  "commits": [
    {
      "sha": "b6b5c4e277f7ddb042ec2ce1aa1349fc278d0cdc",
      "short": "b6b5c4e",
      "parents": [
        "fa702e9abd742bd1cf39376f8959a4c113e99759"
      ],
      "merge": false,
      "subject": "feat(service): bound the widget queue",
      "message": "feat(service): bound the widget queue\n\nA queue nobody bounds is a queue that fills.",
      "body": "A queue nobody bounds is a queue that fills.",
      "author": {
        "name": "Dana Scott",
        "email": "dana@example.com",
        "date": "2026-03-02T14:40:00Z"
      },
      "committer": {
        "name": "Dana Scott",
        "email": "dana@example.com",
        "date": "2026-03-02T14:40:00Z"
      }
    }
  ],
  "changedFiles": [
    "service/src/main/kotlin/Queue.kt"
  ],
  "changes": [
    {
      "path": "service/src/main/kotlin/Queue.kt",
      "added": 1,
      "deleted": 0,
      "binary": false
    }
  ],
  "resolved": true,
  "baseSha": "fa702e9abd742bd1cf39376f8959a4c113e99759"
}

Collected for ​

GuardrailCategoryInputs
git/author-identity-domaingitbaseRef
git/changed-files-budgetgitbaseRef
git/changed-lines-budgetgitbaseRef
git/conventional-commitsgitbaseRef
git/no-merge-commitsgitbaseRef
git/no-wip-commitsgitbaseRef
git/work-item-referencegitbaseRef

All collectors

Buildnote Limited
Registered in England and Wales, Reg: 16140412