OXYGENHUB
# 59171dcff221 oxygen / src / cli / mod.rs
9.2 KiB · Rust · last changed Pack merging above 16 packs; modern CLI presentation (colour, spinners, --plain, NO_COLOR); oxy prompt with manifest-based dirty check and docs/prompt.md 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
29
    Clone { url: String, dest: Option<PathBuf> },
30
    /// Show the working copy: changed paths and conflicts
31
    Status,
135212de Andy McCall ↶ 32
    /// One-line summary for shell prompts; fast and records nothing
33
    Prompt {
34
        /// Placeholders: %b bookmark, %c change id, %h heads, %d dirty (*), %x conflict (!), %A/%D ahead/behind (+n/-n), %a/%B raw counts
35
        #[arg(long, default_value = oxy_core::repo::DEFAULT_PROMPT_FORMAT)]
36
        format: String,
37
    },
fce44ae9 Andy McCall ↶ 38
    /// Record the working copy as a change on the current bookmark
39
    Commit {
40
        #[arg(short, long)]
41
        message: String,
42
        /// Extra explicit dependencies (ChangeId prefix, #hash prefix)
43
        #[arg(long = "depends-on")]
44
        depends_on: Vec<String>,
45
    },
46
    /// List changes reachable from the bookmark and the working copy
47
    Log,
48
    /// Show what the working copy changes against its base
49
    Diff,
614541c3 Andy McCall ↶ 50
    /// Change the message of a head (default: the head the working copy sits on)
51
    Describe {
52
        #[arg(short, long)]
53
        message: String,
54
        change: Option<String>,
55
    },
56
    /// Fold the working copy into the head it sits on
57
    Amend {
58
        #[arg(short, long)]
59
        message: Option<String>,
60
    },
61
    /// Drop a head from the bookmark; its content stays on disk as uncommitted work
62
    Abandon { change: String },
fce44ae9 Andy McCall ↶ 63
    /// Show one change (ChangeId prefix, #hash prefix, or @)
64
    Show { change: String },
65
    /// Move the working copy to the state formed by CHANGE... (default: the bookmark heads)
66
    Checkout {
67
        changes: Vec<String>,
68
        /// Throw away uncommitted working-copy changes
69
        #[arg(long)]
70
        discard: bool,
71
    },
72
    /// Report which conflicts of the working copy's base are resolved by the current files
73
    Resolve,
74
    /// Undo the last operation
75
    Undo,
252e8cb2 Andy McCall ↶ 76
    /// Write a checkpoint of the current bookmark's state now
77
    Checkpoint,
ba160f60 Andy McCall ↶ 78
    /// Pack loose objects and prune unreachable ones older than the grace period
79
    Gc {
80
        /// Ignore the grace period (core.gc-grace) and prune immediately
81
        #[arg(long)]
82
        now: bool,
83
    },
fce44ae9 Andy McCall ↶ 84
    /// List operations
85
    #[command(name = "op")]
86
    Op {
87
        #[command(subcommand)]
88
        cmd: OpCmd,
89
    },
90
    /// Manage remotes
91
    Remote {
92
        #[command(subcommand)]
93
        cmd: RemoteCmd,
94
    },
95
    /// Fetch every bookmark of a remote into its tracking references
96
    Fetch { remote: Option<String> },
97
    /// Fetch, then union the remote bookmark into the local one (no merge change)
98
    Pull { remote: Option<String> },
99
    /// Push the current bookmark to a remote
100
    Push {
101
        remote: Option<String>,
102
        /// Allow dropping heads the remote has
103
        #[arg(long)]
104
        force: bool,
105
    },
2b5ee371 Andy McCall ↶ 106
    /// Serve repositories under ROOT over HTTPS (with --tls-cert/--tls-key) or plain HTTP
