1pub const CLI_VERSION: &str = env!("CARGO_PKG_VERSION");
12
13pub const HELP_TEXT: &str = "\
17usage: mkit <command> [args]
18
19commands:
20 init Create a new mkit repository
21 add [-A|-u] [-f] <path>... Stage files for the next commit
22 add . Stage all files under cwd (respects .gitignore/.mkitignore)
23 add -A Stage all changes incl. deletions (no path args)
24 add -u Restage only already-tracked files (no path args)
25 add -f <path> Stage an ignored path (overrides .gitignore/.mkitignore)
26 add -p <path>... Interactively choose hunks to stage (y/n/q/a/d per hunk)
27 rm [--cached] [-r] [-f] <path>... Remove path(s) and stage the deletion
28 rm --cached Stage the removal only; keep the worktree file(s)
29 mv [-f] <source>... <dest> Move/rename tracked path(s) and stage it
30 (into <dest> when it is an existing directory; -f
31 overwrites an existing destination)
32 restore [--staged] [--worktree] [--source <rev>] [-f] <path>...
33 Discard worktree changes for path(s) (restore from the
34 index), or --staged to unstage (restore the index entry
35 from HEAD); -f overrides the un-staged-edit guard
36 reset [--soft|--mixed|--hard] [-f] [-q] [<commit>]
37 Move HEAD/branch (--soft) or HEAD + reset the index to
38 the commit's tree (--mixed, default); worktree untouched.
39 --hard also resets the worktree (keeps untracked files);
40 refuses to discard dirty/staged content without -f
41 hash <file> Hash a file and store it as a blob
42 cat <hash> Display an object by its hash
43 cat-file (-t|-s|-p) <object> | cat-file --batch
44 Show an object's type, size, or content
45 (-p: blob bytes, tree listing, or commit/tag summary;
46 --batch reads object names from stdin, takes no <object>)
47 show [--stat] [<object>...] Display objects (default HEAD): a commit/remix
48 with its diff vs the first parent, a tag then its target, a
49 tree listing, or a blob's contents (--stat shows a diffstat
50 instead of the patch for commits/remixes)
51 tree Snapshot working directory as a tree object
52 ls-tree [-r] [-z] <tree-ish> [<path>...]
53 List a tree's entries as `<mode> <type> <hash>\t<name>`
54 (-r recurses; -z NUL-terminates with raw paths)
55 ls-files [-s] [-z] [--others] [--ignored] [--exclude-standard]
56 List tracked files (-s adds stage info; --others lists
57 untracked; --exclude-standard drops ignored)
58 rev-parse [--verify] [--short[=N]] [--abbrev-ref] [--show-toplevel] [<rev>...]
59 Resolve revisions to object ids (--short abbreviates,
60 --abbrev-ref HEAD prints the branch, --show-toplevel
61 prints the repo root; --verify is accepted for git-script
62 compatibility — mkit always errors on a bad revision)
63 merge-base [--is-ancestor] <a> <b> Print the common ancestor of two
64 commits (--is-ancestor tests ancestry: exit 0/1, no output)
65 rev-list [--count] <rev> List commit ids reachable from <rev> (--count
66 prints the number)
67 show-ref [--heads] [--tags] List refs as `<hash> <refname>`
68 for-each-ref [--format=<fmt>] [<pattern>...]
69 Iterate refs, optionally with a %(atom) format string
70 symbolic-ref [--short] <name> [<ref>]
71 Read a symbolic ref, or (with <ref>) repoint it
72 (e.g. symbolic-ref HEAD refs/heads/main)
73 update-ref [-d] <ref> [<newvalue> [<oldvalue>]]
74 Create/update/delete refs/heads/* or refs/tags/*
75 (<oldvalue> compare-and-swap; all-zero = must be absent,
76 update mode only; -d's <oldvalue> must be concrete)
77 ref list [--pattern <glob>] List every ref's full name + resolved hash
78 (`<refname> <hash>`, sorted; covers refs/heads/*,
79 refs/tags/*, refs/remotes/*/*; --pattern is a shell glob
80 on the full name, `*` spans `/`)
81 ref cat <name> Print the resolved hash for one ref (HEAD,
82 refs/heads/<b>, refs/tags/<t>, or refs/remotes/<r>/<b>),
83 following HEAD's symbolic indirection
84 commit [-a] [-q] [--amend] [-m <msg> | -F <file>] [--author <spec>] [--format=json]
85 Create a signed commit (opens $EDITOR if -m/-F omitted).
86 -F reads the message from <file> (`-` = stdin); --author
87 overrides the commit author. After `merge --no-commit`
88 (or a resolved merge) this records a two-parent merge commit.
89 -q suppresses the summary; -S/--gpg-sign, --no-verify,
90 --no-edit are accepted no-ops (mkit always signs, no hooks);
91 --format=json emits a JSON result object on success
92 commit --amend [-m <msg>] Replace HEAD: re-commit on HEAD's parent, re-sign,
93 move the branch. Reuses HEAD's message if -m omitted.
94 The superseded commit becomes unreachable until `gc` ships.
95 log [--oneline] [--abbrev-commit] [--abbrev[=N]] [--format=json] [--graph] [--author PAT] [--grep PAT] [--since DATE] [--until DATE] [--no-merges] [--first-parent] [-n N] [<rev> | <A>..<B> | <A>...<B>]
96 Show commit history (default prints the full message
97 body + a UTC date; --oneline/--abbrev-commit abbreviate
98 the commit id, --abbrev[=N] sets the length (default 7);
99 --format=json emits JSONL with the raw timestamp;
100 --graph is accepted but currently a no-op). --author/
101 --grep filter by substring match on the author identity
102 / commit message; --since/--until bound the commit
103 timestamp (@<unix>, now/today/yesterday, N-unit-ago,
104 or YYYY-MM-DD[ HH:MM:SS]); --no-merges hides
105 merge commits from the output; --first-parent walks
106 only first parents, never entering a merged side
107 branch. Filters apply before -n's limit. Optional
108 <rev> starts the walk there; <A>..<B> shows commits in B
109 not in A; <A>...<B> the symmetric difference (empty
110 side = HEAD)
111 reflog [<ref>] [--format=json] [-n N]
112 Show a branch's first-parent ancestry (read-only).
113 Lists the branch's first-parent chain (newest first,
114 addressed <ref>@{N}); defaults to HEAD's branch. With
115 --features history-mmr, cross-checks each entry against
116 the canonical ancestry snapshot. Not a full Git reflog:
117 @{N} indexes the reachable chain, so superseded commits
118 (after amend/reset) are not listed.
119 status [--porcelain[=v1|v2]] [-s|--short] [-z]
120 Show staged and working tree changes (--porcelain, or
121 its -s/--short alias, emits machine-readable XY lines;
122 --porcelain=v2 emits git's richer per-path format with
123 modes + object ids; special-byte paths are C-style
124 quoted; -z NUL-terminates records with raw paths)
125 diff [--staged|--cached] [--name-only|--name-status|--stat] [--merge-base] [--exit-code|--quiet] [--color[=WHEN]|--no-color] [-w|-b] [-U N] [-z] [<rev> [<rev>] | <a>..<b> | <a>...<b>] [<path>...]
126 Show changes as a unified patch (HEAD vs workdir,
127 --staged for HEAD vs index, a single revision vs the
128 worktree, two revisions, an A..B range, or an A...B
129 symmetric range = merge-base(a,b) vs b; --merge-base
130 diffs the merge base of the given revisions (the flag
131 spelling of A...B); revisions
132 are refs, commits, or short hashes). --name-only lists
133 changed paths; --name-status prefixes each with an
134 A/D/M (T = mode change) letter; --stat shows per-file
135 change counts + a +/- graph and a summary line; -z
136 NUL-terminates name-only/-status records with raw paths
137 (else special-byte paths are C-style quoted); -w/
138 --ignore-all-space and -b/--ignore-space-change control
139 whitespace sensitivity (-w wins if both given); -U<n>/
140 --unified=<n> sets the context-line count (default 3)
141 branch [-v|--verbose] [--format=json]
142 List branches (* marks current; no commit id by
143 default, like git; -v adds the abbreviated id +
144 subject; JSONL with --format=json)
145 branch <name> Create a branch at HEAD
146 branch -d <name> Delete a branch (safe; refuses the current branch)
147 branch -D <name> Force-delete a branch (errors on an absent branch,
148 like git; still refuses the current branch)
149 branch -m [<old>] <new> Rename a branch (current branch if <old> omitted)
150 branch --show-current Print the current branch name
151 branch [--list] [--contains [<c>]] [--no-contains [<c>]] [--merged [<c>]] [--no-merged [<c>]] [<pattern>...]
152 Filter the listing (like git): <pattern> are shell globs on
153 branch names (enabled by --list or any filter; `*`/`?`/`[…]`,
154 `*` spans `/`); --contains keeps branches whose tip has <c>
155 as an ancestor; --merged keeps those merged into <c>; the
156 --no-* forms invert. All four commit args default to HEAD
157 when omitted
158 checkout <branch> Switch HEAD to a branch and restore files
159 checkout -b|-B <new> [<start>] Create (or -B reset) a branch and switch to it
160 switch <branch> Switch branches (git switch)
161 switch -c|-C <new> [<start>] Create (or -C reset) a branch and switch to it
162 clean [-n] [-f] [-d] [-x|-X] [<path>...]
163 Remove untracked files (refuses without -f; -n
164 previews). -d also removes untracked dirs; -x includes
165 ignored files, -X removes only ignored
166 tag [<name>] [<commit>] [--format=json] List tags, or create a lightweight tag
167 tag -l [<pattern>] List tags, optionally filtered by a shell glob
168 (JSONL with --format=json)
169 tag -a <name> [-m <msg>] [--author <spec>] [<commit>] Create an annotated tag
170 tag -s <name> [-m <msg>] [--author <spec>] [<commit>] Create a signed tag
171 tag -d <name> Delete a tag
172 config [--format=json] Show all configuration values (JSON with --format=json)
173 config <key> [--format=json] Show one value
174 config <key> <value> Set a configuration value
175 config --unset <key> Remove a configuration value (idempotent; unknown key errors)
176 config [--local|--global] <key> <value>|--unset <key> Force repo/user scope
177 config user.identity <value> Set author Identity
178 (ed25519:<hex>, mid:<N>, or raw [kind][len][bytes] hex)
179 config user.name|user.email <value> Git-compatibility aliases; stored and
180 round-tripped but NON-authoritative — they never set
181 the signed author (use user.identity for that)
182 config trusted_remote_endpoint <url> Trust an HTTP/S3 remote for ambient env credentials
183 config ssh.strict_host_key_checking <yes|no|accept-new> Override SSH host policy
184 config ssh.user_known_hosts_file <path> Custom SSH known_hosts file
185 config ssh.identity_file <path> SSH private key file
186 merge [--no-commit] [-m <msg>] [--format=json] <branch> | --continue | --abort
187 Merge a branch into HEAD (--no-commit stages the merge and
188 stops before committing; finish with `mkit commit` or
189 `mkit merge --continue`; -m overrides the merge message;
190 --format=json emits a JSON outcome object)
191 push [<remote>] [-u|--set-upstream] [--all] [-f|--force|--force-with-lease] [--dry-run] [--format=json]
192 Push current branch to its upstream (--all mirrors every
193 branch; -u records the upstream; prints git's `To <url>` +
194 ref-update summary, or `Everything up-to-date`;
195 --format=json emits a JSON outcome object)
196 pull [<remote>] [--all] [--format=json] Pull changes from remote (fast-forward;
197 prints `Updating <a>..<b>`/`Fast-forward`/diffstat, or
198 `Already up to date.`); --all pulls every configured remote;
199 --format=json emits a JSON outcome object
200 fetch [<remote>] [--all] [--format=json] Download from remote without merging
201 (prints `From <url>` + per-ref summary; silent when nothing
202 changed); --all fetches every configured remote;
203 --format=json emits a JSON outcome object
204 worktree add <path> [<commit-ish>] Create a linked working tree (shares the
205 object store and refs; own HEAD/index/stash). With no
206 commit-ish, creates a branch named after the path
207 worktree list [--porcelain] List the main and every linked working tree
208 worktree remove [--force] <path> Remove a linked working tree
209 worktree prune [--dry-run] Delete registry entries whose tree is gone
210 stash [--format=json] Stash working dir changes (save WIP; -m for a message;
211 --format=json emits a JSON outcome, or JSONL on `list`)
212 stash save -m <msg> Stash with a message
213 stash list List stash entries (printed as stash@{N})
214 stash pop [--index] [<n>|stash@{n}] Apply and remove a stash entry (default
215 0; --index also restores the staged state, like git)
216 stash apply [--index] [<n>|stash@{n}] Apply a stash entry, keeping it (default
217 0; --index also restores the staged state)
218 stash drop [<n>|stash@{n}] Remove a stash entry without applying
219 stash clear Remove all stash entries
220 stash show [<n>|stash@{n}] Show the diff of a stash entry (default 0)
221 clone [--sparse ...] [-b <branch>] [-o <name>] <url> Clone a repository
222 (-b checks out <branch> instead of the remote default;
223 -o names the cloned remote <name> instead of `default`)
224 remote [-v|--verbose] [--format=json] List remotes (names only; -v adds
225 `<name>\t<url> (fetch)`/`(push)`; JSON with --format=json)
226 remote add [<name>] <url> Add a remote (mkit+file://, mkit+https://, mkit+s3://, mkit+ssh://)
227 remote set [<name>] <url> Alias for 'remote add'
228 remote remove <name> Remove a named remote (`default` clears the flat remote)
229 remote rename <old> <new> Rename a named remote
230 remote get-url <name> Print a remote's URL
231 remote set-url <name> <url> Change a remote's URL
232 key generate|list|import|export|delete Manage user-scoped keystore keys
233 grant create|add|list|revoke Issue, import, list and revoke write and read grants
234 (create --cap read|read,write|write --grantee <hex> --repo <name>|--all
235 [--refs <pattern=cufd>] [--audience <origin>] [--ttl 7d] [--store];
236 a push that moves an existing branch needs the f flag while servers
237 are opaque: issue such grants as cuf;
238 add [--remote <remote>|--offline] <file|->; list [--check] [--json];
239 revoke <remote> [--prune])
240 epoch show|bump <remote> Show or advance a namespace's grant epoch on a remote
241 (bump [--by <n>] revokes grants issued at a lower epoch)
242 visibility set <remote> public|private [--statement]
243 Switch a repository between public and private
244 keygen [--algorithm ed25519|secp256k1|p256] [--force] [--print-pubkey]
245 Generate a new signing key (defaults to Ed25519)
246 cherry-pick [-n] [-m <parent-number>] [--format=json] <hash> | --continue | --abort
247 Apply a commit to the current branch (-n stages the change
248 without committing; -m/--mainline selects the mainline
249 parent when replaying a merge commit, like git;
250 --format=json emits a JSON outcome object)
251 revert [-n] [--no-edit] [--format=json] <commit> | --continue | --abort
252 Create a new commit undoing <commit> (forward commit;
253 conflict-aware; -n stages the revert without committing;
254 --format=json emits a JSON outcome object)
255 rebase <branch> Replay commits onto a different base
256 rebase -i <branch> Interactive: reorder/drop/reword/squash/fixup the todo
257 rebase --continue Continue rebase after conflict resolution
258 rebase --abort Abort rebase and restore original state
259 (--format=json on any form emits a JSON outcome object)
260 bisect start Begin binary search for a bug
261 bisect good [hash] Mark a commit as good
262 bisect bad [hash] Mark a commit as bad
263 bisect run <cmd>... Auto-bisect: run <cmd> per candidate, classify by
264 exit status (0=good/125=skip/1-127=bad), print first bad
265 bisect reset End bisect and restore original state
266 gc [-n] [--grace-secs SECS]
267 Reclaim unreachable objects older than the grace
268 window (default 14d); -n/--dry-run previews
269 sparse-checkout Manage sparse checkout patterns
270 serve <path> Start SSH transport server (internal)
271 mcp [--repository <path>]
272 Start a Model Context Protocol server on stdio so LLM
273 agents can drive this repository (status/diff/log/add/
274 commit/branch + verify/attest); --repository confines
275 tool calls to that path
276 pack-shard [--out <dir>] [--force] <hash>
277 Encode a stored pack into Reed-Solomon shards (--out sets
278 the output dir, default .mkit/pack-shards; --force encodes
279 below the size threshold) (feature: pack-shards)
280 git export <dest> Export refs to a git mirror, one-way; --passthrough
281 publishes an imported repo as a true git fork (feature: git-bridge)
282 git import <url> [<dir>] Import a git upstream as a signed downstream fork (feature: git-bridge)
283 git fetch|pull Update refs/remotes/<name>/* and imported tags from the
284 upstream (locally-moved tags are never clobbered);
285 pull also fast-forwards the current branch (feature: git-bridge)
286 git verify Verify bridge state against the local store
287 (--fork-audit re-derives referenced content) (feature: git-bridge)
288 git status Show bridge state dirs: direction, endpoints, key, refs (feature: git-bridge)
289 git format-patch <range> Render native commits as `git am`-able patches (feature: git-bridge)
290 blame [--format=json|--porcelain|--line-porcelain] [-w] [-M[<num>]] [-C[<num>]] [--ignore-rev <rev>] [--ignore-rev-precise] [--first-parent] [--reverse] [-L <start>,<end>] [<rev>] <file>
291 Show line-level commit attribution; -L limits to a line
292 range, -w ignores whitespace, -M/-C detect moved/copied
293 lines (inline -M<num>/-C<num> sets the threshold),
294 --ignore-rev/--ignore-revs-file skip noise commits,
295 --ignore-rev-precise refines that fall-through by content
296 (mkit-only), --first-parent limits the merge-aware walk to
297 first parents, --reverse <start>..<end> walks history
298 forward, <rev> blames as of a revision (default HEAD; JSONL
299 with --format=json, or git-shaped
300 --porcelain/--line-porcelain)
301 prove <rev> [<path>] [--chunk N | --range OFFSET:LEN] [--with-offsets] [-o FILE] [--format=json]
302 Build a disclosure bundle proving a path, chunk, or
303 byte range belongs to a commit (or remix)
304 verify <rev> [--trusted] [--trust-roots <path>]
305 Verify the signature on a commit, remix, or signed tag;
306 --trusted (or --trust-roots) also cross-checks the
307 signer against the trust-roots registry, failing
308 closed on an unlisted signer
309 verify-proof <commit-id> <bundle|-> [--expect-path PATH] [--trusted] [--trust-roots PATH] [--format=json] [--payload-out FILE]
310 Verify a disclosure bundle against a trusted 64-hex
311 commit id (does not resolve refs)
312 closure export <rev> [--history] [-o DIR] [--force] [--format=json]
313 Export a commit's object-set closure as MANIFEST.mkcl
314 plus raw-only packs (not `mkit git export`)
315 closure verify <commit-id> [--from DIR] [--history] [--show-unreferenced] [--format=json]
316 Verify a closure directory against a trusted id, or
317 check the local store (revision allowed without --from);
318 local mode streams reachable objects by default;
319 --show-unreferenced enumerates every local object and
320 reveals the unreferenced list
321 attest [--commit <hash>] [--algorithm <alg>] [--signer <kind>] [--predicate-type <URI>] [--predicate-file <path>]
322 [--additional-signer \"algorithm=<alg>,signer=<kind>[,path=<p>]\"]... [--external-signer-arg <V>]...
323 Produce a signed DSSE attestation for a commit
324 (--external-signer-arg is repeatable; the supplied list
325 replaces attest.external_signer_args from config)
326 verify-attest [--commit <hash>] [--trust-roots <path>] [--algorithm <filter>] [--format=json]
327 Verify every attestation attached to a commit (per-signature
328 verdicts to stderr; --format=json also emits a JSON result)
329 trust add <keyid> <pubkey-hex> [--kind <kind>] [--trust-roots <path>] [--force]
330 trust list [--trust-roots <path>] [--json]
331 trust remove <keyid> [--trust-roots <path>] --yes
332 Manage the trust-roots registry `verify --trusted` and
333 `verify-attest` read from
334 self update [--version <tag>] [--check] [--allow-downgrade] [--format human|json]
335 Update this binary in place from a GitHub Release.
336 Use OS certificate trust and check the release
337 checksum when present; ignore custom remote CAs.
338 Only for installer-managed binaries (curl mkit.sh |
339 sh); refuses with guidance under brew/cargo.
340 --check only reports; `latest` never downgrades
341 version Print version. Also available as the top-level
342 `--version` / `-V` flags; all emit `mkit <X.Y.Z>`.
343
344global flags (before <command>):
345 -C <path> Run as if started in <path> (repeatable, like git)
346 -c <key>=<value> One-shot config override for this invocation (inert /
347 allowlisted keys only; security-sensitive keys refused)
348 --no-pager|-P Accepted no-op (mkit never paginates)
349";
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 fn contains_word(haystack: &str, needle: &str) -> bool {
361 fn is_word_char(c: char) -> bool {
362 c.is_ascii_alphanumeric() || c == '-'
363 }
364 haystack.match_indices(needle).any(|(idx, m)| {
365 let before_ok = haystack[..idx]
366 .chars()
367 .next_back()
368 .is_none_or(|c| !is_word_char(c));
369 let after_ok = haystack[idx + m.len()..]
370 .chars()
371 .next()
372 .is_none_or(|c| !is_word_char(c));
373 before_ok && after_ok
374 })
375 }
376
377 #[test]
378 fn help_contains_every_documented_subcommand() {
379 let required = [
383 "init",
384 "add",
385 "rm",
386 "mv",
387 "restore",
388 "reset",
389 "hash",
390 "cat",
391 "cat-file",
392 "tree",
393 "ls-tree",
394 "ls-files",
395 "rev-parse",
396 "show",
397 "show-ref",
398 "for-each-ref",
399 "symbolic-ref",
400 "update-ref",
401 "ref",
402 "commit",
403 "log",
404 "reflog",
405 "status",
406 "diff",
407 "branch",
408 "checkout",
409 "clean",
410 "tag",
411 "config",
412 "merge",
413 "push",
414 "pull",
415 "fetch",
416 "stash",
417 "clone",
418 "remote",
419 "key",
420 "keygen",
421 "cherry-pick",
422 "rebase",
423 "bisect",
424 "sparse-checkout",
425 "self",
426 "serve",
427 "mcp",
428 "pack-shard",
429 "blame",
430 "prove",
431 "verify",
432 "verify-proof",
433 "closure",
434 "trust",
435 "version",
436 "worktree",
437 ];
438 for cmd in required {
439 assert!(
440 contains_word(HELP_TEXT, cmd),
441 "HELP_TEXT missing documented subcommand: {cmd}"
442 );
443 }
444 }
445}