OXYGENHUB
# 5259a3af8ee6 oxygen / src / cli / sync.rs
5.9 KiB · Rust · last changed Lazy blobs: clone --max-blob / filter.max-blob, absent-blob registry fetched on first read, objects endpoint (spec 0.6e) 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;
4
use super::open;
135212de Andy McCall ↶ 5
use super::ui;
fce44ae9 Andy McCall ↶ 6
use anyhow::{Context, Result};
7
use oxy_core::repo::Repo;
8
use std::path::PathBuf;
9
7421309f Andy McCall ↶ 10
pub fn clone(url: &str, dest: Option<PathBuf>, max_blob: Option<&str>, sparse: &[String]) -> Result<i32> {
fce44ae9 Andy McCall ↶ 11
    let dest = dest.unwrap_or_else(|| {
12
        let base = url.trim_end_matches('/').rsplit('/').next().unwrap_or("repo");
13
        PathBuf::from(base)
14
    });
b3c5a59a Andy McCall ↶ 15
    let max_blob = max_blob.map(oxy_core::repo::parse_size).transpose()?;
135212de Andy McCall ↶ 16
    let sp = ui::spinner(&format!("Cloning {url}…"));
7421309f Andy McCall ↶ 17
    let repo = Repo::clone_with(url, &dest, max_blob, sparse)?;
fce44ae9 Andy McCall ↶ 18
    let heads = repo.bookmark_heads()?;
135212de Andy McCall ↶ 19
    sp.done(&format!("{} into {} ({} on {}: {})", ui::ok("Cloned"), ui::path(repo.root().display()), url, ui::id(&repo.config.bookmark), plural(heads.len(), "head")));
fce44ae9 Andy McCall ↶ 20
    Ok(0)
21
}
22
23
pub fn remote_add(name: &str, url: &str) -> Result<i32> {
24
    let mut repo = open()?;
25
    repo.config.set(&format!("remote.{name}.url"), url)?;
26
    repo.save_config()?;
27
    println!("Added remote {name} = {url}");
28
    Ok(0)
29
}
30
31
pub fn remote_remove(name: &str) -> Result<i32> {
32
    let mut repo = open()?;
33
    repo.config.set(&format!("remote.{name}.url"), "")?;
34
    repo.save_config()?;
35
    println!("Removed remote {name}");
36
    Ok(0)
37
}
38
39
pub fn remote_list() -> Result<i32> {
40
    let repo = open()?;
41
    let view = repo.view()?;
42
    for (name, url) in &repo.config.remotes {
43
        println!("{name}\t{url}");
44
        if let Some(bms) = view.remotes.get(name) {
45
            for (bm, st) in bms {
46
                let heads = repo.store().get_state(st).map(|s| s.heads.len()).unwrap_or(0);
47
                println!("    {bm}: {}", plural(heads, "head"));
48
            }
49
        }
50
    }
51
    Ok(0)
52
}
53
54
fn remote_name(remote: Option<&str>) -> String {
55
    remote.unwrap_or("origin").to_string()
56
}
57
58
pub fn fetch(remote: Option<&str>) -> Result<i32> {
59
    let repo = open()?;
60
    let remote = remote_name(remote);
135212de Andy McCall ↶ 61
    let sp = ui::spinner(&format!("Fetching from {remote}…"));
83e0677b Andy McCall ↶ 62
    let report = repo.fetch(&remote)?;
135212de Andy McCall ↶ 63
    sp.clear();
83e0677b Andy McCall ↶ 64
    for (name, heads, n) in &report.refs {
135212de Andy McCall ↶ 65
        println!("{} {}, {}", ui::id(format!("{remote}/{name}")), plural(heads.len(), "head"), plural(*n, "new change"));
83e0677b Andy McCall ↶ 66
    }
67
    if report.unverified {
135212de Andy McCall ↶ 68
        eprintln!("{} {remote} has no trust policy; nothing fetched from it was verified", ui::warn("warning:"));
fce44ae9 Andy McCall ↶ 69
    }
70
    Ok(0)
71
}
72
73
pub fn pull(remote: Option<&str>) -> Result<i32> {
74
    let repo = open()?;
75
    let remote = remote_name(remote);
135212de Andy McCall ↶ 76
    let sp = ui::spinner(&format!("Pulling from {remote}…"));
fce44ae9 Andy McCall ↶ 77
    let r = repo.pull(&remote)?;
78
    if !r.changed {
135212de Andy McCall ↶ 79
        sp.done(&ui::dim("Already up to date."));
fce44ae9 Andy McCall ↶ 80
        return Ok(0);
81
    }
135212de Andy McCall ↶ 82
    sp.done(&format!(
83
        "{} {} now has {}{}",
84
        ui::ok("Pulled:"),
85
        ui::id(&repo.config.bookmark),
fce44ae9 Andy McCall ↶ 86
        plural(r.heads.len(), "head"),
87
        if r.checked_out { "; working copy updated" } else { "; run `oxy checkout` to update the working copy" }
135212de Andy McCall ↶ 88
    ));
fce44ae9 Andy McCall ↶ 89
    let snap = repo.snapshot("oxy pull")?;
90
    if snap.base_snapshot.is_conflicted() {
135212de Andy McCall ↶ 91
        println!("  {}", ui::warn("the result has conflicts; see oxy status"));
fce44ae9 Andy McCall ↶ 92
    }
93
    Ok(0)
94
}
95
96
pub fn push(remote: Option<&str>, force: bool) -> Result<i32> {
97
    let repo = open()?;
98
    let remote = remote_name(remote);
135212de Andy McCall ↶ 99
    let sp = ui::spinner(&format!("Pushing to {remote}…"));
100
    let results = repo.push(&remote, force)?;
101
    sp.clear();
fce44ae9 Andy McCall ↶ 102
    let mut code = 0;
135212de Andy McCall ↶ 103
    for r in results {
fce44ae9 Andy McCall ↶ 104
        if r.ok {
135212de Andy McCall ↶ 105
            println!("{} {}", ui::id(format!("{remote}/{}", r.name)), ui::ok(if r.reason.is_empty() { "updated" } else { r.reason.as_str() }));
fce44ae9 Andy McCall ↶ 106
        } else {
135212de Andy McCall ↶ 107
            println!("{} {} {}", ui::id(format!("{remote}/{}", r.name)), ui::err("rejected:"), r.reason);
fce44ae9 Andy McCall ↶ 108
            code = 1;
109
        }
110
    }
111
    Ok(code)
112
}
113
2b5ee371 Andy McCall ↶ 114
pub fn serve(root: PathBuf, bind: &str, tokens: Vec<String>, tls: Option<(PathBuf, PathBuf)>) -> Result<i32> {
fce44ae9 Andy McCall ↶ 115
    let listener = std::net::TcpListener::bind(bind).with_context(|| format!("binding {bind}"))?;
116
    let root = root.canonicalize()?;
2b5ee371 Andy McCall ↶ 117
    let tls = tls.map(|(cert, key)| oxy_server::TlsFiles { cert, key });
fce44ae9 Andy McCall ↶ 118
    println!(
2b5ee371 Andy McCall ↶ 119
        "Serving repositories under {} on {}://{bind}/<repo>/oxy/v1/ ({})",
fce44ae9 Andy McCall ↶ 120
        root.display(),
2b5ee371 Andy McCall ↶ 121
        if tls.is_some() { "https" } else { "http" },
fce44ae9 Andy McCall ↶ 122
        if tokens.is_empty() { "open: no push tokens configured" } else { "push requires a token" }
123
    );
2b5ee371 Andy McCall ↶ 124
    if tls.is_none() && !bind.starts_with("127.") && !bind.starts_with("localhost") && !bind.starts_with("[::1]") {
125
        eprintln!("warning: serving plain HTTP on a non-loopback address; put a TLS terminator in front or pass --tls-cert/--tls-key");
126
    }
127
    oxy_server::run_blocking(listener, oxy_server::ServerConfig { root, tokens, tls })?;
fce44ae9 Andy McCall ↶ 128
    Ok(0)
129
}
79ab8528 Andy McCall ↶ 130
131
pub fn git_export(git_dir: &std::path::Path) -> Result<i32> {
132
    let repo = open()?;
135212de Andy McCall ↶ 133
    let sp = ui::spinner("Exporting to Git…");
79ab8528 Andy McCall ↶ 134
    let r = repo.git_export(git_dir)?;
135212de Andy McCall ↶ 135
    sp.clear();
79ab8528 Andy McCall ↶ 136
    println!(
137
        "Exported {} to {} (branch {}: {}{}); {} already exported.",
138
        plural(r.exported, "change"),
139
        git_dir.display(),
140
        r.branch,
141
        plural(r.heads, "head"),
142
        if r.heads > 1 { ", joined by a synthetic merge commit" } else { "" },
143
        r.skipped
144
    );
145
    Ok(0)
146
}
c3069537 Andy McCall ↶ 147
148
pub fn git_import(git_dir: &std::path::Path, dest: &std::path::Path, branch: &str) -> Result<i32> {
149
    let repo = Repo::init(dest)?;
150
    let started = std::time::Instant::now();
135212de Andy McCall ↶ 151
    let sp = ui::spinner(&format!("Importing {} {branch}…", git_dir.display()));
c3069537 Andy McCall ↶ 152
    let r = repo.import_git(git_dir, branch)?;
135212de Andy McCall ↶ 153
    sp.clear();
c3069537 Andy McCall ↶ 154
    println!(
155
        "Imported {} ({} merges) from {} {branch} into {} in {:.1?}: {} now has {}.{}",
156
        plural(r.commits, "commit"),
157
        r.merges,
158
        git_dir.display(),
159
        repo.root().display(),
160
        started.elapsed(),
161
        repo.config.bookmark,
162
        plural(r.heads, "head"),
163
        if r.skipped_submodules > 0 { format!(" Skipped {} submodule entries.", r.skipped_submodules) } else { String::new() }
164
    );
165
    Ok(0)
166
}