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 recorded movement history (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 journaled ref-history MMR. 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 keygen [--algorithm ed25519|secp256k1|p256] [--force] [--print-pubkey]
234 Generate a new signing key (defaults to Ed25519)
235 cherry-pick [-n] [-m <parent-number>] [--format=json] <hash> | --continue | --abort
236 Apply a commit to the current branch (-n stages the change
237 without committing; -m/--mainline selects the mainline
238 parent when replaying a merge commit, like git;
239 --format=json emits a JSON outcome object)
240 revert [-n] [--no-edit] [--format=json] <commit> | --continue | --abort
241 Create a new commit undoing <commit> (forward commit;
242 conflict-aware; -n stages the revert without committing;
243 --format=json emits a JSON outcome object)
244 rebase <branch> Replay commits onto a different base
245 rebase -i <branch> Interactive: reorder/drop/reword/squash/fixup the todo
246 rebase --continue Continue rebase after conflict resolution
247 rebase --abort Abort rebase and restore original state
248 (--format=json on any form emits a JSON outcome object)
249 bisect start Begin binary search for a bug
250 bisect good [hash] Mark a commit as good
251 bisect bad [hash] Mark a commit as bad
252 bisect run <cmd>... Auto-bisect: run <cmd> per candidate, classify by
253 exit status (0=good/125=skip/1-127=bad), print first bad
254 bisect reset End bisect and restore original state
255 gc [-n] [--grace-secs SECS]
256 Reclaim unreachable objects older than the grace
257 window (default 14d); -n/--dry-run previews
258 sparse-checkout Manage sparse checkout patterns
259 serve <path> Start SSH transport server (internal)
260 mcp [--repository <path>]
261 Start a Model Context Protocol server on stdio so LLM
262 agents can drive this repository (status/diff/log/add/
263 commit/branch + verify/attest); --repository confines
264 tool calls to that path
265 pack-shard [--out <dir>] [--force] <hash>
266 Encode a stored pack into Reed-Solomon shards (--out sets
267 the output dir, default .mkit/pack-shards; --force encodes
268 below the size threshold) (feature: pack-shards)
269 git export <dest> Export refs to a git mirror, one-way; --passthrough
270 publishes an imported repo as a true git fork (feature: git-bridge)
271 git import <url> [<dir>] Import a git upstream as a signed downstream fork (feature: git-bridge)
272 git fetch|pull Update refs/remotes/<name>/* and imported tags from the
273 upstream (locally-moved tags are never clobbered);
274 pull also fast-forwards the current branch (feature: git-bridge)
275 git verify Verify bridge state against the local store
276 (--fork-audit re-derives referenced content) (feature: git-bridge)
277 git status Show bridge state dirs: direction, endpoints, key, refs (feature: git-bridge)
278 git format-patch <range> Render native commits as `git am`-able patches (feature: git-bridge)
279 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>
280 Show line-level commit attribution; -L limits to a line
281 range, -w ignores whitespace, -M/-C detect moved/copied
282 lines (inline -M<num>/-C<num> sets the threshold),
283 --ignore-rev/--ignore-revs-file skip noise commits,
284 --ignore-rev-precise refines that fall-through by content
285 (mkit-only), --first-parent limits the merge-aware walk to
286 first parents, --reverse <start>..<end> walks history
287 forward, <rev> blames as of a revision (default HEAD; JSONL
288 with --format=json, or git-shaped
289 --porcelain/--line-porcelain)
290 verify <rev> [--trusted] [--trust-roots <path>]
291 Verify the signature on a commit, remix, or signed tag;
292 --trusted (or --trust-roots) also cross-checks the
293 signer against the trust-roots registry, failing
294 closed on an unlisted signer
295 attest [--commit <hash>] [--algorithm <alg>] [--signer <kind>] [--predicate-type <URI>] [--predicate-file <path>]
296 [--additional-signer \"algorithm=<alg>,signer=<kind>[,path=<p>]\"]... [--external-signer-arg <V>]...
297 Produce a signed DSSE attestation for a commit
298 (--external-signer-arg is repeatable; the supplied list
299 replaces attest.external_signer_args from config)
300 verify-attest [--commit <hash>] [--trust-roots <path>] [--algorithm <filter>] [--format=json]
301 Verify every attestation attached to a commit (per-signature
302 verdicts to stderr; --format=json also emits a JSON result)
303 trust add <keyid> <pubkey-hex> [--kind <kind>] [--trust-roots <path>] [--force]
304 trust list [--trust-roots <path>] [--json]
305 trust remove <keyid> [--trust-roots <path>] --yes
306 Manage the trust-roots registry `verify --trusted` and
307 `verify-attest` read from
308 self update [--version <tag>] [--check] [--allow-downgrade] [--format human|json]
309 Update this binary in place from a signed GitHub
310 Release, verifying the mkit-native release
311 attestation against keys embedded at build time.
312 Only for installer-managed binaries (curl mkit.sh |
313 sh); refuses with guidance under brew/cargo.
314 --check only reports; `latest` never downgrades
315 version Print version. Also available as the top-level
316 `--version` / `-V` flags; all emit `mkit <X.Y.Z>`.
317
318global flags (before <command>):
319 -C <path> Run as if started in <path> (repeatable, like git)
320 -c <key>=<value> One-shot config override for this invocation (inert /
321 allowlisted keys only; security-sensitive keys refused)
322 --no-pager|-P Accepted no-op (mkit never paginates)
323";
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 fn contains_word(haystack: &str, needle: &str) -> bool {
335 fn is_word_char(c: char) -> bool {
336 c.is_ascii_alphanumeric() || c == '-'
337 }
338 haystack.match_indices(needle).any(|(idx, m)| {
339 let before_ok = haystack[..idx]
340 .chars()
341 .next_back()
342 .is_none_or(|c| !is_word_char(c));
343 let after_ok = haystack[idx + m.len()..]
344 .chars()
345 .next()
346 .is_none_or(|c| !is_word_char(c));
347 before_ok && after_ok
348 })
349 }
350
351 #[test]
352 fn help_contains_every_documented_subcommand() {
353 let required = [
357 "init",
358 "add",
359 "rm",
360 "mv",
361 "restore",
362 "reset",
363 "hash",
364 "cat",
365 "cat-file",
366 "tree",
367 "ls-tree",
368 "ls-files",
369 "rev-parse",
370 "show",
371 "show-ref",
372 "for-each-ref",
373 "symbolic-ref",
374 "update-ref",
375 "ref",
376 "commit",
377 "log",
378 "reflog",
379 "status",
380 "diff",
381 "branch",
382 "checkout",
383 "clean",
384 "tag",
385 "config",
386 "merge",
387 "push",
388 "pull",
389 "fetch",
390 "stash",
391 "clone",
392 "remote",
393 "key",
394 "keygen",
395 "cherry-pick",
396 "rebase",
397 "bisect",
398 "sparse-checkout",
399 "self",
400 "serve",
401 "mcp",
402 "pack-shard",
403 "blame",
404 "verify",
405 "trust",
406 "version",
407 "worktree",
408 ];
409 for cmd in required {
410 assert!(
411 contains_word(HELP_TEXT, cmd),
412 "HELP_TEXT missing documented subcommand: {cmd}"
413 );
414 }
415 }
416}