OXYGENHUB
# 60a1223c894f oxygen / src / cli / sync.rs
18.6 KiB · Rust · last changed push --json reports the state each accepted bookmark now holds, to look up what the push started (such as its checks) 1 month ago source blame history raw
fce44ae9 Andy McCall ↶ 1
//! Commands that talk to remotes, and the reference server.
2
3
use super::format::plural;
75167672 Andy McCall 4
use super::json;
fce44ae9 Andy McCall ↶ 5
use super::open;
135212de Andy McCall ↶ 6
use super::ui;
fce44ae9 Andy McCall ↶ 7
use anyhow::{Context, Result};
8
use oxy_core::repo::Repo;
9
use std::path::PathBuf;
10
75167672 Andy McCall 11
pub fn clone(
12
    url: &str,
13
    dest: Option<PathBuf>,
14
    max_blob: Option<&str>,
15
    sparse: &[String],
16
    depth: Option<u32>,
17
) -> Result<i32> {
fce44ae9 Andy McCall ↶ 18
    let dest = dest.unwrap_or_else(|| {
75167672 Andy McCall 19
        let base = url
20
            .trim_end_matches('/')
21
            .rsplit('/')
22
            .next()
23
            .unwrap_or("repo");
fce44ae9 Andy McCall ↶ 24
        PathBuf::from(base)
25
    });
b3c5a59a Andy McCall ↶ 26
    let max_blob = max_blob.map(oxy_core::repo::parse_size).transpose()?;
135212de Andy McCall ↶ 27
    let sp = ui::spinner(&format!("Cloning {url}…"));
2863456a Andy McCall ↶ 28
    let repo = match depth {
29
        Some(d) => Repo::clone_shallow(url, &dest, d, max_blob, sparse)?,
30
        None => Repo::clone_with(url, &dest, max_blob, sparse)?,
31
    };
fce44ae9 Andy McCall ↶ 32
    let heads = repo.bookmark_heads()?;
75167672 Andy McCall 33
    sp.done(&format!(
34
        "{} into {} {}",
35
        ui::done("Cloned"),
36
        ui::path(repo.root().display()),
37
        ui::dim(format!(
38
            "({url}, {} with {})",
39
            ui::bookmark(&repo.config.bookmark),
40
            plural(heads.len(), "head")
41
        ))
42
    ));
fce44ae9 Andy McCall ↶ 43
    Ok(0)
44
}
45
46
pub fn remote_add(name: &str, url: &str) -> Result<i32> {
47
    let mut repo = open()?;
48
    repo.config.set(&format!("remote.{name}.url"), url)?;
49
    repo.save_config()?;
75167672 Andy McCall 50
    println!(
51
        "{} remote {} {} {url}",
52
        ui::done("Added"),
53
        ui::bookmark(name),
54
        ui::dim(ui::glyph("→", "->"))
55
    );
fce44ae9 Andy McCall ↶ 56
    Ok(0)
57
}
58
59
pub fn remote_remove(name: &str) -> Result<i32> {
60
    let mut repo = open()?;
61
    repo.config.set(&format!("remote.{name}.url"), "")?;
62
    repo.save_config()?;
94084087 Andy McCall ↶ 63
    println!("{} remote {}", ui::done("Removed"), ui::bookmark(name));
fce44ae9 Andy McCall ↶ 64
    Ok(0)
65
}
66
c6aa5bd9 Andy McCall ↶ 67
pub fn remote_list(as_json: bool) -> Result<i32> {
fce44ae9 Andy McCall ↶ 68
    let repo = open()?;
69
    let view = repo.view()?;
c6aa5bd9 Andy McCall ↶ 70
    if as_json {
71
        let items = repo.config.remotes.iter().map(|(name, url)| {
72
            let bms = view.remotes.get(name).map(|b| {
73
                json::arr(b.iter().map(|(bm, st)| {
75167672 Andy McCall 74
                    let heads = repo
75
                        .store()
76
                        .get_state(st)
77
                        .map(|s| s.heads)
78
                        .unwrap_or_default();
c6aa5bd9 Andy McCall ↶ 79
                    json::obj([("name", json::s(bm)), ("heads", json::hashes(&heads))])
80
                }))
81
            });
75167672 Andy McCall 82
            json::obj([
83
                ("name", json::s(name)),
84
                ("url", json::s(url)),
85
                ("bookmarks", bms.unwrap_or_else(|| "[]".into())),
86
            ])
c6aa5bd9 Andy McCall ↶ 87
        });
88
        println!("{}", json::arr(items));
89
        return Ok(0);
90
    }
94084087 Andy McCall ↶ 91
    if repo.config.remotes.is_empty() {
75167672 Andy McCall 92
        println!(
93
            "{}",
94
            ui::dim("No remotes; add one with oxy remote add <name> <url>.")
95
        );
94084087 Andy McCall ↶ 96
    }
fce44ae9 Andy McCall ↶ 97
    for (name, url) in &repo.config.remotes {
75167672 Andy McCall 98
        println!(
99
            "{} {}{}{}",
100
            ui::ok(ui::glyph("⇄", "<>")),
101
            ui::bookmark(name),
102
            ui::sep(),
103
            ui::dim(url)
104
        );
fce44ae9 Andy McCall ↶ 105
        if let Some(bms) = view.remotes.get(name) {
106
            for (bm, st) in bms {
75167672 Andy McCall 107
                let heads = repo
108
                    .store()
109
                    .get_state(st)
110
                    .map(|s| s.heads.len())
111
                    .unwrap_or(0);
112
                println!(
113
                    "    {} {}{}{}",
114
                    ui::dim(ui::glyph("↳", "->")),
115
                    ui::id(format!("{name}/{bm}")),
116
                    ui::sep(),
117
                    ui::dim(plural(heads, "head"))
118
                );
fce44ae9 Andy McCall ↶ 119
            }
120
        }
121
    }
122
    Ok(0)
123
}
124
125
fn remote_name(remote: Option<&str>) -> String {
126
    remote.unwrap_or("origin").to_string()
127
}
128
c6aa5bd9 Andy McCall ↶ 129
pub fn fetch(remote: Option<&str>, as_json: bool) -> Result<i32> {
fce44ae9 Andy McCall ↶ 130
    let repo = open()?;
131
    let remote = remote_name(remote);
135212de Andy McCall ↶ 132
    let sp = ui::spinner(&format!("Fetching from {remote}…"));
83e0677b Andy McCall ↶ 133
    let report = repo.fetch(&remote)?;
135212de Andy McCall ↶ 134
    sp.clear();
c6aa5bd9 Andy McCall ↶ 135
    if as_json {
136
        println!(
137
            "{}",
138
            json::obj([
139
                ("remote", json::s(&remote)),
75167672 Andy McCall 140
                (
141
                    "refs",
142
                    json::arr(report.refs.iter().map(|(name, heads, n)| json::obj([
143
                        ("name", json::s(name)),
144
                        ("heads", json::hashes(heads)),
145
                        ("new", json::n(*n))
146
                    ])))
147
                ),
c6aa5bd9 Andy McCall ↶ 148
                ("new_tags", json::n(report.new_tags)),
149
                ("unverified", json::b(report.unverified)),
150
            ])
151
        );
152
        return Ok(0);
153
    }
94084087 Andy McCall ↶ 154
    if report.refs.is_empty() {
75167672 Andy McCall 155
        println!(
156
            "{} {}",
157
            ui::done("Fetched"),
158
            ui::dim(format!("nothing new from {remote}"))
159
        );
94084087 Andy McCall ↶ 160
    }
83e0677b Andy McCall ↶ 161
    for (name, heads, n) in &report.refs {
75167672 Andy McCall 162
        let what = if *n == 0 {
163
            ui::dim("up to date")
164
        } else {
165
            ui::ok(plural(*n, "new change"))
166
        };
167
        println!(
168
            "{} {}{}{}{}{what}",
169
            ui::done("Fetched"),
170
            ui::id(format!("{remote}/{name}")),
171
            ui::sep(),
172
            ui::dim(plural(heads.len(), "head")),
173
            ui::sep()
174
        );
83e0677b Andy McCall ↶ 175
    }
132b884b Andy McCall ↶ 176
    if report.new_tags > 0 {
75167672 Andy McCall 177
        println!(
178
            "  {} {}",
179
            ui::note(),
180
            ui::dim(format!("{} adopted", plural(report.new_tags, "tag")))
181
        );
132b884b Andy McCall ↶ 182
    }
83e0677b Andy McCall ↶ 183
    if report.unverified {
75167672 Andy McCall 184
        eprintln!(
185
            "{} {remote} has no trust policy; nothing fetched from it was verified",
186
            ui::caution()
187
        );
fce44ae9 Andy McCall ↶ 188
    }
189
    Ok(0)
190
}
191
c6aa5bd9 Andy McCall ↶ 192
pub fn pull(remote: Option<&str>, as_json: bool) -> Result<i32> {
fce44ae9 Andy McCall ↶ 193
    let repo = open()?;
194
    let remote = remote_name(remote);
135212de Andy McCall ↶ 195
    let sp = ui::spinner(&format!("Pulling from {remote}…"));
fce44ae9 Andy McCall ↶ 196
    let r = repo.pull(&remote)?;
c6aa5bd9 Andy McCall ↶ 197
    if as_json {
198
        sp.clear();
199
        let conflicted = repo.snapshot("oxy pull")?.base_snapshot.is_conflicted();
75167672 Andy McCall 200
        println!(
201
            "{}",
202
            json::obj([
203
                ("remote", json::s(&remote)),
204
                ("heads", json::hashes(&r.heads)),
205
                ("changed", json::b(r.changed)),
206
                ("checked_out", json::b(r.checked_out)),
207
                ("conflicted", json::b(conflicted))
208
            ])
209
        );
c6aa5bd9 Andy McCall ↶ 210
        return Ok(0);
211
    }
fce44ae9 Andy McCall ↶ 212
    if !r.changed {
75167672 Andy McCall 213
        sp.done(&format!(
214
            "{} {}",
215
            ui::ok(ui::glyph("✔", "*")),
216
            ui::dim("Already up to date.")
217
        ));
fce44ae9 Andy McCall ↶ 218
        return Ok(0);
219
    }
135212de Andy McCall ↶ 220
    sp.done(&format!(
221
        "{} {} now has {}{}",
94084087 Andy McCall ↶ 222
        ui::done("Pulled:"),
223
        ui::bookmark(&repo.config.bookmark),
fce44ae9 Andy McCall ↶ 224
        plural(r.heads.len(), "head"),
75167672 Andy McCall 225
        ui::dim(if r.checked_out {
226
            "; working copy updated"
227
        } else {
228
            "; run oxy checkout to update the working copy"
229
        })
135212de Andy McCall ↶ 230
    ));
fce44ae9 Andy McCall ↶ 231
    let snap = repo.snapshot("oxy pull")?;
232
    if snap.base_snapshot.is_conflicted() {
75167672 Andy McCall 233
        println!(
234
            "  {} {}",
235
            ui::caution(),
236
            ui::warn("the result has conflicts; see oxy status")
237
        );
fce44ae9 Andy McCall ↶ 238
    }
239
    Ok(0)
240
}
241
75167672 Andy McCall 242
pub fn push(
243
    remote: Option<&str>,
244
    force: bool,
245
    bookmark: Option<&str>,
246
    as_json: bool,
247
) -> Result<i32> {
fce44ae9 Andy McCall ↶ 248
    let repo = open()?;
249
    let remote = remote_name(remote);
135212de Andy McCall ↶ 250
    let sp = ui::spinner(&format!("Pushing to {remote}…"));
7b9809e6 Andy McCall ↶ 251
    let results = match bookmark {
252
        Some(b) => repo.push_bookmark(&remote, b, force)?,
253
        None => repo.push(&remote, force)?,
254
    };
135212de Andy McCall ↶ 255
    sp.clear();
c6aa5bd9 Andy McCall ↶ 256
    if as_json {
60a1223c Andy McCall ↶ 257
        // The state each accepted bookmark now holds on the remote (its local
258
        // state object), so a caller can look up what the push started, such
259
        // as the checks a forge runs on it.
260
        let view = repo.view()?;
75167672 Andy McCall 261
        println!(
262
            "{}",
60a1223c Andy McCall ↶ 263
            json::arr(results.iter().map(|r| {
264
                let state = view
265
                    .bookmarks
266
                    .get(&r.name)
267
                    .filter(|_| r.ok)
268
                    .map(|st| st.to_hex())
269
                    .unwrap_or_default();
270
                json::obj([
271
                    ("name", json::s(&r.name)),
272
                    ("ok", json::b(r.ok)),
273
                    ("reason", json::s(&r.reason)),
274
                    ("state", json::s(&state)),
275
                ])
276
            }))
75167672 Andy McCall 277
        );
c6aa5bd9 Andy McCall ↶ 278
        return Ok(if results.iter().all(|r| r.ok) { 0 } else { 1 });
279
    }
fce44ae9 Andy McCall ↶ 280
    let mut code = 0;
135212de Andy McCall ↶ 281
    for r in results {
fce44ae9 Andy McCall ↶ 282
        if r.ok {
75167672 Andy McCall 283
            println!(
284
                "{} {}{}{}",
285
                ui::done("Pushed"),
286
                ui::id(format!("{remote}/{}", r.name)),
287
                ui::sep(),
288
                ui::dim(if r.reason.is_empty() {
289
                    "updated"
290
                } else {
291
                    r.reason.as_str()
292
                })
293
            );
fce44ae9 Andy McCall ↶ 294
        } else {
75167672 Andy McCall 295
            println!(
296
                "{} {} {}{}{}",
297
                ui::err(ui::glyph("✖", "x")),
298
                ui::err("Rejected"),
299
                ui::id(format!("{remote}/{}", r.name)),
300
                ui::sep(),
301
                r.reason
302
            );
fce44ae9 Andy McCall ↶ 303
            code = 1;
304
        }
305
    }
306
    Ok(code)
307
}
308
75167672 Andy McCall 309
pub fn serve(
310
    root: PathBuf,
311
    bind: &str,
312
    tokens: Vec<String>,
313
    tls: Option<(PathBuf, PathBuf)>,
314
) -> Result<i32> {
fce44ae9 Andy McCall ↶ 315
    let listener = std::net::TcpListener::bind(bind).with_context(|| format!("binding {bind}"))?;
316
    let root = root.canonicalize()?;
2b5ee371 Andy McCall ↶ 317
    let tls = tls.map(|(cert, key)| oxy_server::TlsFiles { cert, key });
fce44ae9 Andy McCall ↶ 318
    println!(
2b5ee371 Andy McCall ↶ 319
        "Serving repositories under {} on {}://{bind}/<repo>/oxy/v1/ ({})",
fce44ae9 Andy McCall ↶ 320
        root.display(),
2b5ee371 Andy McCall ↶ 321
        if tls.is_some() { "https" } else { "http" },
75167672 Andy McCall 322
        if tokens.is_empty() {
323
            "open: no push tokens configured"
324
        } else {
325
            "push requires a token"
326
        }
fce44ae9 Andy McCall ↶ 327
    );
75167672 Andy McCall 328
    if tls.is_none()
329
        && !bind.starts_with("127.")
330
        && !bind.starts_with("localhost")
331
        && !bind.starts_with("[::1]")
332
    {
2b5ee371 Andy McCall ↶ 333
        eprintln!("warning: serving plain HTTP on a non-loopback address; put a TLS terminator in front or pass --tls-cert/--tls-key");
334
    }
335
    oxy_server::run_blocking(listener, oxy_server::ServerConfig { root, tokens, tls })?;
fce44ae9 Andy McCall ↶ 336
    Ok(0)
337
}
79ab8528 Andy McCall ↶ 338
30ea9c1e Andy McCall ↶ 339
pub fn git_export(git_dir: &std::path::Path, branch: Option<&str>) -> Result<i32> {
79ab8528 Andy McCall ↶ 340
    let repo = open()?;
135212de Andy McCall ↶ 341
    let sp = ui::spinner("Exporting to Git…");
30ea9c1e Andy McCall ↶ 342
    let r = match branch {
343
        Some(b) => repo.git_export_as(git_dir, b)?,
344
        None => repo.git_export(git_dir)?,
345
    };
135212de Andy McCall ↶ 346
    sp.clear();
79ab8528 Andy McCall ↶ 347
    println!(
94084087 Andy McCall ↶ 348
        "{} {} to {} as branch {} {}",
349
        ui::done("Exported"),
e8b4e156 Andy McCall ↶ 350
        plural(r.exported, "new state"),
94084087 Andy McCall ↶ 351
        ui::path(git_dir.display()),
352
        ui::bookmark(&r.branch),
75167672 Andy McCall 353
        ui::dim(format!(
354
            "(currently {}; {} already exported)",
355
            plural(r.heads, "head"),
356
            r.skipped
357
        ))
79ab8528 Andy McCall ↶ 358
    );
359
    Ok(0)
360
}
c3069537 Andy McCall ↶ 361
30ea9c1e Andy McCall ↶ 362
pub fn git_fetch(url: &str, branch: &str) -> Result<i32> {
363
    let repo = open()?;
364
    let sp = ui::spinner(&format!("Fetching {url} {branch}…"));
365
    let r = repo.git_fetch(url, branch)?;
366
    sp.clear();
367
    if r.commits == 0 {
75167672 Andy McCall 368
        println!(
369
            "{} {}",
370
            ui::done("Fetched"),
371
            ui::dim(format!("nothing new from {url} {branch}"))
372
        );
30ea9c1e Andy McCall ↶ 373
    } else {
374
        println!(
375
            "{} {} ({} merges) from {} {}{}{} now has {}",
376
            ui::done("Fetched"),
377
            plural(r.commits, "new commit"),
378
            r.merges,
379
            ui::path(url),
380
            ui::bookmark(branch),
381
            ui::sep(),
382
            ui::bookmark(&repo.config.bookmark),
383
            plural(r.heads, "head")
384
        );
385
        if !repo.snapshot("oxy git fetch")?.changed {
386
            println!("  {} {}", ui::note(), ui::dim("working copy updated"));
387
        } else {
75167672 Andy McCall 388
            println!(
389
                "  {} {}",
390
                ui::note(),
391
                ui::dim("run oxy checkout to update the working copy")
392
            );
30ea9c1e Andy McCall ↶ 393
        }
394
    }
4dee277c Andy McCall ↶ 395
    if r.submodules > 0 {
75167672 Andy McCall 396
        println!(
397
            "  {} {}",
398
            ui::note(),
399
            ui::dim(format!(
400
                "{} submodule {} imported as subrepos with Git pins",
401
                r.submodules,
402
                if r.submodules == 1 {
403
                    "entry"
404
                } else {
405
                    "entries"
406
                }
407
            ))
408
        );
30ea9c1e Andy McCall ↶ 409
    }
410
    Ok(0)
411
}
412
413
pub fn git_push(url: &str, branch: &str) -> Result<i32> {
414
    let repo = open()?;
415
    let sp = ui::spinner(&format!("Pushing to {url} {branch}…"));
416
    let r = repo.git_push(url, branch)?;
417
    sp.clear();
418
    println!(
419
        "{} {} to {} {} {}",
420
        ui::done("Pushed"),
421
        plural(r.exported, "new commit"),
422
        ui::path(url),
423
        ui::bookmark(&r.branch),
424
        ui::dim(format!("({} already there)", r.skipped))
425
    );
426
    Ok(0)
427
}
428
c3069537 Andy McCall ↶ 429
pub fn git_import(git_dir: &std::path::Path, dest: &std::path::Path, branch: &str) -> Result<i32> {
430
    let repo = Repo::init(dest)?;
431
    let started = std::time::Instant::now();
135212de Andy McCall ↶ 432
    let sp = ui::spinner(&format!("Importing {} {branch}…", git_dir.display()));
c3069537 Andy McCall ↶ 433
    let r = repo.import_git(git_dir, branch)?;
135212de Andy McCall ↶ 434
    sp.clear();
c3069537 Andy McCall ↶ 435
    println!(
94084087 Andy McCall ↶ 436
        "{} {} ({} merges) from {} {} into {} {}",
437
        ui::done("Imported"),
c3069537 Andy McCall ↶ 438
        plural(r.commits, "commit"),
439
        r.merges,
94084087 Andy McCall ↶ 440
        ui::path(git_dir.display()),
441
        ui::bookmark(branch),
442
        ui::path(repo.root().display()),
75167672 Andy McCall 443
        ui::dim(format!(
444
            "in {:.1?}; {} now has {}",
445
            started.elapsed(),
446
            repo.config.bookmark,
447
            plural(r.heads, "head")
448
        ))
c3069537 Andy McCall ↶ 449
    );
4dee277c Andy McCall ↶ 450
    if r.submodules > 0 {
75167672 Andy McCall 451
        println!(
452
            "  {} {}",
453
            ui::note(),
454
            ui::dim(format!(
455
                "{} submodule {} imported as subrepos with Git pins",
456
                r.submodules,
457
                if r.submodules == 1 {
458
                    "entry"
459
                } else {
460
                    "entries"
461
                }
462
            ))
463
        );
4dee277c Andy McCall ↶ 464
    }
465
    Ok(0)
466
}
467
468
pub fn submodule_add(url: &str, path: &str, bookmark: Option<&str>) -> Result<i32> {
469
    let repo = open()?;
470
    let sp = ui::spinner(&format!("Cloning {url} into {path}…"));
471
    let pin = repo.submodule_add(url, path, bookmark)?;
75167672 Andy McCall 472
    sp.done(&format!(
473
        "{} subrepo {} {} {}",
474
        ui::done("Added"),
475
        ui::path(path),
476
        ui::dim(ui::glyph("→", "->")),
477
        ui::dim(pin.summary())
478
    ));
479
    println!(
480
        "  {} {}",
481
        ui::note(),
482
        ui::dim("recorded in .oxymodules; commit to pin it")
483
    );
4dee277c Andy McCall ↶ 484
    Ok(0)
485
}
486
487
pub fn submodule_update(init: bool, paths: &[String]) -> Result<i32> {
488
    let repo = open()?;
489
    let sp = ui::spinner("Updating subrepos…");
490
    let results = repo.submodule_update(init, paths)?;
491
    sp.clear();
492
    if results.is_empty() {
493
        println!("{}", ui::dim("No subrepos."));
494
    }
495
    let mut code = 0;
496
    for (path, r) in results {
497
        use oxy_core::repo::SubrepoSync::*;
498
        let line = match r {
499
            Updated => ui::done("Updated"),
500
            UpToDate => ui::ok(format!("{} up to date", ui::glyph("✔", "*"))),
501
            Missing => {
502
                code = 1;
75167672 Andy McCall 503
                ui::warn(format!(
504
                    "{} missing (pass --init to clone)",
505
                    ui::glyph("⚠", "!")
506
                ))
4dee277c Andy McCall ↶ 507
            }
508
            Dirty => {
509
                code = 1;
75167672 Andy McCall 510
                ui::warn(format!(
511
                    "{} has uncommitted changes; left as is",
512
                    ui::glyph("⚠", "!")
513
                ))
4dee277c Andy McCall ↶ 514
            }
515
            Failed(e) => {
516
                code = 1;
517
                ui::err(format!("{} {e}", ui::glyph("✖", "x")))
518
            }
519
        };
520
        println!("{line} {}", ui::path(path));
521
    }
522
    Ok(code)
523
}
524
c6aa5bd9 Andy McCall ↶ 525
fn pin_json(p: &oxy_core::subrepo::Pin) -> String {
526
    match p {
75167672 Andy McCall 527
        oxy_core::subrepo::Pin::Oxy {
528
            repo,
529
            heads,
530
            bookmark,
531
        } => json::obj([
532
            ("kind", json::s("oxy")),
533
            (
534
                "repo",
535
                json::s(&repo.iter().map(|b| format!("{b:02x}")).collect::<String>()),
536
            ),
537
            ("heads", json::hashes(heads)),
538
            ("bookmark", json::s(bookmark)),
539
        ]),
540
        oxy_core::subrepo::Pin::Git { sha, branch } => json::obj([
541
            ("kind", json::s("git")),
542
            ("sha", json::s(sha)),
543
            ("branch", json::s(branch)),
544
        ]),
c6aa5bd9 Andy McCall ↶ 545
    }
546
}
547
548
pub fn submodule_status(as_json: bool) -> Result<i32> {
4dee277c Andy McCall ↶ 549
    let repo = open()?;
550
    let subs = repo.subrepos()?;
c6aa5bd9 Andy McCall ↶ 551
    if as_json {
552
        println!(
553
            "{}",
554
            json::arr(subs.iter().map(|s| {
555
                json::obj([
556
                    ("path", json::s(&s.path)),
557
                    ("pin", pin_json(&s.pin)),
558
                    ("actual", json::opt(s.actual.as_ref(), pin_json)),
559
                    ("at_pin", json::b(s.actual.as_ref() == Some(&s.pin))),
560
                    ("dirty", json::b(s.dirty)),
561
                    ("url", json::opt(s.url.as_deref(), json::s)),
562
                ])
563
            }))
564
        );
565
        return Ok(0);
566
    }
4dee277c Andy McCall ↶ 567
    if subs.is_empty() {
568
        println!("{}", ui::dim("No subrepos."));
94084087 Andy McCall ↶ 569
    }
4dee277c Andy McCall ↶ 570
    for s in subs {
571
        let state = match (&s.actual, s.dirty) {
572
            (None, _) => ui::warn(format!("{} missing", ui::glyph("⚠", "!"))),
573
            (Some(a), dirty) if *a == s.pin => {
75167672 Andy McCall 574
                if dirty {
575
                    ui::warn(format!(
576
                        "{} at pin, uncommitted changes",
577
                        ui::glyph("✎", "~")
578
                    ))
579
                } else {
580
                    ui::ok(format!("{} at pin", ui::glyph("✔", "*")))
581
                }
4dee277c Andy McCall ↶ 582
            }
583
            (Some(a), _) => ui::warn(format!("{} at {}", ui::glyph("↕", "<>"), a.summary())),
584
        };
75167672 Andy McCall 585
        println!(
586
            "{} {}{}{}{}{state}",
587
            ui::ok(ui::glyph("⧉", "#")),
588
            ui::path(&s.path),
589
            ui::sep(),
590
            ui::dim(s.pin.summary()),
591
            ui::sep()
592
        );
4dee277c Andy McCall ↶ 593
        if let Some(u) = s.url {
594
            println!("    {} {}", ui::dim(ui::glyph("↳", "->")), ui::dim(u));
595
        }
596
    }
597
    Ok(0)
598
}
599
600
pub fn submodule_remove(path: &str) -> Result<i32> {
601
    let repo = open()?;
602
    repo.submodule_remove(path)?;
75167672 Andy McCall 603
    println!(
604
        "{} subrepo {} {}",
605
        ui::done("Removed"),
606
        ui::path(path),
607
        ui::dim("(directory left in place; commit to record the removal)")
608
    );
c3069537 Andy McCall ↶ 609
    Ok(0)
610
}
2863456a Andy McCall ↶ 611
612
pub fn deepen(remote: &str, depth: u32) -> Result<i32> {
613
    let repo = open()?;
614
    if !repo.is_shallow() {
75167672 Andy McCall 615
        println!(
616
            "{}",
617
            ui::dim("This repository is not shallow; nothing to deepen.")
618
        );
2863456a Andy McCall ↶ 619
        return Ok(0);
620
    }
621
    let sp = ui::spinner(&format!("Fetching {depth} more levels from {remote}…"));
622
    let n = repo.deepen(remote, depth)?;
75167672 Andy McCall 623
    sp.done(&format!(
624
        "{} {}; {}",
625
        ui::ok("Deepened by"),
626
        plural(n, "change"),
627
        if repo.is_shallow() {
628
            "still shallow".to_string()
629
        } else {
630
            ui::ok("history is now complete")
631
        }
632
    ));
2863456a Andy McCall ↶ 633
    Ok(0)
634
}