fce44ae9 Andy McCall ↶ 107
    Serve {
108
        /// Directory with one repository per subdirectory
109
        #[arg(long, default_value = ".")]
110
        root: PathBuf,
111
        #[arg(long, default_value = "127.0.0.1:8890")]
112
        bind: String,
113
        /// Bearer token allowed to push (repeatable); none means open
114
        #[arg(long = "token")]
115
        tokens: Vec<String>,
2b5ee371 Andy McCall ↶ 116
        /// PEM certificate chain; enables HTTPS together with --tls-key
117
        #[arg(long, requires = "tls_key")]
118
        tls_cert: Option<PathBuf>,
119
        /// PEM private key
120
        #[arg(long, requires = "tls_cert")]
121
        tls_key: Option<PathBuf>,
fce44ae9 Andy McCall ↶ 122
    },
123
    /// Manage the signing identity
124
    Identity {
125
        #[command(subcommand)]
126
        cmd: IdentityCmd,
127
    },
128
    /// Verify the signatures on a change (default: every change in the log)
129
    Verify { change: Option<String> },
79ab8528 Andy McCall ↶ 130
    /// Git interoperability
131
    Git {
132
        #[command(subcommand)]
133
        cmd: GitCmd,
134
    },
83e0677b Andy McCall ↶ 135
    /// Manage the trust policy (who may update which bookmarks)
136
    Policy {
137
        #[command(subcommand)]
138
        cmd: PolicyCmd,
139
    },
252e8cb2 Andy McCall ↶ 140
    /// Set a configuration key (author.*, core.sign, core.op-retention, core.gc-grace, core.checkpoint-interval, workspace.bookmark, remote.<name>.url|token|ca)
fce44ae9 Andy McCall ↶ 141
    Config {
142
        key: String,
143
        value: String,
144
        /// Write to the user file (~/.config/oxy/config.toml) instead of this repository
145
        #[arg(long)]
146
        user: bool,
147
    },
