use std::path::Path; use anyhow::Context; use command::run_monitor_command; use monitor_client::entities::{ all_logs_success, monitor_timestamp, to_monitor_name, update::Log, CloneArgs, LatestCommit, SystemCommand, }; use run_command::async_run_command; use tracing::instrument; #[tracing::instrument] pub async fn pull( path: &Path, branch: &Option, commit: &Option, on_pull: &Option, ) -> Vec { let branch = match branch { Some(branch) => branch.to_owned(), None => "main".to_string(), }; let command = format!("cd {} && git pull origin {branch}", path.display()); let pull_log = run_monitor_command("git pull", command).await; let mut logs = vec![pull_log]; if !logs[0].success { return logs; } if let Some(commit) = commit { let reset_log = run_monitor_command( "set commit", format!("cd {} && git reset --hard {commit}", path.display()), ) .await; logs.push(reset_log); } let commit_hash_log = get_commit_hash_log(path).await.unwrap_or(Log::simple( "latest commit", String::from("failed to get latest commit"), )); logs.push(commit_hash_log); if let Some(on_pull) = on_pull { if !on_pull.path.is_empty() && !on_pull.command.is_empty() { let path = path.join(&on_pull.path); let on_pull_log = run_monitor_command( "on pull", format!("cd {} && {}", path.display(), on_pull.command), ) .await; logs.push(on_pull_log); } } logs } #[tracing::instrument(skip(repo_dir, github_token))] pub async fn clone( clone_args: T, repo_dir: &Path, github_token: Option, ) -> anyhow::Result> where T: Into + std::fmt::Debug, { let CloneArgs { name, repo, branch, commit, on_clone, on_pull, .. } = clone_args.into(); let repo = repo.as_ref().context("build has no repo attached")?; let name = to_monitor_name(&name); let repo_dir = repo_dir.join(name); let mut logs = clone_inner(repo, &repo_dir, &branch, &commit, github_token) .await; if !all_logs_success(&logs) { tracing::warn!("repo at {repo_dir:?} failed to clone"); return Ok(logs); } tracing::info!("repo at {repo_dir:?} cloned with clone_inner"); let commit_hash_log = get_commit_hash_log(&repo_dir).await?; logs.push(commit_hash_log); if let Some(command) = on_clone { if !command.path.is_empty() && !command.command.is_empty() { let on_clone_path = repo_dir.join(&command.path); let on_clone_log = run_monitor_command( "on clone", format!( "cd {} && {}", on_clone_path.display(), command.command ), ) .await; tracing::info!( "run repo on_clone command | command: {} | cwd: {:?}", command.command, on_clone_path ); logs.push(on_clone_log); } } if let Some(command) = on_pull { if !command.path.is_empty() && !command.command.is_empty() { let on_pull_path = repo_dir.join(&command.path); let on_pull_log = run_monitor_command( "on pull", format!( "cd {} && {}", on_pull_path.display(), command.command ), ) .await; tracing::info!( "run repo on_pull command | command: {} | cwd: {:?}", command.command, on_pull_path ); logs.push(on_pull_log); } } Ok(logs) } #[tracing::instrument(skip(destination, access_token))] async fn clone_inner( repo: &str, destination: &Path, branch: &Option, commit: &Option, access_token: Option, ) -> Vec { let _ = std::fs::remove_dir_all(destination); let access_token_at = match &access_token { Some(token) => format!("{token}@"), None => String::new(), }; let branch = match branch { Some(branch) => format!(" -b {branch}"), None => String::new(), }; let repo_url = format!("https://{access_token_at}github.com/{repo}.git"); let command = format!("git clone {repo_url} {}{branch}", destination.display()); let start_ts = monitor_timestamp(); let output = async_run_command(&command).await; let success = output.success(); let (command, stderr) = if !access_token_at.is_empty() { // know that access token can't be none if access token non-empty let access_token = access_token.unwrap(); ( command.replace(&access_token, ""), output.stderr.replace(&access_token, ""), ) } else { (command, output.stderr) }; let mut logs = vec![Log { stage: "clone repo".to_string(), command, success, stdout: output.stdout, stderr, start_ts, end_ts: monitor_timestamp(), }]; if !logs[0].success { return logs; } if let Some(commit) = commit { let reset_log = run_monitor_command( "set commit", format!( "cd {} && git reset --hard {commit}", destination.display() ), ) .await; logs.push(reset_log); } logs } #[instrument(level = "debug")] pub async fn get_commit_hash_info( repo_dir: &Path, ) -> anyhow::Result { let command = format!("cd {} && git rev-parse --short HEAD && git rev-parse HEAD && git log -1 --pretty=%B", repo_dir.display()); let output = async_run_command(&command).await; let mut split = output.stdout.split('\n'); let (hash, _, message) = ( split .next() .context("failed to get short commit hash")? .to_string(), split.next().context("failed to get long commit hash")?, split .next() .context("failed to get commit message")? .to_string(), ); Ok(LatestCommit { hash, message }) } #[instrument(level = "debug")] pub async fn get_commit_hash_log( repo_dir: &Path, ) -> anyhow::Result { let start_ts = monitor_timestamp(); let command = format!("cd {} && git rev-parse --short HEAD && git rev-parse HEAD && git log -1 --pretty=%B", repo_dir.display()); let output = async_run_command(&command).await; let mut split = output.stdout.split('\n'); let (short, _, msg) = ( split.next().context("failed to get short commit hash")?, split.next().context("failed to get long commit hash")?, split.next().context("failed to get commit message")?, ); let log = Log { stage: "latest commit".into(), command, stdout: format!("hash: {short}\nmessage: {msg}"), stderr: String::new(), success: true, start_ts, end_ts: monitor_timestamp(), }; Ok(log) }