OXYGENHUB
# 77a2a6b3254d oxygen / src / cli / sync.rs
4.0 KiB · Rust · last changed Trust policy: policy objects, init/grant/revoke, enforcement on push and fetch, propagation with downgrade protection (spec 0.6) 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
    let report = repo.fetch(&remote)?;
59
    for (name, heads, n) in &report.refs {
60
        println!("{remote}/{name}: {}, {}", plural(heads.len(), "head"), plural(*n, "new change"));
61
    }
62
    if report.unverified {
63
        eprintln!("warning: {remote} has no trust policy; nothing fetched from it was verified");
64
    }
65
    Ok(0)
66
}
67
68
pub fn pull(remote: Option<&str>) -> Result<i32> {
69
    let repo = open()?;
70
    let remote = remote_name(remote);
71
    let r = repo.pull(&remote)?;
72
    if !r.changed {
73
        println!("Already up to date.");
74
        return Ok(0);
75
    }
76
    println!(
77
        "{} now has {}{}",
78
        repo.config.bookmark,
79
        plural(r.heads.len(), "head"),
80
        if r.checked_out { "; working copy updated" } else { "; run `oxy checkout` to update the working copy" }
81
    );
82
    let snap = repo.snapshot("oxy pull")?;
83
    if snap.base_snapshot.is_conflicted() {
84
        println!("The result has conflicts; see oxy status.");
85
    }
86
    Ok(0)
87
}
88
89
pub fn push(remote: Option<&str>, force: bool) -> Result<i32> {
90
    let repo = open()?;
91
    let remote = remote_name(remote);
92
    let mut code = 0;
93
    for r in repo.push(&remote, force)? {
94
        if r.ok {
95
            println!("{remote}/{}: {}", r.name, if r.reason.is_empty() { "updated" } else { r.reason.as_str() });
96
        } else {
97
            println!("{remote}/{}: rejected: {}", r.name, r.reason);
98
            code = 1;
99
        }
100
    }
101
    Ok(code)
102
}
103
104
pub fn serve(root: PathBuf, bind: &str, tokens: Vec<String>, tls: Option<(PathBuf, PathBuf)>) -> Result<i32> {
105
    let listener = std::net::TcpListener::bind(bind).with_context(|| format!("binding {bind}"))?;
106
    let root = root.canonicalize()?;
107
    let tls = tls.map(|(cert, key)| oxy_server::TlsFiles { cert, key });
108
    println!(
109
        "Serving repositories under {} on {}://{bind}/<repo>/oxy/v1/ ({})",
110
        root.display(),
111
        if tls.is_some() { "https" } else { "http" },
112
        if tokens.is_empty() { "open: no push tokens configured" } else { "push requires a token" }
113
    );
114
    if tls.is_none() && !bind.starts_with("127.") && !bind.starts_with("localhost") && !bind.starts_with("[::1]") {
115
        eprintln!("warning: serving plain HTTP on a non-loopback address; put a TLS terminator in front or pass --tls-cert/--tls-key");
116
    }
117
    oxy_server::run_blocking(listener, oxy_server::ServerConfig { root, tokens, tls })?;
118
    Ok(0)
119
}