OXYGENHUB
# 728da0f92161 oxygen / src / cli / mod.rs
21.7 KiB · Rust · last changed Squash and rebase (spec 0.8 section 8.5): squash folds a change into a dependency with dependents following its atoms; rebase re-derives a change against another base with content-matched correspondence; correspondences keyed by node; spec 0.8a and docs 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 {
694805cc Andy McCall ↶ 59
        /// Placeholders: %b bookmark, %c change id, %h heads, %d dirty (*), %x conflict (!), %A/%D ahead/behind (+n/-n), %a/%B raw counts, %s state glyph, %U/%O ahead/behind arrows, %i icon
135212de Andy McCall ↶ 60
        #[arg(long, default_value = oxy_core::repo::DEFAULT_PROMPT_FORMAT)]
61
        format: String,
694805cc Andy McCall ↶ 62
        /// Glyph format (%i %b %c %s%U%O) instead of the plain default
63
        #[arg(long)]
64
        glyphs: bool,
65
        /// Colour by state (cyan clean, yellow dirty, red conflicted): raw escapes, or guarded for a bash or zsh PS1
66
        #[arg(long, value_parser = ["raw", "bash", "zsh"])]
67
        color: Option<String>,
135212de Andy McCall ↶ 68
    },
fce44ae9 Andy McCall ↶ 69
    /// Record the working copy as a change on the current bookmark
70
    Commit {
71
        #[arg(short, long)]
72
        message: String,
73
        /// Extra explicit dependencies (ChangeId prefix, #hash prefix)
74
        #[arg(long = "depends-on")]
75
        depends_on: Vec<String>,
957fee30 Andy McCall ↶ 76
        /// Commit only these paths; the rest stays as uncommitted work
77
        paths: Vec<String>,
78
        /// Choose hunks one by one
79
        #[arg(short, long)]
80
        interactive: bool,
fce44ae9 Andy McCall ↶ 81
    },
728da0f9 Andy McCall ↶ 82
    /// Fold CHANGE into one of its dependencies (keeps that change's id)
83
    Squash {
84
        change: String,
85
        #[arg(long)]
86
        into: String,
87
    },
88
    /// Re-express CHANGE against another base state
89
    Rebase {
90
        change: String,
91
        /// The changes, bookmark or tag whose state becomes the new base
92
        #[arg(long, required = true, num_args = 1..)]
93
        onto: Vec<String>,
94
    },
957fee30 Andy McCall ↶ 95
    /// Show which change introduced each line of a file
96
    Blame {
97
        path: String,
98
        #[arg(long)]
99
        json: bool,
100
    },
101
    /// Find the change that introduced a regression
102
    Bisect {
103
        #[command(subcommand)]
104
        cmd: BisectCmd,
105
    },
fce44ae9 Andy McCall ↶ 106
    /// List changes reachable from the bookmark and the working copy
94084087 Andy McCall ↶ 107
    Log {
108
        /// Show at most this many changes
109
        #[arg(short = 'n', long)]
110
        limit: Option<usize>,
111
        /// Show full messages, dependencies, and signatures
112
        #[arg(short, long)]
113
        verbose: bool,
114
    },
fce44ae9 Andy McCall ↶ 115
    /// Show what the working copy changes against its base
116
    Diff,
614541c3 Andy McCall ↶ 117
    /// Change the message of a head (default: the head the working copy sits on)
118
    Describe {
119
        #[arg(short, long)]
120
        message: String,
121
        change: Option<String>,
122
    },
23b217d7 Andy McCall ↶ 123
    /// Fold the working copy into the head it sits on, or into CHANGE with --into
614541c3 Andy McCall ↶ 124
    Amend {
125
        #[arg(short, long)]
126
        message: Option<String>,
23b217d7 Andy McCall ↶ 127
        /// Amend an older change; the edits must touch only lines it already knows
128
        #[arg(long)]
129
        into: Option<String>,
614541c3 Andy McCall ↶ 130
    },
