use git_workarea::{GitContext, GitError};
use log::info;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FollowError {
#[error("failed to push {} into {}: {}", branch, refname, output)]
Push {
branch: String,
refname: String,
output: String,
},
#[error("git error: {}", source)]
Git {
#[from]
source: GitError,
},
}
impl FollowError {
fn push(branch: String, refname: String, output: &[u8]) -> Self {
FollowError::Push {
branch,
refname,
output: String::from_utf8_lossy(output).into(),
}
}
}
type FollowResult<T> = Result<T, FollowError>;
#[derive(Debug)]
pub struct Follow {
ctx: GitContext,
branch: String,
ref_namespace: String,
}
impl Follow {
pub fn new<B>(ctx: GitContext, branch: B) -> Self
where
B: Into<String>,
{
Self {
ctx,
branch: branch.into(),
ref_namespace: "follow".into(),
}
}
pub fn ref_namespace<R>(&mut self, ref_namespace: R) -> &mut Self
where
R: Into<String>,
{
self.ref_namespace = ref_namespace.into();
self
}
pub fn update<N>(&self, name: N) -> FollowResult<()>
where
N: AsRef<str>,
{
self.update_impl(name.as_ref())
}
fn update_impl(&self, name: &str) -> FollowResult<()> {
info!(
target: "ghostflow/follow",
"following {} into {}",
self.branch,
name,
);
let refname = format!("refs/{}/{}/{}", self.ref_namespace, self.branch, name);
let push = self
.ctx
.git()
.arg("push")
.arg("--atomic")
.arg("--porcelain")
.arg("origin")
.arg(format!("+refs/heads/{}:{}", self.branch, refname))
.output()
.map_err(|err| GitError::subcommand("push", err))?;
if !push.status.success() {
return Err(FollowError::push(
self.branch.clone(),
refname,
&push.stderr,
));
}
Ok(())
}
}