1use std::path::Path;
14use std::process::Command;
15
16use rtb_credentials::CredentialRef;
17
18use super::{auth, Repo, RepoError};
19
20#[derive(Debug, Default, Clone)]
22#[non_exhaustive]
23pub struct PushOptions {
24 pub credential: Option<CredentialRef>,
27}
28
29impl PushOptions {
30 #[must_use]
33 pub fn with_credential(mut self, cref: CredentialRef) -> Self {
34 self.credential = Some(cref);
35 self
36 }
37}
38
39impl Repo {
40 pub async fn push(
49 &self,
50 remote: &str,
51 refspec: &str,
52 opts: PushOptions,
53 ) -> Result<(), RepoError> {
54 let workdir = self.path().to_path_buf();
55 let remote = remote.to_string();
56 let refspec = refspec.to_string();
57
58 let token = match opts.credential.as_ref() {
59 Some(cref) => Some(auth::resolve_default(cref).await?),
60 None => None,
61 };
62
63 tokio::task::spawn_blocking(move || {
64 let token_str = token.as_ref().map(auth::expose);
65 run_push(&workdir, &remote, &refspec, token_str.as_deref())
66 })
67 .await
68 .map_err(|join| RepoError::PushFailed {
69 remote: String::new(),
70 refspec: String::new(),
71 cause: format!("spawn_blocking join: {join}"),
72 })?
73 }
74}
75
76fn run_push(
77 workdir: &Path,
78 remote: &str,
79 refspec: &str,
80 token: Option<&str>,
81) -> Result<(), RepoError> {
82 let mut cmd = Command::new("git");
83 if token.is_some() {
84 cmd.arg("-c").arg(format!("credential.helper={}", auth::CREDENTIAL_HELPER));
85 }
86 cmd.arg("push").arg(remote).arg(refspec).current_dir(workdir).env("GIT_TERMINAL_PROMPT", "0");
87 if let Some(t) = token {
88 cmd.env(auth::TOKEN_ENV, t);
89 }
90 let out = cmd.output().map_err(|e| RepoError::PushFailed {
91 remote: remote.to_string(),
92 refspec: refspec.to_string(),
93 cause: format!("spawn `git push`: {e}"),
94 })?;
95 if !out.status.success() {
96 return Err(RepoError::PushFailed {
97 remote: remote.to_string(),
98 refspec: refspec.to_string(),
99 cause: format!("git push: {}", String::from_utf8_lossy(&out.stderr).trim()),
100 });
101 }
102 Ok(())
103}