23b217d7 Andy McCall ↶ 131
    /// Split a head: the named paths become a separate first change
132
    Split {
133
        change: String,
134
        paths: Vec<String>,
135
    },
614541c3 Andy McCall ↶ 136
    /// Drop a head from the bookmark; its content stays on disk as uncommitted work
137
    Abandon { change: String },
fce44ae9 Andy McCall ↶ 138
    /// Show one change (ChangeId prefix, #hash prefix, or @)
139
    Show { change: String },
140
    /// Move the working copy to the state formed by CHANGE... (default: the bookmark heads)
141
    Checkout {
142
        changes: Vec<String>,
143
        /// Throw away uncommitted working-copy changes
144
        #[arg(long)]
145
        discard: bool,
146
    },
147
    /// Report which conflicts of the working copy's base are resolved by the current files
148
    Resolve,
149
    /// Undo the last operation
150
    Undo,
252e8cb2 Andy McCall ↶ 151
    /// Write a checkpoint of the current bookmark's state now
152
    Checkpoint,
ba160f60 Andy McCall ↶ 153
    /// Pack loose objects and prune unreachable ones older than the grace period
154
    Gc {
155
        /// Ignore the grace period (core.gc-grace) and prune immediately
156
        #[arg(long)]
157
        now: bool,
158
    },
fce44ae9 Andy McCall ↶ 159
    /// List operations
160
    #[command(name = "op")]
161
    Op {
162
        #[command(subcommand)]
163
        cmd: OpCmd,
164
    },
cdb5beba Andy McCall ↶ 165
    /// Take the advisory local lock on a lockable file, making it writable
166
    Lock { path: String },
167
    /// Release the local lock and make the file read-only again
168
    Unlock { path: String },
4e88e774 Andy McCall ↶ 169
    /// Workspaces: extra checkout directories sharing this repository
170
    Workspace {
171
        #[command(subcommand)]
172
        cmd: WorkspaceCmd,
173
    },
7b9809e6 Andy McCall ↶ 174
    /// Bookmarks: named states; each workspace works on one
175
    Bookmark {
176
        #[command(subcommand)]
177
        cmd: BookmarkCmd,
178
    },
179
    /// Union another bookmark's heads into the current one (no merge change)
180
    Merge { bookmark: String },
4dee277c Andy McCall ↶ 181
    /// Subrepositories: nested repositories pinned to a state
182
    Submodule {
183
        #[command(subcommand)]
184
        cmd: SubmoduleCmd,
185
    },
132b884b Andy McCall ↶ 186
    /// Tags: immutable named states
187
    Tag {
188
        #[command(subcommand)]
189
        cmd: TagCmd,
190
    },
fce44ae9 Andy McCall ↶ 191
    /// Manage remotes
192
    Remote {
193
        #[command(subcommand)]
194
        cmd: RemoteCmd,
195
    },
196
    /// Fetch every bookmark of a remote into its tracking references
197
    Fetch { remote: Option<String> },
198
    /// Fetch, then union the remote bookmark into the local one (no merge change)
199
    Pull { remote: Option<String> },
200
    /// Push the current bookmark to a remote
201
    Push {
202
        remote: Option<String>,
203
        /// Allow dropping heads the remote has
204
        #[arg(long)]
205
        force: bool,
7b9809e6 Andy McCall ↶ 206
        /// Push this bookmark instead of the workspace's current one
207
        #[arg(short = 'b', long)]
208
        bookmark: Option<String>,
fce44ae9 Andy McCall ↶ 209
    },
2b5ee371 Andy McCall ↶ 210
    /// Serve repositories under ROOT over HTTPS (with --tls-cert/--tls-key) or plain HTTP
