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] [<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 [<object>...] Display objects (default HEAD): a commit/remix with its
48 diff vs the first parent, a tag then its target, a tree
49 listing, or a blob's contents
50 tree Snapshot working directory as a tree object
51 ls-tree [-r] [-z] <tree-ish> [<path>...]
52 List a tree's entries as `<mode> <type> <hash>\t<name>`
53 (-r recurses; -z NUL-terminates with raw paths)
54 ls-files [-s] [-z] [--others] [--ignored] [--exclude-standard]
55 List tracked files (-s adds stage info; --others lists
56 untracked; --exclude-standard drops ignored)
57 rev-parse [--verify] [--short[=N]] [--abbrev-ref] [--show-toplevel] [<rev>...]
58 Resolve revisions to object ids (--short abbreviates,
59 --abbrev-ref HEAD prints the branch, --show-toplevel
60 prints the repo root)
61 show-ref [--heads] [--tags] List refs as `<hash> <refname>`
62 for-each-ref [--format=<fmt>] [<pattern>...]
63 Iterate refs, optionally with a %(atom) format string
64 symbolic-ref [--short] <name> [<ref>]
65 Read a symbolic ref, or (with <ref>) repoint it
66 (e.g. symbolic-ref HEAD refs/heads/main)
67 update-ref [-d] <ref> [<newvalue> [<oldvalue>]]
68 Create/update/delete refs/heads/* or refs/tags/*
69 (<oldvalue> compare-and-swap; all-zero = must be absent,
70 update mode only; -d's <oldvalue> must be concrete)
71 commit [-a] [--amend] [-m <msg>] Create a signed commit (opens $EDITOR if -m omitted)
72 commit --amend [-m <msg>] Replace HEAD: re-commit on HEAD's parent, re-sign,
73 move the branch. Reuses HEAD's message if -m omitted.
74 The superseded commit becomes unreachable until `gc` ships.
75 log [--oneline] [--abbrev-commit] [--abbrev[=N]] [--format=json] [--graph] [-n N] [<rev> | <A>..<B> | <A>...<B>]
76 Show commit history (default prints the full message
77 body + a UTC date; --oneline/--abbrev-commit abbreviate
78 the commit id, --abbrev[=N] sets the length (default 7);
79 --format=json emits JSONL with the raw timestamp;
80 --graph is accepted but currently a no-op). Optional
81 <rev> starts the walk there; <A>..<B> shows commits in B
82 not in A; <A>...<B> the symmetric difference (empty
83 side = HEAD)
84 reflog [<ref>] [--format=json] [-n N]
85 Show a branch's recorded movement history (read-only).
86 Lists the branch's first-parent chain (newest first,
87 addressed <ref>@{N}); defaults to HEAD's branch. With
88 --features history-mmr, cross-checks each entry against
89 the journaled ref-history MMR. Not a full Git reflog:
90 @{N} indexes the reachable chain, so superseded commits
91 (after amend/reset) are not listed.
92 status [--porcelain[=v1|v2]] [-s|--short] [-z]
93 Show staged and working tree changes (--porcelain, or
94 its -s/--short alias, emits machine-readable XY lines;
95 --porcelain=v2 emits git's richer per-path format with
96 modes + object ids; special-byte paths are C-style
97 quoted; -z NUL-terminates records with raw paths)
98 diff [--staged|--cached] [--name-only|--name-status|--stat] [-z] [<rev> [<rev>] | <a>..<b> | <a>...<b>] [<path>...]
99 Show changes as a unified patch (HEAD vs workdir,
100 --staged for HEAD vs index, a single revision vs the
101 worktree, two revisions, an A..B range, or an A...B
102 symmetric range = merge-base(a,b) vs b; revisions
103 are refs, commits, or short hashes). --name-only lists
104 changed paths; --name-status prefixes each with an
105 A/D/M (T = mode change) letter; --stat shows per-file
106 change counts + a +/- graph and a summary line; -z
107 NUL-terminates name-only/-status records with raw paths
108 (else special-byte paths are C-style quoted)
109 branch [-v|--verbose] [--format=json]
110 List branches (* marks current; no commit id by
111 default, like git; -v adds the abbreviated id +
112 subject; JSONL with --format=json)
113 branch <name> Create a branch at HEAD
114 branch -d <name> Delete a branch (safe; refuses the current branch)
115 branch -D <name> Force-delete a branch (errors on an absent branch,
116 like git; still refuses the current branch)
117 branch -m [<old>] <new> Rename a branch (current branch if <old> omitted)
118 checkout <branch> Switch HEAD to a branch and restore files
119 clean [-n] [-f] [-d] [-x|-X] [<path>...]
120 Remove untracked files (refuses without -f; -n
121 previews). -d also removes untracked dirs; -x includes
122 ignored files, -X removes only ignored
123 tag [<name>] [<commit>] List tags, or create a lightweight tag
124 tag -a <name> [-m <msg>] [<commit>] Create an annotated tag object
125 tag -s <name> [-m <msg>] [<commit>] Create a signed (Ed25519) tag object
126 tag -d <name> Delete a tag
127 config [--format=json] Show all configuration values (JSON with --format=json)
128 config <key> [--format=json] Show one value
129 config <key> <value> Set a configuration value
130 config user.identity <value> Set author Identity
131 (ed25519:<hex>, mid:<N>, or raw [kind][len][bytes] hex)
132 config user.name|user.email <value> Git-compatibility aliases; stored and
133 round-tripped but NON-authoritative — they never set
134 the signed author (use user.identity for that)
135 config trusted_remote_endpoint <url> Trust an HTTP/S3 remote for ambient env credentials
136 config ssh.strict_host_key_checking <yes|no|accept-new> Override SSH host policy
137 config ssh.user_known_hosts_file <path> Custom SSH known_hosts file
138 config ssh.identity_file <path> SSH private key file
139 merge <branch> Merge a branch into HEAD
140 push [<remote>] [--all] [--force|--force-with-lease] [--dry-run]
141 Push current branch to its upstream (--all mirrors every branch)
142 pull Pull changes from remote
143 fetch Download from remote without merging
144 stash Stash working dir changes (save WIP)
145 stash save -m <msg> Stash with a message
146 stash list List stash entries
147 stash pop [N] Apply and remove stash entry N (default 0)
148 stash apply [N] Apply stash entry N without removing it (default 0)
149 stash drop [N] Remove stash entry N without applying
150 stash clear Remove all stash entries
151 stash show [N] Show diff of stash entry N
152 clone [--depth N] [--sparse ...] <url> Clone a repository
153 remote [--format=json] Show remote configuration (JSON with --format=json)
154 remote add [<name>] <url> Add a remote (mkit+file://, mkit+https://, mkit+s3://, mkit+ssh://)
155 remote set [<name>] <url> Alias for 'remote add'
156 remote remove <name> Remove a named remote (`default` clears the flat remote)
157 remote rename <old> <new> Rename a named remote
158 key generate|list|import|export|delete Manage user-scoped keystore keys
159 keygen [--algorithm ed25519|secp256k1|p256] [--force] [--print-pubkey]
160 Generate a new signing key (defaults to Ed25519)
161 cherry-pick <hash> Apply a commit to the current branch
162 revert <commit> | --continue | --abort
163 Create a new commit undoing <commit> (forward commit;
164 conflict-aware)
165 rebase <branch> Replay commits onto a different base
166 rebase -i <branch> Interactive: reorder/drop/reword/squash/fixup the todo
167 rebase --continue Continue rebase after conflict resolution
168 rebase --abort Abort rebase and restore original state
169 bisect start Begin binary search for a bug
170 bisect good [hash] Mark a commit as good
171 bisect bad [hash] Mark a commit as bad
172 bisect reset End bisect and restore original state
173 gc [-n] [--grace-secs SECS]
174 Reclaim unreachable objects older than the grace
175 window (default 14d); -n/--dry-run previews
176 sparse-checkout Manage sparse checkout patterns
177 serve <path> Start SSH transport server (internal)
178 mcp [--repository <path>]
179 Start a Model Context Protocol server on stdio so LLM
180 agents can drive this repository (status/diff/log/add/
181 commit/branch + verify/attest); --repository confines
182 tool calls to that path
183 pack-shard <hash> Encode a stored pack into Reed-Solomon shards (feature: pack-shards)
184 git export <dest> Export refs to a git mirror, one-way; --passthrough
185 publishes an imported repo as a true git fork (feature: git-bridge)
186 git import <url> [<dir>] Import a git upstream as a signed downstream fork (feature: git-bridge)
187 git fetch|pull Update refs/remotes/<name>/* and imported tags from the
188 upstream (locally-moved tags are never clobbered);
189 pull also fast-forwards the current branch (feature: git-bridge)
190 git verify Verify bridge state against the local store
191 (--fork-audit re-derives referenced content) (feature: git-bridge)
192 git status Show bridge state dirs: direction, endpoints, key, refs (feature: git-bridge)
193 git format-patch <range> Render native commits as `git am`-able patches (feature: git-bridge)
194 blame [--format=json] <file>
195 Show line-level commit attribution (JSONL with --format=json)
196 verify <rev> Verify the signature on a commit, remix, or signed tag
197 attest [--commit <hash>] [--algorithm <alg>] [--signer <kind>] [--predicate-type <URI>] [--predicate-file <path>]
198 [--additional-signer \"algorithm=<alg>,signer=<kind>[,path=<p>]\"]...
199 Produce a signed DSSE attestation for a commit
200 verify-attest [--commit <hash>] [--trust-roots <path>] [--algorithm <filter>]
201 Verify every attestation attached to a commit
202 version Print version. Also available as the top-level
203 `--version` / `-V` flags; all emit `mkit <X.Y.Z>`.
204";
205
206#[must_use]
210pub fn strip_comments_and_trim(input: &str) -> String {
211 let mut out = String::with_capacity(input.len());
212 for line in input.split('\n') {
213 let leading = line.trim_start_matches([' ', '\t']);
214 if leading.starts_with('#') {
215 continue;
216 }
217 out.push_str(line);
218 out.push('\n');
219 }
220 out.trim_matches([' ', '\t', '\r', '\n']).to_owned()
221}
222
223pub const COMMIT_EDITMSG_TEMPLATE: &str = "\n# Please enter the commit message for your changes. Lines starting\n# with '#' will be ignored, and an empty message aborts the commit.\n";
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn version_matches_package_version() {
233 assert_eq!(CLI_VERSION, env!("CARGO_PKG_VERSION"));
234 }
235
236 #[test]
237 fn help_contains_every_documented_subcommand() {
238 let required = [
242 "init",
243 "add",
244 "rm",
245 "mv",
246 "restore",
247 "reset",
248 "hash",
249 "cat",
250 "cat-file",
251 "tree",
252 "ls-tree",
253 "ls-files",
254 "rev-parse",
255 "show",
256 "show-ref",
257 "for-each-ref",
258 "symbolic-ref",
259 "update-ref",
260 "commit",
261 "log",
262 "reflog",
263 "status",
264 "diff",
265 "branch",
266 "checkout",
267 "clean",
268 "tag",
269 "config",
270 "merge",
271 "push",
272 "pull",
273 "fetch",
274 "stash",
275 "clone",
276 "remote",
277 "key",
278 "keygen",
279 "cherry-pick",
280 "rebase",
281 "bisect",
282 "sparse-checkout",
283 "serve",
284 "mcp",
285 "pack-shard",
286 "blame",
287 "verify",
288 "version",
289 ];
290 for cmd in required {
291 assert!(
292 HELP_TEXT.contains(cmd),
293 "HELP_TEXT missing documented subcommand: {cmd}"
294 );
295 }
296 }
297
298 #[test]
299 fn strip_comments_drops_hash_lines_and_trims() {
300 let input = "\nhello\n# a comment\nworld\n # indented\n\n";
301 assert_eq!(strip_comments_and_trim(input), "hello\nworld");
302 }
303
304 #[test]
305 fn strip_comments_all_comments_is_empty() {
306 assert_eq!(strip_comments_and_trim("# only\n# comments\n"), "");
307 }
308
309 #[test]
310 fn strip_comments_preserves_interior_blank_lines() {
311 assert_eq!(
312 strip_comments_and_trim("title\n\nbody\n# comment\n"),
313 "title\n\nbody"
314 );
315 }
316}