148
}
149
150
#[derive(Subcommand)]
151
enum OpCmd {
152
    Log,
153
}
154
155
#[derive(Subcommand)]
156
enum RemoteCmd {
157
    Add { name: String, url: String },
158
    Remove { name: String },
159
    List,
160
}
161
162
#[derive(Subcommand)]
163
enum IdentityCmd {
164
    /// Generate a composite Ed25519 + ML-DSA-65 key pair and make it your author identity
165
    Create {
166
        #[arg(long)]
167
        name: Option<String>,
168
        #[arg(long)]
169
        email: Option<String>,
170
        /// Record the author in this repository only, not in the user config
171
        #[arg(long)]
172
        local: bool,
173
    },
174
    /// Show the configured identity
175
    Show,
83e0677b Andy McCall ↶ 176
    /// Write your identity object to a file, to hand to a policy holder
177
    Export { path: Option<PathBuf> },
fce44ae9 Andy McCall ↶ 178
}
179
83e0677b Andy McCall ↶ 180
#[derive(Subcommand)]
79ab8528 Andy McCall ↶ 181
enum GitCmd {
182
    /// Export the current bookmark to a Git repository (created bare if absent)
183
    Export { git_dir: PathBuf },
c3069537 Andy McCall ↶ 184
    /// Import a Git branch into a new Oxygen repository at DEST
185
    Import {
186
        git_dir: PathBuf,
187
        dest: PathBuf,
188
        #[arg(long, default_value = "main")]
189
        branch: String,
190
    },
79ab8528 Andy McCall ↶ 191
}
192
193
#[derive(Subcommand)]
83e0677b Andy McCall ↶ 194
enum PolicyCmd {
195
    /// Create the initial policy with your identity as the only root
196
    Init,
197
    /// Show the current policy
198
    Show,
199
    /// Allow an identity (from `oxy identity export`) to update bookmarks
200
    Grant {
201
        identity_file: PathBuf,
202
        /// Reference pattern the grant applies to (default: every bookmark)
203
        #[arg(long)]
204
        pattern: Option<String>,
205
    },
206
    /// Revoke a key (KeyId prefix of an identity in the policy)
207
    Revoke { key: String },
208
}
209
fce44ae9 Andy McCall ↶ 210
/// Open the repository containing the current directory.
211
fn open() -> Result<Repo> {
212
    Repo::open(&std::env::current_dir()?).context("opening repository")
213
}
214
215
/// Parse arguments and run; returns the process exit code.
216
pub fn run() -> Result<i32> {
217
    let cli = Cli::parse();
135212de Andy McCall ↶ 218
    ui::init(cli.plain);
fce44ae9 Andy McCall ↶ 219
    match cli.cmd {
220
        Cmd::Init { path } => workspace::init(path),
221
        Cmd::Status => workspace::status(),
135212de Andy McCall ↶ 222
        Cmd::Prompt { format } => workspace::prompt(&format),
fce44ae9 Andy McCall ↶ 223
        Cmd::Commit { message, depends_on } => workspace::commit(&message, &depends_on),
224
        Cmd::Log => workspace::log(),
225
        Cmd::Diff => workspace::diff(),
614541c3 Andy McCall ↶ 226
        Cmd::Describe { message, change } => workspace::describe(&message, change.as_deref()),
227
        Cmd::Amend { message } => workspace::amend(message.as_deref()),
228
        Cmd::Abandon { change } => workspace::abandon(&change),
fce44ae9 Andy McCall ↶ 229
        Cmd::Show { change } => workspace::show(&change),
230
        Cmd::Checkout { changes, discard } => workspace::checkout(&changes, discard),
231
        Cmd::Resolve => workspace::resolve(),
232
        Cmd::Undo => workspace::undo(),
ba160f60 Andy McCall ↶ 233
        Cmd::Gc { now } => workspace::gc(now),
252e8cb2 Andy McCall ↶ 234
        Cmd::Checkpoint => workspace::checkpoint(),
fce44ae9 Andy McCall ↶ 235
        Cmd::Op { cmd: OpCmd::Log } => workspace::op_log(),
236
        Cmd::Clone { url, dest } => sync::clone(&url, dest),
237
        Cmd::Remote { cmd } => match cmd {
238
            RemoteCmd::Add { name, url } => sync::remote_add(&name, &url),
239
            RemoteCmd::Remove { name } => sync::remote_remove(&name),
240
            RemoteCmd::List => sync::remote_list(),
241
        },
242
        Cmd::Fetch { remote } => sync::fetch(remote.as_deref()),
243
        Cmd::Pull { remote } => sync::pull(remote.as_deref()),
244
        Cmd::Push { remote, force } => sync::push(remote.as_deref(), force),
2b5ee371 Andy McCall ↶ 245
        Cmd::Serve { root, bind, tokens, tls_cert, tls_key } => sync::serve(root, &bind, tokens, tls_cert.zip(tls_key)),
fce44ae9 Andy McCall ↶ 246
        Cmd::Identity { cmd } => match cmd {
247
            IdentityCmd::Create { name, email, local } => identity::create(name, email, local),
248
            IdentityCmd::Show => identity::show(),
83e0677b Andy McCall ↶ 249
            IdentityCmd::Export { path } => identity::export(path),
250
        },
79ab8528 Andy McCall ↶ 251
        Cmd::Git { cmd: GitCmd::Export { git_dir } } => sync::git_export(&git_dir),
c3069537 Andy McCall ↶ 252
        Cmd::Git { cmd: GitCmd::Import { git_dir, dest, branch } } => sync::git_import(&git_dir, &dest, &branch),
83e0677b Andy McCall ↶ 253
        Cmd::Policy { cmd } => match cmd {
254
            PolicyCmd::Init => identity::policy_init(),
255
            PolicyCmd::Show => identity::policy_show(),
256
            PolicyCmd::Grant { identity_file, pattern } => identity::policy_grant(&identity_file, pattern.as_deref()),
257
            PolicyCmd::Revoke { key } => identity::policy_revoke(&key),
fce44ae9 Andy McCall ↶ 258
        },
259
        Cmd::Verify { change } => identity::verify(change.as_deref()),
260
        Cmd::Config { key, value, user } => identity::config(&key, &value, user),
261
    }
262
}