fce44ae9 Andy McCall ↶ 211
    Serve {
212
        /// Directory with one repository per subdirectory
213
        #[arg(long, default_value = ".")]
214
        root: PathBuf,
215
        #[arg(long, default_value = "127.0.0.1:8890")]
216
        bind: String,
217
        /// Bearer token allowed to push (repeatable); none means open
218
        #[arg(long = "token")]
219
        tokens: Vec<String>,
2b5ee371 Andy McCall ↶ 220
        /// PEM certificate chain; enables HTTPS together with --tls-key
221
        #[arg(long, requires = "tls_key")]
222
        tls_cert: Option<PathBuf>,
223
        /// PEM private key
224
        #[arg(long, requires = "tls_cert")]
225
        tls_key: Option<PathBuf>,
fce44ae9 Andy McCall ↶ 226
    },
227
    /// Manage the signing identity
228
    Identity {
229
        #[command(subcommand)]
230
        cmd: IdentityCmd,
231
    },
232
    /// Verify the signatures on a change (default: every change in the log)
233
    Verify { change: Option<String> },
79ab8528 Andy McCall ↶ 234
    /// Git interoperability
235
    Git {
236
        #[command(subcommand)]
237
        cmd: GitCmd,
238
    },
83e0677b Andy McCall ↶ 239
    /// Manage the trust policy (who may update which bookmarks)
240
    Policy {
241
        #[command(subcommand)]
242
        cmd: PolicyCmd,
243
    },
7421309f Andy McCall ↶ 244
    /// 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 ↶ 245
    Config {
246
        key: String,
247
        value: String,
248
        /// Write to the user file (~/.config/oxy/config.toml) instead of this repository
249
        #[arg(long)]
250
        user: bool,
251
    },
