OXYGENHUB
# 2b5ee371060e oxygen / src / cli / sync.rs
3.9 KiB · Rust · last changed TLS: oxy serve --tls-cert/--tls-key via rustls; remote.<name>.ca and OXY_CA for self-hosted roots 1 month ago source blame history raw
1
//! Commands that talk to remotes, and the reference server.
2
3
use super::format::plural;
4
use super::open;
5
use anyhow::{Context, Result};
6
use oxy_core::repo::Repo;
7
use std::path::PathBuf;
8
9
pub fn clone(url: &str, dest: Option<PathBuf>) -> Result<i32> {
10
    let dest = dest.unwrap_or_else(|| {
11
        let base = url.trim_end_matches('/').rsplit('/').next().unwrap_or("repo");
12
        PathBuf::from(base)
13
    });
14
    let repo = Repo::clone(url, &dest)?;
15
    let heads = repo.bookmark_heads()?;
16
    println!("Cloned into {} ({url} on {}: {})", repo.root().display(), repo.config.bookmark, plural(heads.len(), "head"));
17
    Ok(0)
18
}
19
20
pub fn remote_add(name: &str, url: &str) -> Result<i32> {
21
    let mut repo = open()?;
22
    repo.config.set(&format!("remote.{name}.url"), url)?;
23
    repo.save_config()?;
24
    println!("Added remote {name} = {url}");
25
    Ok(0)
26
}
27
28
pub fn remote_remove(name: &str) -> Result<i32> {
29
    let mut repo = open()?;
30
    repo.config.set(&format!("remote.{name}.url"), "")?;
31
    repo.save_config()?;
32
    println!("Removed remote {name}");
33
    Ok(0)
34
}
35
36
pub fn remote_list() -> Result<i32> {
37
    let repo = open()?;
38
    let view = repo.view()?;
39
    for (name, url) in &repo.config.remotes {
40
        println!("{name}\t{url}");
41
        if let Some(bms) = view.remotes.get(name) {
42
            for (bm, st) in bms {
43
                let heads = repo.store().get_state(st).map(|s| s.heads.len()).unwrap_or(0);
44
                println!("    {bm}: {}", plural(heads, "head"));
45
            }
46
        }
47
    }
48
    Ok(0)
49
}
50
51
fn remote_name(remote: Option<&str>) -> String {
52
    remote.unwrap_or("origin").to_string()
53
}
54
55
pub fn fetch(remote: Option<&str>) -> Result<i32> {
56
    let repo = open()?;
57
    let remote = remote_name(remote);
58
    for (name, heads, n) in repo.fetch(&remote)? {
59
        println!("{remote}/{name}: {}, {}", plural(heads.len(), "head"), plural(n, "new change"));
60
    }
61
    Ok(0)
62
}
63
64
pub fn pull(remote: Option<&str>) -> Result<i32> {
65
    let repo = open()?;
66
    let remote = remote_name(remote);
67
    let r = repo.pull(&remote)?;
68
    if !r.changed {
69
        println!("Already up to date.");
70
        return Ok(0);
71
    }
72
    println!(
73
        "{} now has {}{}",
74
        repo.config.bookmark,
75
        plural(r.heads.len(), "head"),
76
        if r.checked_out { "; working copy updated" } else { "; run `oxy checkout` to update the working copy" }
77
    );
78
    let snap = repo.snapshot("oxy pull")?;
79
    if snap.base_snapshot.is_conflicted() {
80
        println!("The result has conflicts; see oxy status.");
81
    }
82
    Ok(0)
83
}
84
85
pub fn push(remote: Option<&str>, force: bool) -> Result<i32> {
86
    let repo = open()?;
87
    let remote = remote_name(remote);
88
    let mut code = 0;
89
    for r in repo.push(&remote, force)? {
90
        if r.ok {
91
            println!("{remote}/{}: {}", r.name, if r.reason.is_empty() { "updated" } else { r.reason.as_str() });
92
        } else {
93
            println!("{remote}/{}: rejected: {}", r.name, r.reason);
94
            code = 1;
95
        }
96
    }
97
    Ok(code)
98
}
99
100
pub fn serve(root: PathBuf, bind: &str, tokens: Vec<String>, tls: Option<(PathBuf, PathBuf)>) -> Result<i32> {
101
    let listener = std::net::TcpListener::bind(bind).with_context(|| format!("binding {bind}"))?;
102
    let root = root.canonicalize()?;
103
    let tls = tls.map(|(cert, key)| oxy_server::TlsFiles { cert, key });
104
    println!(
105
        "Serving repositories under {} on {}://{bind}/<repo>/oxy/v1/ ({})",
106
        root.display(),
107
        if tls.is_some() { "https" } else { "http" },
108
        if tokens.is_empty() { "open: no push tokens configured" } else { "push requires a token" }
109
    );
110
    if tls.is_none() && !bind.starts_with("127.") && !bind.starts_with("localhost") && !bind.starts_with("[::1]") {
111
        eprintln!("warning: serving plain HTTP on a non-loopback address; put a TLS terminator in front or pass --tls-cert/--tls-key");
112
    }
113
    oxy_server::run_blocking(listener, oxy_server::ServerConfig { root, tokens, tls })?;
114
    Ok(0)
115
}