1use std::path::Path;
9use std::process::Command;
10
11use rtb_credentials::CredentialRef;
12
13use super::{auth, Repo, RepoError};
14
15#[derive(Debug, Default, Clone)]
20#[non_exhaustive]
21pub struct FetchOptions {
22 pub credential: Option<CredentialRef>,
25}
26
27impl FetchOptions {
28 #[must_use]
31 pub fn with_credential(mut self, cref: CredentialRef) -> Self {
32 self.credential = Some(cref);
33 self
34 }
35}
36
37impl Repo {
38 pub async fn fetch(&self, remote: &str, opts: FetchOptions) -> Result<(), RepoError> {
47 let workdir = self.path().to_path_buf();
48 let remote = remote.to_string();
49
50 let token = match opts.credential.as_ref() {
51 Some(cref) => Some(auth::resolve_default(cref).await?),
52 None => None,
53 };
54
55 tokio::task::spawn_blocking(move || {
56 let token_str = token.as_ref().map(auth::expose);
57 run_fetch(&workdir, &remote, token_str.as_deref())
58 })
59 .await
60 .map_err(|join| RepoError::FetchFailed {
61 remote: String::new(),
62 cause: format!("spawn_blocking join: {join}"),
63 })?
64 }
65}
66
67fn run_fetch(workdir: &Path, remote: &str, token: Option<&str>) -> Result<(), RepoError> {
68 let mut cmd = Command::new("git");
69 if token.is_some() {
70 cmd.arg("-c").arg(format!("credential.helper={}", auth::CREDENTIAL_HELPER));
71 }
72 cmd.arg("fetch").arg(remote).current_dir(workdir).env("GIT_TERMINAL_PROMPT", "0");
73 if let Some(t) = token {
74 cmd.env(auth::TOKEN_ENV, t);
75 }
76 let out = cmd.output().map_err(|e| RepoError::FetchFailed {
77 remote: remote.to_string(),
78 cause: format!("spawn `git fetch`: {e}"),
79 })?;
80 if !out.status.success() {
81 return Err(RepoError::FetchFailed {
82 remote: remote.to_string(),
83 cause: format!("git fetch: {}", String::from_utf8_lossy(&out.stderr).trim()),
84 });
85 }
86 Ok(())
87}