252
}
253
254
#[derive(Subcommand)]
255
enum OpCmd {
256
    Log,
257
}
258
259
#[derive(Subcommand)]
4e88e774 Andy McCall ↶ 260
enum WorkspaceCmd {
261
    /// Create a workspace at PATH checked out on CHANGE... (default: the bookmark heads)
262
    Add { path: PathBuf, changes: Vec<String> },
263
    List,
264
    /// Stop tracking a workspace (by id prefix or path); its files stay
265
    Forget { workspace: String },
266
}
267
268
#[derive(Subcommand)]
957fee30 Andy McCall ↶ 269
enum BisectCmd {
270
    /// Begin; remembers where the working copy was
271
    Start,
272
    /// Mark CHANGE... (or the state under test) as good
273
    Good { changes: Vec<String> },
274
    /// Mark CHANGE... (or the state under test) as bad
275
    Bad { changes: Vec<String> },
276
    /// The state under test cannot be tested; try another
277
    Skip,
278
    /// Run a command for each step: exit 0 good, 125 skip, else bad
279
    Run {
280
        #[arg(required = true, trailing_var_arg = true)]
281
        command: Vec<String>,
282
    },
283
    /// Where the search stands
284
    Status,
285
    /// Restore the working copy and forget the search
286
    Reset,
287
}
288
289
#[derive(Subcommand)]
7b9809e6 Andy McCall ↶ 290
enum BookmarkCmd {
291
    /// Create a bookmark at CHANGE... (default: the working copy's base)
292
    Create { name: String, changes: Vec<String> },
293
    /// Point an existing bookmark at CHANGE...
294
    Set { name: String, changes: Vec<String> },
295
    /// Remove a bookmark; its changes stay
296
    Delete { name: String },
297
    /// List bookmarks with their heads and difference from origin
298
    List,
299
    /// Make NAME this workspace's bookmark and check it out
300
    Switch {
301
        name: String,
302
        /// Throw away uncommitted working-copy changes
303
        #[arg(long)]
304
        discard: bool,
305
    },
306
}
307
308
#[derive(Subcommand)]
4dee277c Andy McCall ↶ 309
enum SubmoduleCmd {
310
    /// Clone URL (Oxygen or Git) into PATH and register it in .oxymodules
311
    Add {
312
        url: String,
313
        path: String,
314
        /// Bookmark (Oxygen) or branch (Git) to check out and track
315
        #[arg(long)]
316
        bookmark: Option<String>,
317
    },
318
    /// Bring nested repositories to their pins
319
    Update {
320
        /// Clone nested repositories that are absent
321
        #[arg(long)]
322
        init: bool,
323
        paths: Vec<String>,
324
    },
325
    /// List subrepos with their pins and on-disk state
326
    Status,
327
    /// Unregister PATH; the directory stays, the node goes with the next commit
328
    Remove { path: String },
329
}
330
331
#[derive(Subcommand)]
132b884b Andy McCall ↶ 332
enum TagCmd {
333
    /// Tag the state of CHANGE... (default: the bookmark heads)
334
    Create {
335
        name: String,
336
        changes: Vec<String>,
337
        #[arg(short, long, default_value = "")]
338
        message: String,
339
    },
340
    List,
341
}
342
343
#[derive(Subcommand)]
fce44ae9 Andy McCall ↶ 344
enum RemoteCmd {
345
    Add { name: String, url: String },
346
    Remove { name: String },
347
    List,
348
}
349
350
#[derive(Subcommand)]
351
enum IdentityCmd {
352
    /// Generate a composite Ed25519 + ML-DSA-65 key pair and make it your author identity
353
    Create {
354
        #[arg(long)]
355
        name: Option<String>,
356
        #[arg(long)]
357
        email: Option<String>,
358
        /// Record the author in this repository only, not in the user config
359
        #[arg(long)]
360
        local: bool,
28fc23d7 Andy McCall ↶ 361
        /// Expire the identity this many days from now
362
        #[arg(long)]
363
        expires: Option<u64>,
fce44ae9 Andy McCall ↶ 364
    },
365
    /// Show the configured identity
366
    Show,
83e0677b Andy McCall ↶ 367
    /// Write your identity object to a file, to hand to a policy holder
368
    Export { path: Option<PathBuf> },
28fc23d7 Andy McCall ↶ 369
    /// Move to a fresh key pair; the policy follows if it names your key
370
    Rotate,
371
    /// Re-issue your identity with a new expiry (in days from now; 0 = never)
372
    Renew {
373
        #[arg(long)]
374
        expires: u64,
375
    },
376
    /// Revoke a key: yours by default, or any key if you hold a policy root
377
    Revoke {
378
        /// KeyId prefix (default: your own key)
379
        key: Option<String>,
380
        #[arg(long, default_value = "")]
381
        reason: String,
382
    },
383
    /// Keys allowed to sign or seal on your behalf
384
    Delegate {
385
        #[command(subcommand)]
386
        cmd: DelegateCmd,
387
    },
388
}
389
390
#[derive(Subcommand)]
391
enum DelegateCmd {
392
    /// Add a delegate from an exported identity file
393
    Add { identity_file: PathBuf },
394
    /// Remove a delegate by KeyId prefix
395
    Remove { key: String },
396
    /// List delegates
397
    List,
fce44ae9 Andy McCall ↶ 398
}
399
83e0677b Andy McCall ↶ 400
#[derive(Subcommand)]
79ab8528 Andy McCall ↶ 401
enum GitCmd {
402
    /// Export the current bookmark to a Git repository (created bare if absent)
30ea9c1e Andy McCall ↶ 403
    Export {
404
        git_dir: PathBuf,
405
        /// Git branch to write (default: the bookmark name)
406
        #[arg(long)]
407
        branch: Option<String>,
408
    },
409
    /// Fetch a branch from a Git remote (path or URL) into the current bookmark
410
    Fetch {
411
        url: String,
412
        #[arg(long, default_value = "main")]
413
        branch: String,
414
    },
415
    /// Push the current bookmark to a branch of a Git remote (fetch first)
416
    Push {
417
        url: String,
418
        #[arg(long, default_value = "main")]
419
        branch: String,
420
    },
c3069537 Andy McCall ↶ 421
    /// Import a Git branch into a new Oxygen repository at DEST
422
    Import {
423
        git_dir: PathBuf,
424
        dest: PathBuf,
425
        #[arg(long, default_value = "main")]
426
        branch: String,
427
    },
79ab8528 Andy McCall ↶ 428
}
429
430
#[derive(Subcommand)]
83e0677b Andy McCall ↶ 431
enum PolicyCmd {
432
    /// Create the initial policy with your identity as the only root
433
    Init,
434
    /// Show the current policy
435
    Show,
436
    /// Allow an identity (from `oxy identity export`) to update bookmarks
437
    Grant {
438
        identity_file: PathBuf,
439
        /// Reference pattern the grant applies to (default: every bookmark)
440
        #[arg(long)]
441
        pattern: Option<String>,
28fc23d7 Andy McCall ↶ 442
        /// Where to write the proposal when more root signatures are needed
443
        #[arg(long)]
444
        out: Option<PathBuf>,
83e0677b Andy McCall ↶ 445
    },
446
    /// Revoke a key (KeyId prefix of an identity in the policy)
28fc23d7 Andy McCall ↶ 447
    Revoke {
448
        key: String,
449
        /// Where to write the proposal when more root signatures are needed
450
        #[arg(long)]
451
        out: Option<PathBuf>,
452
    },
453
    /// Manage root keys and the signature threshold for policy updates
454
    Root {
455
        #[command(subcommand)]
456
        cmd: RootCmd,
457
    },
458
    /// Add your root signature to a proposal file; applies it once the threshold is met
459
    Sign {
460
        proposal: PathBuf,
461
        /// Where to write the proposal if it still needs signatures (default: in place)
462
        #[arg(long)]
463
        out: Option<PathBuf>,
464
    },
465
}
466
467
#[derive(Subcommand)]
468
enum RootCmd {
469
    /// Make an exported identity a root key
470
    Add {
471
        identity_file: PathBuf,
472
        #[arg(long)]
473
        out: Option<PathBuf>,
474
    },
475
    /// Remove a root key by KeyId prefix
476
    Remove {
477
        key: String,
478
        #[arg(long)]
479
        out: Option<PathBuf>,
480
    },
481
    /// Require this many root signatures on policy updates
482
    Threshold {
483
        n: u32,
484
        #[arg(long)]
485
        out: Option<PathBuf>,
486
    },
83e0677b Andy McCall ↶ 487
}
488
fce44ae9 Andy McCall ↶ 489
/// Open the repository containing the current directory.
490
fn open() -> Result<Repo> {
491
    Repo::open(&std::env::current_dir()?).context("opening repository")
492
}
493
494
/// Parse arguments and run; returns the process exit code.
495
pub fn run() -> Result<i32> {
496
    let cli = Cli::parse();
135212de Andy McCall ↶ 497
    ui::init(cli.plain);
fce44ae9 Andy McCall ↶ 498
    match cli.cmd {
499
        Cmd::Init { path } => workspace::init(path),
50a046e4 Andy McCall ↶ 500
        Cmd::Status { json } => workspace::status(json),
694805cc Andy McCall ↶ 501
        Cmd::Prompt { format, glyphs, color } => workspace::prompt(&format, glyphs, color.as_deref()),
957fee30 Andy McCall ↶ 502
        Cmd::Commit { message, depends_on, paths, interactive } => workspace::commit(&message, &depends_on, &paths, interactive),
728da0f9 Andy McCall ↶ 503
        Cmd::Squash { change, into } => workspace::squash(&change, &into),
504
        Cmd::Rebase { change, onto } => workspace::rebase(&change, &onto),
957fee30 Andy McCall ↶ 505
        Cmd::Blame { path, json } => workspace::blame(&path, json),
506
        Cmd::Bisect { cmd } => match cmd {
507
            BisectCmd::Start => workspace::bisect_start(),
508
            BisectCmd::Good { changes } => workspace::bisect_mark(Some(true), &changes),
509
            BisectCmd::Bad { changes } => workspace::bisect_mark(Some(false), &changes),
510
            BisectCmd::Skip => workspace::bisect_mark(None, &[]),
511
            BisectCmd::Run { command } => workspace::bisect_run(&command),
512
            BisectCmd::Status => workspace::bisect_status(),
513
            BisectCmd::Reset => workspace::bisect_reset(),
514
        },
94084087 Andy McCall ↶ 515
        Cmd::Log { limit, verbose } => workspace::log(limit, verbose),
fce44ae9 Andy McCall ↶ 516
        Cmd::Diff => workspace::diff(),
614541c3 Andy McCall ↶ 517
        Cmd::Describe { message, change } => workspace::describe(&message, change.as_deref()),
23b217d7 Andy McCall ↶ 518
        Cmd::Amend { message, into } => workspace::amend(message.as_deref(), into.as_deref()),
519
        Cmd::Split { change, paths } => workspace::split(&change, &paths),
614541c3 Andy McCall ↶ 520
        Cmd::Abandon { change } => workspace::abandon(&change),
fce44ae9 Andy McCall ↶ 521
        Cmd::Show { change } => workspace::show(&change),
522
        Cmd::Checkout { changes, discard } => workspace::checkout(&changes, discard),
523
        Cmd::Resolve => workspace::resolve(),
524
        Cmd::Undo => workspace::undo(),
ba160f60 Andy McCall ↶ 525
        Cmd::Gc { now } => workspace::gc(now),
252e8cb2 Andy McCall ↶ 526
        Cmd::Checkpoint => workspace::checkpoint(),
fce44ae9 Andy McCall ↶ 527
        Cmd::Op { cmd: OpCmd::Log } => workspace::op_log(),
2863456a Andy McCall ↶ 528
        Cmd::Clone { url, dest, max_blob, sparse, depth } => sync::clone(&url, dest, max_blob.as_deref(), &sparse, depth),
529
        Cmd::Deepen { depth, remote } => sync::deepen(&remote, depth),
7421309f Andy McCall ↶ 530
        Cmd::Sparse { paths } => workspace::sparse(&paths),
cdb5beba Andy McCall ↶ 531
        Cmd::Lock { path } => workspace::lock(&path),
532
        Cmd::Unlock { path } => workspace::unlock(&path),
4e88e774 Andy McCall ↶ 533
        Cmd::Workspace { cmd } => match cmd {
534
            WorkspaceCmd::Add { path, changes } => workspace::workspace_add(&path, &changes),
535
            WorkspaceCmd::List => workspace::workspace_list(),
536
            WorkspaceCmd::Forget { workspace } => workspace::workspace_forget(&workspace),
537
        },
7b9809e6 Andy McCall ↶ 538
        Cmd::Bookmark { cmd } => match cmd {
539
            BookmarkCmd::Create { name, changes } => workspace::bookmark_create(&name, &changes),
540
            BookmarkCmd::Set { name, changes } => workspace::bookmark_set(&name, &changes),
541
            BookmarkCmd::Delete { name } => workspace::bookmark_delete(&name),
542
            BookmarkCmd::List => workspace::bookmark_list(),
543
            BookmarkCmd::Switch { name, discard } => workspace::bookmark_switch(&name, discard),
544
        },
545
        Cmd::Merge { bookmark } => workspace::merge(&bookmark),
4dee277c Andy McCall ↶ 546
        Cmd::Submodule { cmd } => match cmd {
547
            SubmoduleCmd::Add { url, path, bookmark } => sync::submodule_add(&url, &path, bookmark.as_deref()),
548
            SubmoduleCmd::Update { init, paths } => sync::submodule_update(init, &paths),
549
            SubmoduleCmd::Status => sync::submodule_status(),
550
            SubmoduleCmd::Remove { path } => sync::submodule_remove(&path),
551
        },
132b884b Andy McCall ↶ 552
        Cmd::Tag { cmd } => match cmd {
553
            TagCmd::Create { name, changes, message } => workspace::tag_create(&name, &changes, &message),
554
            TagCmd::List => workspace::tag_list(),
555
        },
fce44ae9 Andy McCall ↶ 556
        Cmd::Remote { cmd } => match cmd {
557
            RemoteCmd::Add { name, url } => sync::remote_add(&name, &url),
558
            RemoteCmd::Remove { name } => sync::remote_remove(&name),
559
            RemoteCmd::List => sync::remote_list(),
560
        },
561
        Cmd::Fetch { remote } => sync::fetch(remote.as_deref()),
562
        Cmd::Pull { remote } => sync::pull(remote.as_deref()),
7b9809e6 Andy McCall ↶ 563
        Cmd::Push { remote, force, bookmark } => sync::push(remote.as_deref(), force, bookmark.as_deref()),
2b5ee371 Andy McCall ↶ 564
        Cmd::Serve { root, bind, tokens, tls_cert, tls_key } => sync::serve(root, &bind, tokens, tls_cert.zip(tls_key)),
fce44ae9 Andy McCall ↶ 565
        Cmd::Identity { cmd } => match cmd {
28fc23d7 Andy McCall ↶ 566
            IdentityCmd::Create { name, email, local, expires } => identity::create(name, email, local, expires),
fce44ae9 Andy McCall ↶ 567
            IdentityCmd::Show => identity::show(),
83e0677b Andy McCall ↶ 568
            IdentityCmd::Export { path } => identity::export(path),
28fc23d7 Andy McCall ↶ 569
            IdentityCmd::Rotate => identity::rotate(),
570
            IdentityCmd::Renew { expires } => identity::renew(expires),
571
            IdentityCmd::Revoke { key, reason } => identity::revoke(key.as_deref(), &reason),
572
            IdentityCmd::Delegate { cmd } => match cmd {
573
                DelegateCmd::Add { identity_file } => identity::delegate_add(&identity_file),
574
                DelegateCmd::Remove { key } => identity::delegate_remove(&key),
575
                DelegateCmd::List => identity::delegate_list(),
576
            },
83e0677b Andy McCall ↶ 577
        },
30ea9c1e Andy McCall ↶ 578
        Cmd::Git { cmd: GitCmd::Export { git_dir, branch } } => sync::git_export(&git_dir, branch.as_deref()),
579
        Cmd::Git { cmd: GitCmd::Fetch { url, branch } } => sync::git_fetch(&url, &branch),
580
        Cmd::Git { cmd: GitCmd::Push { url, branch } } => sync::git_push(&url, &branch),
c3069537 Andy McCall ↶ 581
        Cmd::Git { cmd: GitCmd::Import { git_dir, dest, branch } } => sync::git_import(&git_dir, &dest, &branch),
83e0677b Andy McCall ↶ 582
        Cmd::Policy { cmd } => match cmd {
583
            PolicyCmd::Init => identity::policy_init(),
584
            PolicyCmd::Show => identity::policy_show(),
28fc23d7 Andy McCall ↶ 585
            PolicyCmd::Grant { identity_file, pattern, out } => identity::policy_grant(&identity_file, pattern.as_deref(), out),
586
            PolicyCmd::Revoke { key, out } => identity::policy_revoke(&key, out),
587
            PolicyCmd::Root { cmd } => match cmd {
588
                RootCmd::Add { identity_file, out } => identity::policy_root_add(&identity_file, out),
589
                RootCmd::Remove { key, out } => identity::policy_root_remove(&key, out),
590
                RootCmd::Threshold { n, out } => identity::policy_threshold(n, out),
591
            },
592
            PolicyCmd::Sign { proposal, out } => identity::policy_sign(&proposal, out),
fce44ae9 Andy McCall ↶ 593
        },
594
        Cmd::Verify { change } => identity::verify(change.as_deref()),
595
        Cmd::Config { key, value, user } => identity::config(&key, &value, user),
596
    }
597
}