2021-03-29 17:10:53 +02:00
|
|
|
use std::os::unix::process::ExitStatusExt;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::process::Command;
|
|
|
|
use std::str;
|
|
|
|
|
|
|
|
use anyhow::bail;
|
|
|
|
|
|
|
|
use log::info;
|
|
|
|
|
|
|
|
use crate::gitea::Repository;
|
2021-03-29 20:52:04 +02:00
|
|
|
use crate::settings::{GlobalSettings, RepoUrl};
|
2021-03-29 17:10:53 +02:00
|
|
|
|
|
|
|
pub(crate) struct Job {
|
|
|
|
repo: Repository,
|
|
|
|
local_path: Option<PathBuf>,
|
2021-03-29 02:01:53 +02:00
|
|
|
}
|
|
|
|
|
2021-03-29 17:10:53 +02:00
|
|
|
// TODO: implement git operations with git2-rs where possible
|
|
|
|
|
2021-03-29 02:01:53 +02:00
|
|
|
impl Job {
|
2021-03-29 17:10:53 +02:00
|
|
|
pub(crate) fn new(repo: Repository) -> Self {
|
|
|
|
Self {
|
|
|
|
repo,
|
|
|
|
local_path: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn repo_exists(&self) -> bool {
|
|
|
|
self.local_path
|
|
|
|
.as_ref()
|
|
|
|
.map(|p| p.is_dir())
|
|
|
|
.unwrap_or(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mirror_repo(&self) -> anyhow::Result<()> {
|
|
|
|
info!("Cloning repo {}...", self.repo.full_name);
|
|
|
|
|
|
|
|
let output = Command::new("git")
|
|
|
|
.arg("clone")
|
|
|
|
.arg("--mirror")
|
|
|
|
.arg(&self.repo.clone_url)
|
|
|
|
.arg(format!("{}", self.local_path.as_ref().unwrap().display()))
|
|
|
|
.output()?;
|
|
|
|
|
|
|
|
if !output.status.success() {
|
|
|
|
let error = str::from_utf8(&output.stderr)?;
|
|
|
|
let code = output
|
|
|
|
.status
|
|
|
|
.code()
|
|
|
|
.unwrap_or_else(|| output.status.signal().unwrap());
|
|
|
|
|
|
|
|
bail!(
|
|
|
|
"couldn't mirror repo: exit code {}, stderr:\n{}",
|
|
|
|
code,
|
|
|
|
error
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle git LFS mirroring:
|
|
|
|
// https://github.com/git-lfs/git-lfs/issues/2342#issuecomment-310323647
|
|
|
|
|
|
|
|
Ok(())
|
2021-03-29 02:01:53 +02:00
|
|
|
}
|
2021-03-29 17:10:53 +02:00
|
|
|
|
|
|
|
fn update_repo(&self) -> anyhow::Result<()> {
|
|
|
|
info!("Updating repo {}...", self.repo.full_name);
|
|
|
|
|
|
|
|
let output = Command::new("git")
|
|
|
|
.arg("-C")
|
|
|
|
.arg(format!("{}", self.local_path.as_ref().unwrap().display()))
|
|
|
|
.arg("remote")
|
|
|
|
.arg("update")
|
|
|
|
.arg("origin")
|
|
|
|
// otherwise deleted tags and branches aren't updated on local copy
|
|
|
|
.arg("--prune")
|
|
|
|
.output()?;
|
|
|
|
|
|
|
|
if !output.status.success() {
|
|
|
|
let error = str::from_utf8(&output.stderr)?;
|
|
|
|
let code = output
|
|
|
|
.status
|
|
|
|
.code()
|
|
|
|
.unwrap_or_else(|| output.status.signal().unwrap());
|
|
|
|
|
|
|
|
bail!(
|
|
|
|
"couldn't update origin remote: exit code {}, stderr:\n{}",
|
|
|
|
code,
|
|
|
|
error
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-03-29 20:52:04 +02:00
|
|
|
/// Can return Ok(None) if the .lohr file didn't exist, but no significant error occured
|
|
|
|
fn read_remotes_from_lohr_file(&self) -> anyhow::Result<Option<Vec<RepoUrl>>> {
|
|
|
|
// try to read .lohr file from bare repo (hence the git show sorcery)
|
2021-03-29 17:21:55 +02:00
|
|
|
let output = Command::new("git")
|
|
|
|
.arg("-C")
|
|
|
|
.arg(format!("{}", self.local_path.as_ref().unwrap().display()))
|
|
|
|
.arg("show")
|
|
|
|
.arg("HEAD:.lohr")
|
|
|
|
.output()?;
|
|
|
|
|
|
|
|
if !output.status.success() {
|
|
|
|
let error = str::from_utf8(&output.stderr)?;
|
|
|
|
|
2021-03-29 20:52:04 +02:00
|
|
|
// this error case is okay, .lohr just doesn't exist
|
|
|
|
if error.contains("does not exist in 'HEAD'") {
|
|
|
|
return Ok(None);
|
|
|
|
} else {
|
|
|
|
let code = output
|
|
|
|
.status
|
|
|
|
.code()
|
|
|
|
.unwrap_or_else(|| output.status.signal().unwrap());
|
|
|
|
|
|
|
|
bail!(
|
|
|
|
"couldn't read .lohr file from repo {}: exit code {}, stderr:\n{}",
|
|
|
|
self.repo.full_name,
|
|
|
|
code,
|
|
|
|
error
|
|
|
|
);
|
|
|
|
}
|
2021-03-29 17:21:55 +02:00
|
|
|
}
|
|
|
|
|
2021-03-29 20:52:04 +02:00
|
|
|
let output = String::from_utf8(output.stdout)?;
|
|
|
|
|
|
|
|
Ok(Some(output.lines().map(String::from).collect()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_remotes(&self, config: &GlobalSettings) -> anyhow::Result<Vec<RepoUrl>> {
|
|
|
|
let local_path = self.local_path.as_ref().unwrap();
|
|
|
|
|
|
|
|
let stem_to_repo = |stem: &RepoUrl| -> RepoUrl {
|
|
|
|
let mut res = stem.clone();
|
|
|
|
if !res.ends_with('/') {
|
|
|
|
res.push('/');
|
|
|
|
};
|
|
|
|
res.push_str(local_path.file_name().unwrap().to_str().unwrap());
|
|
|
|
res
|
|
|
|
};
|
|
|
|
|
|
|
|
// use either .lohr file or default remotes from config
|
|
|
|
let mut remotes = match self.read_remotes_from_lohr_file()? {
|
|
|
|
Some(remotes) if !remotes.is_empty() => remotes,
|
|
|
|
_ => config.default_remotes.iter().map(stem_to_repo).collect(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// additional remotes
|
|
|
|
remotes.append(&mut config.additional_remotes.iter().map(stem_to_repo).collect());
|
|
|
|
|
|
|
|
Ok(remotes)
|
2021-03-29 17:21:55 +02:00
|
|
|
}
|
|
|
|
|
2021-03-29 20:52:04 +02:00
|
|
|
fn update_mirrors(&self, config: &GlobalSettings) -> anyhow::Result<()> {
|
|
|
|
for remote in &self.get_remotes(config)? {
|
2021-03-29 20:54:19 +02:00
|
|
|
info!("Updating mirror {}...", remote);
|
2021-03-29 17:10:53 +02:00
|
|
|
|
|
|
|
let output = Command::new("git")
|
|
|
|
.arg("-C")
|
|
|
|
.arg(format!("{}", self.local_path.as_ref().unwrap().display()))
|
|
|
|
.arg("push")
|
|
|
|
.arg("--mirror")
|
2021-03-29 17:21:55 +02:00
|
|
|
.arg(remote)
|
2021-03-29 17:10:53 +02:00
|
|
|
.output()?;
|
|
|
|
|
|
|
|
if !output.status.success() {
|
|
|
|
let error = str::from_utf8(&output.stderr)?;
|
|
|
|
let code = output
|
|
|
|
.status
|
|
|
|
.code()
|
|
|
|
.unwrap_or_else(|| output.status.signal().unwrap());
|
|
|
|
|
|
|
|
bail!(
|
2021-03-29 17:21:55 +02:00
|
|
|
"couldn't update remote {}: exit code {}, stderr:\n{}",
|
|
|
|
remote,
|
2021-03-29 17:10:53 +02:00
|
|
|
code,
|
|
|
|
error
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-03-29 20:52:04 +02:00
|
|
|
pub(crate) fn run(&mut self, homedir: &Path, config: &GlobalSettings) -> anyhow::Result<()> {
|
2021-03-29 17:10:53 +02:00
|
|
|
let local_path = homedir.join(&self.repo.full_name);
|
|
|
|
assert!(local_path.is_absolute());
|
|
|
|
self.local_path = Some(local_path);
|
|
|
|
|
|
|
|
if !self.repo_exists() {
|
|
|
|
self.mirror_repo()?;
|
|
|
|
} else {
|
|
|
|
self.update_repo()?;
|
|
|
|
}
|
|
|
|
|
2021-03-29 20:11:37 +02:00
|
|
|
self.update_mirrors(config)?;
|
|
|
|
|
|
|
|
info!("Done processing job {}!", self.repo.full_name);
|
|
|
|
|
|
|
|
Ok(())
|
2021-03-29 02:01:53 +02:00
|
|
|
}
|
|
|
|
}
|