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 key | git |
| Version | v1 |
| Script | git.py |
| Timeout | 60 seconds |
Inputs
| Input | Description | Default | Environment |
|---|---|---|---|
baseRef | Ref the collected range starts at. The commits collected are <baseRef>..HEAD. | origin/main | GUARDRAIL_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:
{
"collect": ["git"]
}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.
| Fact | Meaning |
|---|---|
repository | Whether the CLI is running inside a git repository. Nothing else is collected when it is not. |
root | Absolute path of the working tree root, or null when git could not report it. |
head.sha | Full SHA of the commit checked out. |
head.short | First seven characters of that SHA. |
head.branch | Branch name checked out, or null when HEAD is detached. |
head.detached | Whether HEAD is detached rather than on a branch. |
head.tags | Tags pointing at HEAD. |
remotes | Remote name to fetch URL, as git remote -v reports them. |
dirty | Whether the working tree carries uncommitted changes, or null when git could not report it. |
baseRef | The baseRef input, as the guardrail configured it. |
resolved | Whether 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. |
baseSha | SHA baseRef resolved to. |
commits | Every commit in <baseRef>..HEAD, newest first. Empty for a range with no commits, which is a pass rather than a skip. |
commits[].sha | Full SHA of the commit. |
commits[].short | First seven characters of that SHA. |
commits[].parents | SHAs of the commit's parents. |
commits[].merge | Whether the commit has more than one parent. |
commits[].subject | First line of the commit message. |
commits[].message | The whole commit message, trailing newlines removed. |
commits[].body | The message below the subject line. |
commits[].author.name | Author name. |
commits[].author.email | Author email. |
commits[].author.date | Author date, ISO 8601. |
commits[].committer.name | Committer name. |
commits[].committer.email | Committer email. |
commits[].committer.date | Commit date, ISO 8601. |
changedFiles | Paths <baseRef>...HEAD changed, relative to the working tree root. |
changes | Every path in changedFiles with the lines that range added and removed to it. Empty when the range could not be diffed. |
changes[].path | Path the change is to, as changedFiles states it. A rename is stated under the path it was renamed to. |
changes[].added | Lines added to the path, or null for a binary file, whose lines git does not count. |
changes[].deleted | Lines removed from the path, or null for a binary file. |
changes[].binary | Whether 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:
{
"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
| Guardrail | Category | Inputs |
|---|---|---|
git/author-identity-domain | git | baseRef |
git/changed-files-budget | git | baseRef |
git/changed-lines-budget | git | baseRef |
git/conventional-commits | git | baseRef |
git/no-merge-commits | git | baseRef |
git/no-wip-commits | git | baseRef |
git/work-item-reference | git | baseRef |