use std::path::Path;
use std::process::Command;
use rtb_credentials::CredentialRef;
use super::{auth, Repo, RepoError};
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct PushOptions {
pub credential: Option<CredentialRef>,
}
impl PushOptions {
#[must_use]
pub fn with_credential(mut self, cref: CredentialRef) -> Self {
self.credential = Some(cref);
self
}
}
impl Repo {
pub async fn push(
&self,
remote: &str,
refspec: &str,
opts: PushOptions,
) -> Result<(), RepoError> {
let workdir = self.path().to_path_buf();
let remote = remote.to_string();
let refspec = refspec.to_string();
let token = match opts.credential.as_ref() {
Some(cref) => Some(auth::resolve_default(cref).await?),
None => None,
};
tokio::task::spawn_blocking(move || {
let token_str = token.as_ref().map(auth::expose);
run_push(&workdir, &remote, &refspec, token_str.as_deref())
})
.await
.map_err(|join| RepoError::PushFailed {
remote: String::new(),
refspec: String::new(),
cause: format!("spawn_blocking join: {join}"),
})?
}
}
fn run_push(
workdir: &Path,
remote: &str,
refspec: &str,
token: Option<&str>,
) -> Result<(), RepoError> {
let mut cmd = Command::new("git");
if token.is_some() {
cmd.arg("-c").arg(format!("credential.helper={}", auth::CREDENTIAL_HELPER));
}
cmd.arg("push").arg(remote).arg(refspec).current_dir(workdir).env("GIT_TERMINAL_PROMPT", "0");
if let Some(t) = token {
cmd.env(auth::TOKEN_ENV, t);
}
let out = cmd.output().map_err(|e| RepoError::PushFailed {
remote: remote.to_string(),
refspec: refspec.to_string(),
cause: format!("spawn `git push`: {e}"),
})?;
if !out.status.success() {
return Err(RepoError::PushFailed {
remote: remote.to_string(),
refspec: refspec.to_string(),
cause: format!("git push: {}", String::from_utf8_lossy(&out.stderr).trim()),
});
}
Ok(())
}