OXYGENHUB
# a5f76c79666c oxygen / src / cli / mod.rs
19.0 KiB · Rust · last changed Subrepositories: subrepo node kind with Oxygen and Git pins, .oxymodules, nested repositories skipped by the walker and moved by checkout, oxy submodule add|update|status|remove, Git gitlink import and export (spec 0.8 section 7.8) 1 month ago source blame history raw
fce44ae9 Andy McCall ↶ 1
//! Command-line definitions and dispatch.
2
3
mod format;
4
mod identity;
5
mod sync;
135212de Andy McCall ↶ 6
pub mod ui;
fce44ae9 Andy McCall ↶ 7
mod workspace;
8
9
use anyhow::{Context, Result};
10
use clap::{Parser, Subcommand};
11
use oxy_core::repo::Repo;
12
use std::path::PathBuf;
13
14
#[derive(Parser)]
15
#[command(name = "oxy", version, about = "Oxygen: breathing fresh air into version control")]
16
struct Cli {
135212de Andy McCall ↶ 17
    /// Plain output: no colour, no spinner (also implied by NO_COLOR or a non-terminal)
18
    #[arg(long, global = true)]
19
    plain: bool,
fce44ae9 Andy McCall ↶ 20
    #[command(subcommand)]
21
    cmd: Cmd,
22
}
23
24
#[derive(Subcommand)]
25
enum Cmd {
26
    /// Create a repository in PATH (default: current directory)
27
    Init { path: Option<PathBuf> },
28
    /// Clone a repository from a local path or an http(s) URL
b3c5a59a Andy McCall ↶ 29
    Clone {
30
        url: String,
31
        dest: Option<PathBuf>,
32
        /// Leave blobs larger than this (e.g. 1M) to be fetched on first read
33
        #[arg(long)]
34
        max_blob: Option<String>,
7421309f Andy McCall ↶ 35
        /// Materialize only these paths (repeatable); implies lazy blobs
36
        #[arg(long = "sparse")]
37
        sparse: Vec<String>,
2863456a Andy McCall ↶ 38
        /// Shallow clone: fetch only this many levels of history below the heads
39
        #[arg(long)]
40
        depth: Option<u32>,
b3c5a59a Andy McCall ↶ 41
    },
2863456a Andy McCall ↶ 42
    /// Fetch more history below a shallow clone's boundary
43
    Deepen {
44
        #[arg(default_value_t = 10)]
45
        depth: u32,
46
        #[arg(long, default_value = "origin")]
47
        remote: String,
48
    },
7421309f Andy McCall ↶ 49
    /// Show or set the sparse filter (paths to materialize); re-checks out the bookmark
50
    Sparse { paths: Vec<String> },
fce44ae9 Andy McCall ↶ 51
    /// Show the working copy: changed paths and conflicts
50a046e4 Andy McCall ↶ 52
    Status {
53
        /// Machine-readable output: paths, and every conflict with its kind and contributing changes
54
        #[arg(long)]
55
        json: bool,
56
    },
135212de Andy McCall ↶ 57
    /// One-line summary for shell prompts; fast and records nothing
58
    Prompt {
59
        /// Placeholders: %b bookmark, %c change id, %h heads, %d dirty (*), %x conflict (!), %A/%D ahead/behind (+n/-n), %a/%B raw counts
60
        #[arg(long, default_value = oxy_core::repo::DEFAULT_PROMPT_FORMAT)]
61
        format: String,
62
    },
fce44ae9 Andy McCall ↶ 63
    /// Record the working copy as a change on the current bookmark
64
    Commit {
65
        #[arg(short, long)]
66
        message: String,
67
        /// Extra explicit dependencies (ChangeId prefix, #hash prefix)
68
        #[arg(long = "depends-on")]
69
        depends_on: Vec<String>,
70
    },
71
    /// List changes reachable from the bookmark and the working copy
94084087 Andy McCall ↶ 72
    Log {
73
        /// Show at most this many changes
74
        #[arg(short = 'n', long)]
75
        limit: Option<usize>,
76
        /// Show full messages, dependencies, and signatures
77
        #[arg(short, long)]
78
        verbose: bool,
79
    },
fce44ae9 Andy McCall ↶ 80
    /// Show what the working copy changes against its base
81
    Diff,
614541c3 Andy McCall ↶ 82
    /// Change the message of a head (default: the head the working copy sits on)
83
    Describe {
84
        #[arg(short, long)]
85
        message: String,
86
        change: Option<String>,
87
    },
23b217d7 Andy McCall ↶ 88
    /// Fold the working copy into the head it sits on, or into CHANGE with --into
614541c3 Andy McCall ↶ 89
    Amend {
90
        #[arg(short, long)]
91
        message: Option<String>,
23b217d7 Andy McCall ↶ 92
        /// Amend an older change; the edits must touch only lines it already knows
93
        #[arg(long)]
94
        into: Option<String>,
614541c3 Andy McCall ↶ 95
    },
23b217d7 Andy McCall ↶ 96
    /// Split a head: the named paths become a separate first change
97
    Split {
98
        change: String,
99
        paths: Vec<String>,
100
    },
614541c3 Andy McCall ↶ 101
    /// Drop a head from the bookmark; its content stays on disk as uncommitted work
102
    Abandon { change: String },
fce44ae9 Andy McCall ↶ 103
    /// Show one change (ChangeId prefix, #hash prefix, or @)
104
    Show { change: String },
105
    /// Move the working copy to the state formed by CHANGE... (default: the bookmark heads)
106
    Checkout {
107
        changes: Vec<String>,
108
        /// Throw away uncommitted working-copy changes
109
        #[arg(long)]
110
        discard: bool,
111
    },
112
    /// Report which conflicts of the working copy's base are resolved by the current files
113
    Resolve,
114
    /// Undo the last operation
115
    Undo,
252e8cb2 Andy McCall ↶ 116
    /// Write a checkpoint of the current bookmark's state now
117
    Checkpoint,
ba160f60 Andy McCall ↶ 118
    /// Pack loose objects and prune unreachable ones older than the grace period
119
    Gc {
120
        /// Ignore the grace period (core.gc-grace) and prune immediately
121
        #[arg(long)]
122
        now: bool,
123
    },
fce44ae9 Andy McCall ↶ 124
    /// List operations
125
    #[command(name = "op")]
126
    Op {
127
        #[command(subcommand)]
128
        cmd: OpCmd,
129
    },
cdb5beba Andy McCall ↶ 130
    /// Take the advisory local lock on a lockable file, making it writable
131
    Lock { path: String },
132
    /// Release the local lock and make the file read-only again
133
    Unlock { path: String },
4e88e774 Andy McCall ↶ 134
    /// Workspaces: extra checkout directories sharing this repository
135
    Workspace {
136
        #[command(subcommand)]
137
        cmd: WorkspaceCmd,
138
    },
7b9809e6 Andy McCall ↶ 139
    /// Bookmarks: named states; each workspace works on one
140
    Bookmark {
141
        #[command(subcommand)]
142
        cmd: BookmarkCmd,
143
    },
144
    /// Union another bookmark's heads into the current one (no merge change)
145
    Merge { bookmark: String },
4dee277c Andy McCall ↶ 146
    /// Subrepositories: nested repositories pinned to a state
147
    Submodule {
148
        #[command(subcommand)]
149
        cmd: SubmoduleCmd,
150
    },
132b884b Andy McCall ↶ 151
    /// Tags: immutable named states
152
    Tag {
153
        #[command(subcommand)]
154
        cmd: TagCmd,
155
    },
fce44ae9 Andy McCall ↶ 156
    /// Manage remotes
157
    Remote {
158
        #[command(subcommand)]
159
        cmd: RemoteCmd,
160
    },
161
    /// Fetch every bookmark of a remote into its tracking references
162
    Fetch { remote: Option<String> },
163
    /// Fetch, then union the remote bookmark into the local one (no merge change)
164
    Pull { remote: Option<String> },
165
    /// Push the current bookmark to a remote
166
    Push {
167
        remote: Option<String>,
168
        /// Allow dropping heads the remote has
169
        #[arg(long)]
170
        force: bool,
7b9809e6 Andy McCall ↶ 171
        /// Push this bookmark instead of the workspace's current one
172
        #[arg(short = 'b', long)]
173
        bookmark: Option<String>,
fce44ae9 Andy McCall ↶ 174
    },
2b5ee371 Andy McCall ↶ 175
    /// Serve repositories under ROOT over HTTPS (with --tls-cert/--tls-key) or plain HTTP
fce44ae9 Andy McCall ↶ 176
    Serve {
177
        /// Directory with one repository per subdirectory
178
        #[arg(long, default_value = ".")]
179
        root: PathBuf,
180
        #[arg(long, default_value = "127.0.0.1:8890")]
181
        bind: String,
182
        /// Bearer token allowed to push (repeatable); none means open
183
        #[arg(long = "token")]
184
        tokens: Vec<String>,
2b5ee371 Andy McCall ↶ 185
        /// PEM certificate chain; enables HTTPS together with --tls-key
186
        #[arg(long, requires = "tls_key")]
187
        tls_cert: Option<PathBuf>,
188
        /// PEM private key
189
        #[arg(long, requires = "tls_cert")]
190
        tls_key: Option<PathBuf>,
fce44ae9 Andy McCall ↶ 191
    },
192
    /// Manage the signing identity
193
    Identity {
194
        #[command(subcommand)]
195
        cmd: IdentityCmd,
196
    },
197
    /// Verify the signatures on a change (default: every change in the log)
198
    Verify { change: Option<String> },
79ab8528 Andy McCall ↶ 199
    /// Git interoperability
200
    Git {
201
        #[command(subcommand)]
202
        cmd: GitCmd,
203
    },
83e0677b Andy McCall ↶ 204
    /// Manage the trust policy (who may update which bookmarks)
205
    Policy {
206
        #[command(subcommand)]
207
        cmd: PolicyCmd,
208
    },
7421309f Andy McCall ↶ 209
    /// Set a configuration key (author.*, core.sign, core.op-retention, core.gc-grace, core.checkpoint-interval, filter.max-blob, filter.paths, workspace.bookmark, remote.<name>.url|token|ca)
fce44ae9 Andy McCall ↶ 210
    Config {
211
        key: String,
212
        value: String,
213
        /// Write to the user file (~/.config/oxy/config.toml) instead of this repository
214
        #[arg(long)]
215
        user: bool,
216
    },
217
}
218
219
#[derive(Subcommand)]
220
enum OpCmd {
221
    Log,
222
}
223
224
#[derive(Subcommand)]
4e88e774 Andy McCall ↶ 225
enum WorkspaceCmd {
226
    /// Create a workspace at PATH checked out on CHANGE... (default: the bookmark heads)
227
    Add { path: PathBuf, changes: Vec<String> },
228
    List,
229
    /// Stop tracking a workspace (by id prefix or path); its files stay
230
    Forget { workspace: String },
231
}
232
233
#[derive(Subcommand)]
7b9809e6 Andy McCall ↶ 234
enum BookmarkCmd {
235
    /// Create a bookmark at CHANGE... (default: the working copy's base)
236
    Create { name: String, changes: Vec<String> },
237
    /// Point an existing bookmark at CHANGE...
238
    Set { name: String, changes: Vec<String> },
239
    /// Remove a bookmark; its changes stay
240
    Delete { name: String },
241
    /// List bookmarks with their heads and difference from origin
242
    List,
243
    /// Make NAME this workspace's bookmark and check it out
244
    Switch {
245
        name: String,
246
        /// Throw away uncommitted working-copy changes
247
        #[arg(long)]
248
        discard: bool,
249
    },
250
}
251
252
#[derive(Subcommand)]
4dee277c Andy McCall ↶ 253
enum SubmoduleCmd {
254
    /// Clone URL (Oxygen or Git) into PATH and register it in .oxymodules
255
    Add {
256
        url: String,
257
        path: String,
258
        /// Bookmark (Oxygen) or branch (Git) to check out and track
259
        #[arg(long)]
260
        bookmark: Option<String>,
261
    },
262
    /// Bring nested repositories to their pins
263
    Update {
264
        /// Clone nested repositories that are absent
265
        #[arg(long)]
266
        init: bool,
267
        paths: Vec<String>,
268
    },
269
    /// List subrepos with their pins and on-disk state
270
    Status,
271
    /// Unregister PATH; the directory stays, the node goes with the next commit
272
    Remove { path: String },
273
}
274
275
#[derive(Subcommand)]
132b884b Andy McCall ↶ 276
enum TagCmd {
277
    /// Tag the state of CHANGE... (default: the bookmark heads)
278
    Create {
279
        name: String,
280
        changes: Vec<String>,
281
        #[arg(short, long, default_value = "")]
282
        message: String,
283
    },
284
    List,
285
}
286
287
#[derive(Subcommand)]
fce44ae9 Andy McCall ↶ 288
enum RemoteCmd {
289
    Add { name: String, url: String },
290
    Remove { name: String },
291
    List,
292
}
293
294
#[derive(Subcommand)]
295
enum IdentityCmd {
296
    /// Generate a composite Ed25519 + ML-DSA-65 key pair and make it your author identity
297
    Create {
298
        #[arg(long)]
299
        name: Option<String>,
300
        #[arg(long)]
301
        email: Option<String>,
302
        /// Record the author in this repository only, not in the user config
303
        #[arg(long)]
304
        local: bool,
28fc23d7 Andy McCall ↶ 305
        /// Expire the identity this many days from now
306
        #[arg(long)]
307
        expires: Option<u64>,
fce44ae9 Andy McCall ↶ 308
    },
309
    /// Show the configured identity
310
    Show,
83e0677b Andy McCall ↶ 311
    /// Write your identity object to a file, to hand to a policy holder
312
    Export { path: Option<PathBuf> },
28fc23d7 Andy McCall ↶ 313
    /// Move to a fresh key pair; the policy follows if it names your key
314
    Rotate,
315
    /// Re-issue your identity with a new expiry (in days from now; 0 = never)
316
    Renew {
317
        #[arg(long)]
318
        expires: u64,
319
    },
320
    /// Revoke a key: yours by default, or any key if you hold a policy root
321
    Revoke {
322
        /// KeyId prefix (default: your own key)
323
        key: Option<String>,
324
        #[arg(long, default_value = "")]
325
        reason: String,
326
    },
327
    /// Keys allowed to sign or seal on your behalf
328
    Delegate {
329
        #[command(subcommand)]
330
        cmd: DelegateCmd,
331
    },
332
}
333
334
#[derive(Subcommand)]
335
enum DelegateCmd {
336
    /// Add a delegate from an exported identity file
337
    Add { identity_file: PathBuf },
338
    /// Remove a delegate by KeyId prefix
339
    Remove { key: String },
340
    /// List delegates
341
    List,
fce44ae9 Andy McCall ↶ 342
}
343
83e0677b Andy McCall ↶ 344
#[derive(Subcommand)]
79ab8528 Andy McCall ↶ 345
enum GitCmd {
346
    /// Export the current bookmark to a Git repository (created bare if absent)
30ea9c1e Andy McCall ↶ 347
    Export {
348
        git_dir: PathBuf,
349
        /// Git branch to write (default: the bookmark name)
350
        #[arg(long)]
351
        branch: Option<String>,
352
    },
353
    /// Fetch a branch from a Git remote (path or URL) into the current bookmark
354
    Fetch {
355
        url: String,
356
        #[arg(long, default_value = "main")]
357
        branch: String,
358
    },
359
    /// Push the current bookmark to a branch of a Git remote (fetch first)
360
    Push {
361
        url: String,
362
        #[arg(long, default_value = "main")]
363
        branch: String,
364
    },
c3069537 Andy McCall ↶ 365
    /// Import a Git branch into a new Oxygen repository at DEST
366
    Import {
367
        git_dir: PathBuf,
368
        dest: PathBuf,
369
        #[arg(long, default_value = "main")]
370
        branch: String,
371
    },
79ab8528 Andy McCall ↶ 372
}
373
374
#[derive(Subcommand)]
83e0677b Andy McCall ↶ 375
enum PolicyCmd {
376
    /// Create the initial policy with your identity as the only root
377
    Init,
378
    /// Show the current policy
379
    Show,
380
    /// Allow an identity (from `oxy identity export`) to update bookmarks
381
    Grant {
382
        identity_file: PathBuf,
383
        /// Reference pattern the grant applies to (default: every bookmark)
384
        #[arg(long)]
385
        pattern: Option<String>,
28fc23d7 Andy McCall ↶ 386
        /// Where to write the proposal when more root signatures are needed
387
        #[arg(long)]
388
        out: Option<PathBuf>,
83e0677b Andy McCall ↶ 389
    },
390
    /// Revoke a key (KeyId prefix of an identity in the policy)
28fc23d7 Andy McCall ↶ 391
    Revoke {
392
        key: String,
393
        /// Where to write the proposal when more root signatures are needed
394
        #[arg(long)]
395
        out: Option<PathBuf>,
396
    },
397
    /// Manage root keys and the signature threshold for policy updates
398
    Root {
399
        #[command(subcommand)]
400
        cmd: RootCmd,
401
    },
402
    /// Add your root signature to a proposal file; applies it once the threshold is met
403
    Sign {
404
        proposal: PathBuf,
405
        /// Where to write the proposal if it still needs signatures (default: in place)
406
        #[arg(long)]
407
        out: Option<PathBuf>,
408
    },
409
}
410
411
#[derive(Subcommand)]
412
enum RootCmd {
413
    /// Make an exported identity a root key
414
    Add {
415
        identity_file: PathBuf,
416
        #[arg(long)]
417
        out: Option<PathBuf>,
418
    },
419
    /// Remove a root key by KeyId prefix
420
    Remove {
421
        key: String,
422
        #[arg(long)]
423
        out: Option<PathBuf>,
424
    },
425
    /// Require this many root signatures on policy updates
426
    Threshold {
427
        n: u32,
428
        #[arg(long)]
429
        out: Option<PathBuf>,
430
    },
83e0677b Andy McCall ↶ 431
}
432
fce44ae9 Andy McCall ↶ 433
/// Open the repository containing the current directory.
434
fn open() -> Result<Repo> {
435
    Repo::open(&std::env::current_dir()?).context("opening repository")
436
}
437
438
/// Parse arguments and run; returns the process exit code.
439
pub fn run() -> Result<i32> {
440
    let cli = Cli::parse();
135212de Andy McCall ↶ 441
    ui::init(cli.plain);
fce44ae9 Andy McCall ↶ 442
    match cli.cmd {
443
        Cmd::Init { path } => workspace::init(path),
50a046e4 Andy McCall ↶ 444
        Cmd::Status { json } => workspace::status(json),
135212de Andy McCall ↶ 445
        Cmd::Prompt { format } => workspace::prompt(&format),
fce44ae9 Andy McCall ↶ 446
        Cmd::Commit { message, depends_on } => workspace::commit(&message, &depends_on),
94084087 Andy McCall ↶ 447
        Cmd::Log { limit, verbose } => workspace::log(limit, verbose),
fce44ae9 Andy McCall ↶ 448
        Cmd::Diff => workspace::diff(),
614541c3 Andy McCall ↶ 449
        Cmd::Describe { message, change } => workspace::describe(&message, change.as_deref()),
23b217d7 Andy McCall ↶ 450
        Cmd::Amend { message, into } => workspace::amend(message.as_deref(), into.as_deref()),
451
        Cmd::Split { change, paths } => workspace::split(&change, &paths),
614541c3 Andy McCall ↶ 452
        Cmd::Abandon { change } => workspace::abandon(&change),
fce44ae9 Andy McCall ↶ 453
        Cmd::Show { change } => workspace::show(&change),
454
        Cmd::Checkout { changes, discard } => workspace::checkout(&changes, discard),
455
        Cmd::Resolve => workspace::resolve(),
456
        Cmd::Undo => workspace::undo(),
ba160f60 Andy McCall ↶ 457
        Cmd::Gc { now } => workspace::gc(now),
252e8cb2 Andy McCall ↶ 458
        Cmd::Checkpoint => workspace::checkpoint(),
fce44ae9 Andy McCall ↶ 459
        Cmd::Op { cmd: OpCmd::Log } => workspace::op_log(),
2863456a Andy McCall ↶ 460
        Cmd::Clone { url, dest, max_blob, sparse, depth } => sync::clone(&url, dest, max_blob.as_deref(), &sparse, depth),
461
        Cmd::Deepen { depth, remote } => sync::deepen(&remote, depth),
7421309f Andy McCall ↶ 462
        Cmd::Sparse { paths } => workspace::sparse(&paths),
cdb5beba Andy McCall ↶ 463
        Cmd::Lock { path } => workspace::lock(&path),
464
        Cmd::Unlock { path } => workspace::unlock(&path),
4e88e774 Andy McCall ↶ 465
        Cmd::Workspace { cmd } => match cmd {
466
            WorkspaceCmd::Add { path, changes } => workspace::workspace_add(&path, &changes),
467
            WorkspaceCmd::List => workspace::workspace_list(),
468
            WorkspaceCmd::Forget { workspace } => workspace::workspace_forget(&workspace),
469
        },
7b9809e6 Andy McCall ↶ 470
        Cmd::Bookmark { cmd } => match cmd {
471
            BookmarkCmd::Create { name, changes } => workspace::bookmark_create(&name, &changes),
472
            BookmarkCmd::Set { name, changes } => workspace::bookmark_set(&name, &changes),
473
            BookmarkCmd::Delete { name } => workspace::bookmark_delete(&name),
474
            BookmarkCmd::List => workspace::bookmark_list(),
475
            BookmarkCmd::Switch { name, discard } => workspace::bookmark_switch(&name, discard),
476
        },
477
        Cmd::Merge { bookmark } => workspace::merge(&bookmark),
4dee277c Andy McCall ↶ 478
        Cmd::Submodule { cmd } => match cmd {
479
            SubmoduleCmd::Add { url, path, bookmark } => sync::submodule_add(&url, &path, bookmark.as_deref()),
480
            SubmoduleCmd::Update { init, paths } => sync::submodule_update(init, &paths),
481
            SubmoduleCmd::Status => sync::submodule_status(),
482
            SubmoduleCmd::Remove { path } => sync::submodule_remove(&path),
483
        },
132b884b Andy McCall ↶ 484
        Cmd::Tag { cmd } => match cmd {
485
            TagCmd::Create { name, changes, message } => workspace::tag_create(&name, &changes, &message),
486
            TagCmd::List => workspace::tag_list(),
487
        },
fce44ae9 Andy McCall ↶ 488
        Cmd::Remote { cmd } => match cmd {
489
            RemoteCmd::Add { name, url } => sync::remote_add(&name, &url),
490
            RemoteCmd::Remove { name } => sync::remote_remove(&name),
491
            RemoteCmd::List => sync::remote_list(),
492
        },
493
        Cmd::Fetch { remote } => sync::fetch(remote.as_deref()),
494
        Cmd::Pull { remote } => sync::pull(remote.as_deref()),
7b9809e6 Andy McCall ↶ 495
        Cmd::Push { remote, force, bookmark } => sync::push(remote.as_deref(), force, bookmark.as_deref()),
2b5ee371 Andy McCall ↶ 496
        Cmd::Serve { root, bind, tokens, tls_cert, tls_key } => sync::serve(root, &bind, tokens, tls_cert.zip(tls_key)),
fce44ae9 Andy McCall ↶ 497
        Cmd::Identity { cmd } => match cmd {
28fc23d7 Andy McCall ↶ 498
            IdentityCmd::Create { name, email, local, expires } => identity::create(name, email, local, expires),
fce44ae9 Andy McCall ↶ 499
            IdentityCmd::Show => identity::show(),
83e0677b Andy McCall ↶ 500
            IdentityCmd::Export { path } => identity::export(path),
28fc23d7 Andy McCall ↶ 501
            IdentityCmd::Rotate => identity::rotate(),
502
            IdentityCmd::Renew { expires } => identity::renew(expires),
503
            IdentityCmd::Revoke { key, reason } => identity::revoke(key.as_deref(), &reason),
504
            IdentityCmd::Delegate { cmd } => match cmd {
505
                DelegateCmd::Add { identity_file } => identity::delegate_add(&identity_file),
506
                DelegateCmd::Remove { key } => identity::delegate_remove(&key),
507
                DelegateCmd::List => identity::delegate_list(),
508
            },
83e0677b Andy McCall ↶ 509
        },
30ea9c1e Andy McCall ↶ 510
        Cmd::Git { cmd: GitCmd::Export { git_dir, branch } } => sync::git_export(&git_dir, branch.as_deref()),
511
        Cmd::Git { cmd: GitCmd::Fetch { url, branch } } => sync::git_fetch(&url, &branch),
512
        Cmd::Git { cmd: GitCmd::Push { url, branch } } => sync::git_push(&url, &branch),
c3069537 Andy McCall ↶ 513
        Cmd::Git { cmd: GitCmd::Import { git_dir, dest, branch } } => sync::git_import(&git_dir, &dest, &branch),
83e0677b Andy McCall ↶ 514
        Cmd::Policy { cmd } => match cmd {
515
            PolicyCmd::Init => identity::policy_init(),
516
            PolicyCmd::Show => identity::policy_show(),
28fc23d7 Andy McCall ↶ 517
            PolicyCmd::Grant { identity_file, pattern, out } => identity::policy_grant(&identity_file, pattern.as_deref(), out),
518
            PolicyCmd::Revoke { key, out } => identity::policy_revoke(&key, out),
519
            PolicyCmd::Root { cmd } => match cmd {
520
                RootCmd::Add { identity_file, out } => identity::policy_root_add(&identity_file, out),
521
                RootCmd::Remove { key, out } => identity::policy_root_remove(&key, out),
522
                RootCmd::Threshold { n, out } => identity::policy_threshold(n, out),
523
            },
524
            PolicyCmd::Sign { proposal, out } => identity::policy_sign(&proposal, out),
fce44ae9 Andy McCall ↶ 525
        },
526
        Cmd::Verify { change } => identity::verify(change.as_deref()),
527
        Cmd::Config { key, value, user } => identity::config(&key, &value, user),
528
    }
529
}