| 1 |
|
| 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 |
} |
| 120 |
|
| 121 |
pub fn git_export(git_dir: &std::path::Path) -> Result<i32> { |
| 122 |
let repo = open()?; |
| 123 |
let r = repo.git_export(git_dir)?; |
| 124 |
println!( |
| 125 |
"Exported {} to {} (branch {}: {}{}); {} already exported.", |
| 126 |
plural(r.exported, "change"), |
| 127 |
git_dir.display(), |
| 128 |
r.branch, |
| 129 |
plural(r.heads, "head"), |
| 130 |
if r.heads > 1 { ", joined by a synthetic merge commit" } else { "" }, |
| 131 |
r.skipped |
| 132 |
); |
| 133 |
Ok(0) |
| 134 |
} |