Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
monochromatic-git-policy-cli
A Git wrapper that enforces repository-owned policies.
It is installed as git ahead of the real Git on PATH,
forwards every command to the real Git,
and checks the commands and the commits that pass through it against policies the repository configures as data.
The executable is cli-git-native.
The library inside the crate is named git_policy_cli.
What it does
Every Git command goes through the wrapper first. Read-only commands, and commands the wrapper has no rule for, run the real Git unchanged.
- Rules before forwarding.
require-rootdemands the repository top level as the working directory.linked-worktree-onlykeeps destructive commands such asgit stash,git cleanandgit resetout of the main worktree.branch-worktree-onlysends branch creation throughgit worktree add.add-explicitrejects bulk staging patterns such asgit add .. - Commits as transactions.
A
git commitis prepared in a private index and a shadow repository. Hooks run there, the content policies check the exact bytes, and only then is the commit landed on the real index and branch. When another commit lands first, the wrapper replays its own commit on top of the new state. Concurrent commits from several worktrees or sessions therefore never overwrite each other. A transaction that an interrupted process left behind is recovered by the next invocation. - Content policies.
final-newline,markdown/autofix,mono/forbidden-root-context,mono/dependent-version-bumpandsecurity/forbidden-stringscheck or rewrite what a commit contains. The same content policies scan what a manualgit pushnewly publishes. - After a landed commit.
The wrapper checks the committed bytes again,
then pushes the branch.
A branch without an upstream is pushed to
originwith--set-upstreamwhen that remote exists. A branch with an upstream is pushed to it. A failed push never fails the commit, and no configuration key turns the push off. Automatic maintenance, as native Git runs it after a commit, runs after each landing, because a landing stores its objects in one new pack. - Linked worktrees. When a Git command creates a linked worktree, the wrapper copies the ignored state of the source worktree into it under one settlement lock per repository.
- Signals.
The wrapper catches
SIGINT,SIGTERM,SIGHUPandSIGQUITwhile it waits for Git. A signal sent by another process is relayed to the Git child, and the wrapper then releases its locks and removes its transaction instead of leaving it for recovery.
Requirements
- Linux.
Release archives exist for
x86_64-unknown-linux-gnuandaarch64-unknown-linux-gnuonly. The sources contain Windows-specific branches that no release gate has exercised. - Real Git 2.55.0 or newer on
PATHafter the wrapper. The wrapper checks the version before work that depends on Git's behavior, such as a commit, and refuses older Git. The wrapper finds the real Git by walkingPATH, skipping itself and any shim that leads back to it. monochromatic-linton an absolutePATHentry, only whenmarkdown/autofixis configured.- The scanner is linked into the executable and needs no separate installation.
Install
The crate is published on crates.io.
Install it as a cargo: tool of mise,
the way the repository installs monochromatic-lint:
# mise.toml
[]
= { = "0.1.4", = ["linux"] }
mise install downloads the release archive of the crate's GitHub release when one exists for the host.
Otherwise it compiles the crate from source with the Rust toolchain.
cargo install monochromatic-git-policy-cli --locked compiles the same executable into ~/.cargo/bin.
The installed file is cli-git-native,
not git.
Link it as git in a directory that comes before the real Git on PATH:
# Run in the repository once per checkout, after the installation.
Never name a file git inside the tool's own bin/ directory.
mise would then create a git shim for the whole account,
and every Git command would run through the wrapper.
PATH shadowing covers only processes that find git through PATH.
An absolute path to the real Git,
a PATH without the link directory,
a GUI with an embedded Git library and direct
libgit2 consumers bypass the wrapper completely,
including its recovery of interrupted commits and its policies.
Commands
The wrapper adds one namespace to Git.
# Check or fix the whole repository, or the named pathspecs.
Exactly one scope is required:
--all,
or pathspecs after --.
--policy <id> limits the run to one policy and may be repeated.
Events are JSON lines on standard output.
The former trust,
untrust and status commands are retired.
Configuration is data,
so nothing needs approval.
They print an explanation and change nothing.
Wrapper-only options
A wrapper-only option is removed before Git sees the arguments and applies to that one invocation.
--no-enforce-<policy id>skips one policy, for example--no-enforce-final-newlineor--no-enforce-security/forbidden-strings.--no-enforce-worktreeskipslinked-worktree-onlyforgit stash,git cleanandgit reset.--no-enforce-worktree-branchskipsbranch-worktree-only.--no-enforce-bulk-addskipsadd-explicit.--no-worktree-copyskips the ignored-state copy into a new linked worktree.--cli-git-keep-goingreports every finding of the command in one run instead of stopping at the first blocking one.
Output and exit status
Policy events of a wrapped command are JSON lines on standard error,
one object per line,
with schemaVersion 1.
Real Git keeps the normal standard streams.
- When real Git does not run:
0means clean or warnings only,1means final error findings, and2means a usage, configuration, transaction or engine failure. - When real Git runs,
its exit status is preserved.
A landed commit that a post-commit policy then blocks exits
2and stays local.
Configuration
The wrapper reads cli-git.config.jsonc at the repository top level.
The file is JSON with comments,
at most 1 MiB,
and it is data:
it never names code to run.
Without the file,
the defaults apply.
The former TypeScript wrapper read cli-git.config.mjs and cli-git.config.ts;
those files are never executed or read.
With no JSONC file beside them,
they are a configuration failure that asks for the translation.
Beside a JSONC file,
they are reported and ignored.
The keys plugins and trust are refused.
// cli-git.config.jsonc
{
"policies": {
// A severity alone: "off", "warn" or "error".
"final-newline": "error",
// A severity with options.
"markdown/autofix": ["warn", { "rules": ["lfs-image-url"], "exclude": ["package/ssg/"] }],
"mono/forbidden-root-context": "error",
"mono/dependent-version-bump": "error",
"security/forbidden-strings": ["error", { "builtinRules": true, "rulesFile": ".cache/forbidden-strings.rules.txt" }]
},
"hooks": { "concurrentCommits": false },
"indexLock": { "unprovenOwnerTimeoutMs": 1000 },
"landing": { "reserveAfterLostRaces": 1 }
}
An unknown key,
a wrong type or an unknown policy id is a configuration failure that exits 2 before any policy runs.
Policies
The wrapper ships these policies and no others. The first five run at their default severity without being listed. The last four run only where the file lists them.
require-root: defaulterror.linked-worktree-only: defaulterror.branch-worktree-only: defaulterror.add-explicit: defaulterror.final-newline: defaultwarn. Text files end with exactly one line feed.markdown/autofix: defaultwarn. Runsmonochromatic-lint --fixover staged Markdown inside the commit. Options:rules(onlylfs-image-urlexists) andexclude(gitignore-style patterns).mono/forbidden-root-context: defaulterror. NoCONTEXT.mdat the repository top level enters a commit.mono/dependent-version-bump: defaulterror. Packages that depend on a package with a bumped version follow the bump.security/forbidden-strings: defaulterror. Scans candidate bytes and path names with the linked scanner. Options:builtinRules(boolean, load the scanner's built-in baseline) andrulesFile(a rules file path relative to the repository top level). The rules file isrulesFile, else the environment variableFORBIDDEN_STRINGS_RULES, elseforbidden-strings.local.txtin the repository top level, which may be absent. A file named byrulesFileor by the variable must exist.
A warn setting reports findings without blocking.
A policy whose protection warn would remove produces a configuration warning event when the file sets it to warn.
Concurrency
hooks.concurrentCommits: boolean, defaultfalse.falseserializes the hooks of concurrent commit transactions;truelets them overlap.indexLock.unprovenOwnerTimeoutMs: non-negative integer, default1000. How long the wrapper backs off for anindex.lockwhose owner is dead or unproven.landing.reserveAfterLostRaces: positive integer, default1. How many lost landing races pass before a transaction asks for the landing reservation.
The concurrent transaction itself cannot be turned off.
Waiting and time limits
The wrapper never waits for another process without a limit. Three environment variables set the limits, each a positive whole number of seconds; a zero, negative, fractional or non-numeric value is ignored.
CLI_GIT_LOCK_WAIT_SECONDS, default1800. How long a command waits for a lock that a live process holds: the capture, landing, hook and registry locks, the landing reservation, the worktree-copy settlement and a liveindex.lockholder. After five seconds of waiting the command prints one line naming the process and the limit. At the limit it stops with a message that names the holder, leaves the lock in place and changes nothing else, so the command can simply be run again.CLI_GIT_PUSH_DEADLINE_SECONDS, default600. How long the push after a landed commit may run before it is stopped. The commit stays saved, the failure is reported, andgit pushcan be run by hand. Git's own read-only queries before the push stop at this deadline or 120 seconds, whichever is shorter.CLI_GIT_PUSH_LOCK_WAIT_SECONDS, default the push deadline plus 65. How long a commit waits behind another process's push before it gives up and reports its push as failed.
A command that passes through to Git unchanged, and the user's own hooks, run as long as they run and receive Ctrl-C like any Git command.
State on disk
The wrapper keeps its state beside the repository's Git data under cli-git* paths in the common Git directory.
Delete nothing there while a Git command is running.
The next invocation recovers what an interrupted one left.
The ignored-state copy into a new linked worktree stages its files in a .cli-git-worktree-copy-* directory
beside the new worktree, which can hold gigabytes.
The wrapper records that directory's name in the common Git directory before it creates it,
so a copy that was killed while staging cannot leak it:
the next invocation in the repository removes the directory and says so on stderr.
Delete a .cli-git-worktree-copy-* directory by hand only when no git command is running in that repository.
Development
The commands below run in the repository that holds the crate,
Aquaticat/Monochromatic,
from the repository top level.
Every task runs through mise.
# Run from the repository top level.
native:checktype-checks every target.native:clippylints every target with warnings denied.native:buildwrites the debug executable topackage/git-policy/cli/target/debug/cli-git-native.native:test:hostruns the unit tests andnative:test:binary:hostruns the tests that start the built executable, both against the host's Git while iterating.native:test:containeris the authority. It builds the crate and runs the tests and Clippy in bounded containers with Git 2.56.0 and no network. SetGIT_POLICY_NATIVE_IMAGE_TAG=<name>to keep the images of parallel runs apart.native:formatformats the Rust sources.native:lock:membersrecords a changed dependency list or version inCargo.lockwithout moving any other locked version.
A release build of the executable:
# Run in package/git-policy/cli.
The release profile keeps panic = "unwind" and overflow-checks = true.
The linked scanner turns a matcher panic into a failing finding by catching the unwind,
and the crate refuses to compile under panic = "abort".
License
LGPL-3.0-or-later.
The license texts are in LICENSES/.