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