xref: /DADK/dadk-user/src/executor/source.rs (revision d2ade6ef037f43d0342021730a0d8b3af7bc36c1)
173779f3dSLoGin use log::info;
273779f3dSLoGin use regex::Regex;
373779f3dSLoGin use reqwest::Url;
473779f3dSLoGin use serde::{Deserialize, Serialize};
573779f3dSLoGin use std::os::unix::fs::PermissionsExt;
673779f3dSLoGin use std::{
773779f3dSLoGin     fs::File,
873779f3dSLoGin     path::PathBuf,
973779f3dSLoGin     process::{Command, Stdio},
1073779f3dSLoGin };
1173779f3dSLoGin use zip::ZipArchive;
1273779f3dSLoGin 
1373779f3dSLoGin use crate::utils::{file::FileUtils, stdio::StdioUtils};
1473779f3dSLoGin 
1573779f3dSLoGin use super::cache::CacheDir;
1673779f3dSLoGin 
17*d2ade6efSJomo use anyhow::{Error, Result};
18*d2ade6efSJomo 
1973779f3dSLoGin /// # Git源
2073779f3dSLoGin ///
2173779f3dSLoGin /// 从Git仓库获取源码
2273779f3dSLoGin #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2373779f3dSLoGin pub struct GitSource {
2473779f3dSLoGin     /// Git仓库地址
2573779f3dSLoGin     url: String,
2673779f3dSLoGin     /// 分支(可选,如果为空,则拉取master)branch和revision只能同时指定一个
2773779f3dSLoGin     branch: Option<String>,
2873779f3dSLoGin     /// 特定的提交的hash值(可选,如果为空,则拉取branch的最新提交)
2973779f3dSLoGin     revision: Option<String>,
3073779f3dSLoGin }
new(url: String, branch: Option<String>, revision: Option<String>) -> Self3173779f3dSLoGin 
3273779f3dSLoGin impl GitSource {
3373779f3dSLoGin     pub fn new(url: String, branch: Option<String>, revision: Option<String>) -> Self {
3473779f3dSLoGin         Self {
3573779f3dSLoGin             url,
3673779f3dSLoGin             branch,
3773779f3dSLoGin             revision,
3873779f3dSLoGin         }
3973779f3dSLoGin     }
4073779f3dSLoGin     /// # 验证参数合法性
4173779f3dSLoGin     ///
4273779f3dSLoGin     /// 仅进行形式校验,不会检查Git仓库是否存在,以及分支是否存在、是否有权限访问等
43*d2ade6efSJomo     pub fn validate(&mut self) -> Result<()> {
4473779f3dSLoGin         if self.url.is_empty() {
45*d2ade6efSJomo             return Err(Error::msg("url is empty"));
4673779f3dSLoGin         }
4773779f3dSLoGin         // branch和revision不能同时为空
4873779f3dSLoGin         if self.branch.is_none() && self.revision.is_none() {
4973779f3dSLoGin             self.branch = Some("master".to_string());
5073779f3dSLoGin         }
5173779f3dSLoGin         // branch和revision只能同时指定一个
5273779f3dSLoGin         if self.branch.is_some() && self.revision.is_some() {
53*d2ade6efSJomo             return Err(Error::msg("branch and revision are both specified"));
5473779f3dSLoGin         }
5573779f3dSLoGin 
5673779f3dSLoGin         if self.branch.is_some() {
5773779f3dSLoGin             if self.branch.as_ref().unwrap().is_empty() {
58*d2ade6efSJomo                 return Err(Error::msg("branch is empty"));
5973779f3dSLoGin             }
6073779f3dSLoGin         }
6173779f3dSLoGin         if self.revision.is_some() {
6273779f3dSLoGin             if self.revision.as_ref().unwrap().is_empty() {
63*d2ade6efSJomo                 return Err(Error::msg("revision is empty"));
6473779f3dSLoGin             }
6573779f3dSLoGin         }
6673779f3dSLoGin         return Ok(());
6773779f3dSLoGin     }
6873779f3dSLoGin 
6973779f3dSLoGin     pub fn trim(&mut self) {
7073779f3dSLoGin         self.url = self.url.trim().to_string();
7173779f3dSLoGin         if let Some(branch) = &mut self.branch {
7273779f3dSLoGin             *branch = branch.trim().to_string();
7373779f3dSLoGin         }
7473779f3dSLoGin 
7573779f3dSLoGin         if let Some(revision) = &mut self.revision {
7673779f3dSLoGin             *revision = revision.trim().to_string();
7773779f3dSLoGin         }
7873779f3dSLoGin     }
7973779f3dSLoGin 
8073779f3dSLoGin     /// # 确保Git仓库已经克隆到指定目录,并且切换到指定分支/Revision
8173779f3dSLoGin     ///
8273779f3dSLoGin     /// 如果目录不存在,则会自动创建
8373779f3dSLoGin     ///
8473779f3dSLoGin     /// ## 参数
8573779f3dSLoGin     ///
8673779f3dSLoGin     /// - `target_dir` - 目标目录
8773779f3dSLoGin     ///
8873779f3dSLoGin     /// ## 返回
8973779f3dSLoGin     ///
9073779f3dSLoGin     /// - `Ok(())` - 成功
9173779f3dSLoGin     /// - `Err(String)` - 失败,错误信息
9273779f3dSLoGin     pub fn prepare(&self, target_dir: &CacheDir) -> Result<(), String> {
9373779f3dSLoGin         info!(
9473779f3dSLoGin             "Preparing git repo: {}, branch: {:?}, revision: {:?}",
9573779f3dSLoGin             self.url, self.branch, self.revision
9673779f3dSLoGin         );
9773779f3dSLoGin 
9873779f3dSLoGin         target_dir.create().map_err(|e| {
9973779f3dSLoGin             format!(
10073779f3dSLoGin                 "Failed to create target dir: {}, message: {e:?}",
10173779f3dSLoGin                 target_dir.path.display()
10273779f3dSLoGin             )
10373779f3dSLoGin         })?;
10473779f3dSLoGin 
10573779f3dSLoGin         if target_dir.is_empty().map_err(|e| {
10673779f3dSLoGin             format!(
10773779f3dSLoGin                 "Failed to check if target dir is empty: {}, message: {e:?}",
10873779f3dSLoGin                 target_dir.path.display()
10973779f3dSLoGin             )
11073779f3dSLoGin         })? {
11173779f3dSLoGin             info!("Target dir is empty, cloning repo");
11273779f3dSLoGin             self.clone_repo(target_dir)?;
11373779f3dSLoGin         }
11473779f3dSLoGin 
11573779f3dSLoGin         self.checkout(target_dir)?;
11673779f3dSLoGin 
11773779f3dSLoGin         self.pull(target_dir)?;
11873779f3dSLoGin 
11973779f3dSLoGin         return Ok(());
12073779f3dSLoGin     }
12173779f3dSLoGin 
12273779f3dSLoGin     fn check_repo(&self, target_dir: &CacheDir) -> Result<bool, String> {
12373779f3dSLoGin         let path: &PathBuf = &target_dir.path;
12473779f3dSLoGin         let mut cmd = Command::new("git");
12573779f3dSLoGin         cmd.arg("remote").arg("get-url").arg("origin");
12673779f3dSLoGin 
12773779f3dSLoGin         // 设置工作目录
12873779f3dSLoGin         cmd.current_dir(path);
12973779f3dSLoGin 
13073779f3dSLoGin         // 创建子进程,执行命令
13173779f3dSLoGin         let proc: std::process::Child = cmd
13273779f3dSLoGin             .stderr(Stdio::piped())
13373779f3dSLoGin             .stdout(Stdio::piped())
13473779f3dSLoGin             .spawn()
13573779f3dSLoGin             .map_err(|e| e.to_string())?;
13673779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
13773779f3dSLoGin 
13873779f3dSLoGin         if output.status.success() {
13973779f3dSLoGin             let mut r = String::from_utf8(output.stdout).unwrap();
14073779f3dSLoGin             r.pop();
14173779f3dSLoGin             Ok(r == self.url)
14273779f3dSLoGin         } else {
14373779f3dSLoGin             return Err(format!(
14473779f3dSLoGin                 "git remote get-url origin failed, status: {:?},  stderr: {:?}",
14573779f3dSLoGin                 output.status,
14673779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
14773779f3dSLoGin             ));
14873779f3dSLoGin         }
14973779f3dSLoGin     }
15073779f3dSLoGin 
15173779f3dSLoGin     fn set_url(&self, target_dir: &CacheDir) -> Result<(), String> {
15273779f3dSLoGin         let path: &PathBuf = &target_dir.path;
15373779f3dSLoGin         let mut cmd = Command::new("git");
15473779f3dSLoGin         cmd.arg("remote")
15573779f3dSLoGin             .arg("set-url")
15673779f3dSLoGin             .arg("origin")
15773779f3dSLoGin             .arg(self.url.as_str());
15873779f3dSLoGin 
15973779f3dSLoGin         // 设置工作目录
16073779f3dSLoGin         cmd.current_dir(path);
16173779f3dSLoGin 
16273779f3dSLoGin         // 创建子进程,执行命令
16373779f3dSLoGin         let proc: std::process::Child = cmd
16473779f3dSLoGin             .stderr(Stdio::piped())
16573779f3dSLoGin             .spawn()
16673779f3dSLoGin             .map_err(|e| e.to_string())?;
16773779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
16873779f3dSLoGin 
16973779f3dSLoGin         if !output.status.success() {
17073779f3dSLoGin             return Err(format!(
17173779f3dSLoGin                 "git remote set-url origin failed, status: {:?},  stderr: {:?}",
17273779f3dSLoGin                 output.status,
17373779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
17473779f3dSLoGin             ));
17573779f3dSLoGin         }
17673779f3dSLoGin         Ok(())
17773779f3dSLoGin     }
17873779f3dSLoGin 
17973779f3dSLoGin     fn checkout(&self, target_dir: &CacheDir) -> Result<(), String> {
18073779f3dSLoGin         // 确保目标目录中的仓库为所指定仓库
18173779f3dSLoGin         if !self.check_repo(target_dir).map_err(|e| {
18273779f3dSLoGin             format!(
18373779f3dSLoGin                 "Failed to check repo: {}, message: {e:?}",
18473779f3dSLoGin                 target_dir.path.display()
18573779f3dSLoGin             )
18673779f3dSLoGin         })? {
18773779f3dSLoGin             info!("Target dir isn't specified repo, change remote url");
18873779f3dSLoGin             self.set_url(target_dir)?;
18973779f3dSLoGin         }
19073779f3dSLoGin 
19173779f3dSLoGin         let do_checkout = || -> Result<(), String> {
19273779f3dSLoGin             let mut cmd = Command::new("git");
19373779f3dSLoGin             cmd.current_dir(&target_dir.path);
19473779f3dSLoGin             cmd.arg("checkout");
19573779f3dSLoGin 
19673779f3dSLoGin             if let Some(branch) = &self.branch {
19773779f3dSLoGin                 cmd.arg(branch);
19873779f3dSLoGin             }
19973779f3dSLoGin             if let Some(revision) = &self.revision {
20073779f3dSLoGin                 cmd.arg(revision);
20173779f3dSLoGin             }
20273779f3dSLoGin 
20373779f3dSLoGin             // 强制切换分支,且安静模式
20473779f3dSLoGin             cmd.arg("-f").arg("-q");
20573779f3dSLoGin 
20673779f3dSLoGin             // 创建子进程,执行命令
20773779f3dSLoGin             let proc: std::process::Child = cmd
20873779f3dSLoGin                 .stderr(Stdio::piped())
20973779f3dSLoGin                 .spawn()
21073779f3dSLoGin                 .map_err(|e| e.to_string())?;
21173779f3dSLoGin             let output = proc.wait_with_output().map_err(|e| e.to_string())?;
21273779f3dSLoGin 
21373779f3dSLoGin             if !output.status.success() {
21473779f3dSLoGin                 return Err(format!(
21573779f3dSLoGin                     "Failed to checkout {}, message: {}",
21673779f3dSLoGin                     target_dir.path.display(),
21773779f3dSLoGin                     String::from_utf8_lossy(&output.stdout)
21873779f3dSLoGin                 ));
21973779f3dSLoGin             }
22073779f3dSLoGin 
22173779f3dSLoGin             let mut subcmd = Command::new("git");
22273779f3dSLoGin             subcmd.current_dir(&target_dir.path);
22373779f3dSLoGin             subcmd.arg("submodule").arg("update").arg("--remote");
22473779f3dSLoGin 
22573779f3dSLoGin             //当checkout仓库的子进程结束后,启动checkout子模块的子进程
22673779f3dSLoGin             let subproc: std::process::Child = subcmd
22773779f3dSLoGin                 .stderr(Stdio::piped())
22873779f3dSLoGin                 .spawn()
22973779f3dSLoGin                 .map_err(|e| e.to_string())?;
23073779f3dSLoGin             let suboutput = subproc.wait_with_output().map_err(|e| e.to_string())?;
23173779f3dSLoGin 
23273779f3dSLoGin             if !suboutput.status.success() {
23373779f3dSLoGin                 return Err(format!(
23473779f3dSLoGin                     "Failed to checkout submodule {}, message: {}",
23573779f3dSLoGin                     target_dir.path.display(),
23673779f3dSLoGin                     String::from_utf8_lossy(&suboutput.stdout)
23773779f3dSLoGin                 ));
23873779f3dSLoGin             }
23973779f3dSLoGin             return Ok(());
24073779f3dSLoGin         };
24173779f3dSLoGin 
24273779f3dSLoGin         if let Err(_) = do_checkout() {
24373779f3dSLoGin             // 如果切换分支失败,则尝试重新fetch
24473779f3dSLoGin             if self.revision.is_some() {
24573779f3dSLoGin                 self.set_fetch_config(target_dir)?;
24673779f3dSLoGin                 self.unshallow(target_dir)?
24773779f3dSLoGin             };
24873779f3dSLoGin 
24973779f3dSLoGin             self.fetch_all(target_dir).ok();
25073779f3dSLoGin             do_checkout()?;
25173779f3dSLoGin         }
25273779f3dSLoGin 
25373779f3dSLoGin         return Ok(());
25473779f3dSLoGin     }
25573779f3dSLoGin 
25673779f3dSLoGin     pub fn clone_repo(&self, cache_dir: &CacheDir) -> Result<(), String> {
25773779f3dSLoGin         let path: &PathBuf = &cache_dir.path;
25873779f3dSLoGin         let mut cmd = Command::new("git");
25973779f3dSLoGin         cmd.arg("clone").arg(&self.url).arg(".").arg("--recursive");
26073779f3dSLoGin 
26173779f3dSLoGin         if let Some(branch) = &self.branch {
26273779f3dSLoGin             cmd.arg("--branch").arg(branch).arg("--depth").arg("1");
26373779f3dSLoGin         }
26473779f3dSLoGin 
26573779f3dSLoGin         // 对于克隆,如果指定了revision,则直接克隆整个仓库,稍后再切换到指定的revision
26673779f3dSLoGin 
26773779f3dSLoGin         // 设置工作目录
26873779f3dSLoGin         cmd.current_dir(path);
26973779f3dSLoGin 
27073779f3dSLoGin         // 创建子进程,执行命令
27173779f3dSLoGin         let proc: std::process::Child = cmd
27273779f3dSLoGin             .stderr(Stdio::piped())
27373779f3dSLoGin             .stdout(Stdio::inherit())
27473779f3dSLoGin             .spawn()
27573779f3dSLoGin             .map_err(|e| e.to_string())?;
27673779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
27773779f3dSLoGin 
27873779f3dSLoGin         if !output.status.success() {
27973779f3dSLoGin             return Err(format!(
28073779f3dSLoGin                 "clone git repo failed, status: {:?},  stderr: {:?}",
28173779f3dSLoGin                 output.status,
28273779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
28373779f3dSLoGin             ));
28473779f3dSLoGin         }
28573779f3dSLoGin 
28673779f3dSLoGin         let mut subcmd = Command::new("git");
28773779f3dSLoGin         subcmd
28873779f3dSLoGin             .arg("submodule")
28973779f3dSLoGin             .arg("update")
29073779f3dSLoGin             .arg("--init")
29173779f3dSLoGin             .arg("--recursive")
29273779f3dSLoGin             .arg("--force");
29373779f3dSLoGin 
29473779f3dSLoGin         subcmd.current_dir(path);
29573779f3dSLoGin 
29673779f3dSLoGin         //当克隆仓库的子进程结束后,启动保证克隆子模块的子进程
29773779f3dSLoGin         let subproc: std::process::Child = subcmd
29873779f3dSLoGin             .stderr(Stdio::piped())
29973779f3dSLoGin             .stdout(Stdio::inherit())
30073779f3dSLoGin             .spawn()
30173779f3dSLoGin             .map_err(|e| e.to_string())?;
30273779f3dSLoGin         let suboutput = subproc.wait_with_output().map_err(|e| e.to_string())?;
30373779f3dSLoGin 
30473779f3dSLoGin         if !suboutput.status.success() {
30573779f3dSLoGin             return Err(format!(
30673779f3dSLoGin                 "clone submodule failed, status: {:?},  stderr: {:?}",
30773779f3dSLoGin                 suboutput.status,
30873779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&suboutput.stderr), 5)
30973779f3dSLoGin             ));
31073779f3dSLoGin         }
31173779f3dSLoGin         return Ok(());
31273779f3dSLoGin     }
31373779f3dSLoGin 
31473779f3dSLoGin     /// 设置fetch所有分支
31573779f3dSLoGin     fn set_fetch_config(&self, target_dir: &CacheDir) -> Result<(), String> {
31673779f3dSLoGin         let mut cmd = Command::new("git");
31773779f3dSLoGin         cmd.current_dir(&target_dir.path);
31873779f3dSLoGin         cmd.arg("config")
31973779f3dSLoGin             .arg("remote.origin.fetch")
32073779f3dSLoGin             .arg("+refs/heads/*:refs/remotes/origin/*");
32173779f3dSLoGin 
32273779f3dSLoGin         // 创建子进程,执行命令
32373779f3dSLoGin         let proc: std::process::Child = cmd
32473779f3dSLoGin             .stderr(Stdio::piped())
32573779f3dSLoGin             .spawn()
32673779f3dSLoGin             .map_err(|e| e.to_string())?;
32773779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
32873779f3dSLoGin 
32973779f3dSLoGin         if !output.status.success() {
33073779f3dSLoGin             return Err(format!(
33173779f3dSLoGin                 "Failed to set fetch config {}, message: {}",
33273779f3dSLoGin                 target_dir.path.display(),
33373779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
33473779f3dSLoGin             ));
33573779f3dSLoGin         }
33673779f3dSLoGin         return Ok(());
33773779f3dSLoGin     }
33873779f3dSLoGin     /// # 把浅克隆的仓库变成深克隆
33973779f3dSLoGin     fn unshallow(&self, target_dir: &CacheDir) -> Result<(), String> {
34073779f3dSLoGin         if self.is_shallow(target_dir)? == false {
34173779f3dSLoGin             return Ok(());
34273779f3dSLoGin         }
34373779f3dSLoGin 
34473779f3dSLoGin         let mut cmd = Command::new("git");
34573779f3dSLoGin         cmd.current_dir(&target_dir.path);
34673779f3dSLoGin         cmd.arg("fetch").arg("--unshallow");
34773779f3dSLoGin 
34873779f3dSLoGin         cmd.arg("-f");
34973779f3dSLoGin 
35073779f3dSLoGin         // 创建子进程,执行命令
35173779f3dSLoGin         let proc: std::process::Child = cmd
35273779f3dSLoGin             .stderr(Stdio::piped())
35373779f3dSLoGin             .spawn()
35473779f3dSLoGin             .map_err(|e| e.to_string())?;
35573779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
35673779f3dSLoGin 
35773779f3dSLoGin         if !output.status.success() {
35873779f3dSLoGin             return Err(format!(
35973779f3dSLoGin                 "Failed to unshallow {}, message: {}",
36073779f3dSLoGin                 target_dir.path.display(),
36173779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
36273779f3dSLoGin             ));
36373779f3dSLoGin         }
36473779f3dSLoGin         return Ok(());
36573779f3dSLoGin     }
36673779f3dSLoGin 
36773779f3dSLoGin     /// 判断当前仓库是否是浅克隆
36873779f3dSLoGin     fn is_shallow(&self, target_dir: &CacheDir) -> Result<bool, String> {
36973779f3dSLoGin         let mut cmd = Command::new("git");
37073779f3dSLoGin         cmd.current_dir(&target_dir.path);
37173779f3dSLoGin         cmd.arg("rev-parse").arg("--is-shallow-repository");
37273779f3dSLoGin 
37373779f3dSLoGin         let proc: std::process::Child = cmd
37473779f3dSLoGin             .stderr(Stdio::piped())
37573779f3dSLoGin             .spawn()
37673779f3dSLoGin             .map_err(|e| e.to_string())?;
37773779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
37873779f3dSLoGin 
37973779f3dSLoGin         if !output.status.success() {
38073779f3dSLoGin             return Err(format!(
38173779f3dSLoGin                 "Failed to check if shallow {}, message: {}",
38273779f3dSLoGin                 target_dir.path.display(),
38373779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
38473779f3dSLoGin             ));
38573779f3dSLoGin         }
38673779f3dSLoGin 
38773779f3dSLoGin         let is_shallow = String::from_utf8_lossy(&output.stdout).trim() == "true";
38873779f3dSLoGin         return Ok(is_shallow);
38973779f3dSLoGin     }
39073779f3dSLoGin 
39173779f3dSLoGin     fn fetch_all(&self, target_dir: &CacheDir) -> Result<(), String> {
39273779f3dSLoGin         self.set_fetch_config(target_dir)?;
39373779f3dSLoGin         let mut cmd = Command::new("git");
39473779f3dSLoGin         cmd.current_dir(&target_dir.path);
39573779f3dSLoGin         cmd.arg("fetch").arg("--all");
39673779f3dSLoGin 
39773779f3dSLoGin         // 安静模式
39873779f3dSLoGin         cmd.arg("-f").arg("-q");
39973779f3dSLoGin 
40073779f3dSLoGin         // 创建子进程,执行命令
40173779f3dSLoGin         let proc: std::process::Child = cmd
40273779f3dSLoGin             .stderr(Stdio::piped())
40373779f3dSLoGin             .spawn()
40473779f3dSLoGin             .map_err(|e| e.to_string())?;
40573779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
40673779f3dSLoGin 
40773779f3dSLoGin         if !output.status.success() {
40873779f3dSLoGin             return Err(format!(
40973779f3dSLoGin                 "Failed to fetch all {}, message: {}",
41073779f3dSLoGin                 target_dir.path.display(),
41173779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
41273779f3dSLoGin             ));
41373779f3dSLoGin         }
41473779f3dSLoGin 
41573779f3dSLoGin         return Ok(());
41673779f3dSLoGin     }
41773779f3dSLoGin 
41873779f3dSLoGin     fn pull(&self, target_dir: &CacheDir) -> Result<(), String> {
41973779f3dSLoGin         // 如果没有指定branch,则不执行pull
42073779f3dSLoGin         if !self.branch.is_some() {
42173779f3dSLoGin             return Ok(());
42273779f3dSLoGin         }
42373779f3dSLoGin         info!("git pulling: {}", target_dir.path.display());
42473779f3dSLoGin 
42573779f3dSLoGin         let mut cmd = Command::new("git");
42673779f3dSLoGin         cmd.current_dir(&target_dir.path);
42773779f3dSLoGin         cmd.arg("pull");
42873779f3dSLoGin 
42973779f3dSLoGin         // 安静模式
43073779f3dSLoGin         cmd.arg("-f").arg("-q");
43173779f3dSLoGin 
43273779f3dSLoGin         // 创建子进程,执行命令
43373779f3dSLoGin         let proc: std::process::Child = cmd
43473779f3dSLoGin             .stderr(Stdio::piped())
43573779f3dSLoGin             .spawn()
43673779f3dSLoGin             .map_err(|e| e.to_string())?;
43773779f3dSLoGin         let output = proc.wait_with_output().map_err(|e| e.to_string())?;
43873779f3dSLoGin 
43973779f3dSLoGin         // 如果pull失败,且指定了branch,则报错
44073779f3dSLoGin         if !output.status.success() {
44173779f3dSLoGin             return Err(format!(
44273779f3dSLoGin                 "Failed to pull {}, message: {}",
44373779f3dSLoGin                 target_dir.path.display(),
44473779f3dSLoGin                 StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
44573779f3dSLoGin             ));
44673779f3dSLoGin         }
44773779f3dSLoGin 
44873779f3dSLoGin         return Ok(());
44973779f3dSLoGin     }
45073779f3dSLoGin }
45173779f3dSLoGin 
45273779f3dSLoGin /// # 本地源
45373779f3dSLoGin #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45473779f3dSLoGin pub struct LocalSource {
45573779f3dSLoGin     /// 本地目录/文件的路径
45673779f3dSLoGin     path: PathBuf,
45773779f3dSLoGin }
45873779f3dSLoGin 
new(path: PathBuf) -> Self45973779f3dSLoGin impl LocalSource {
46073779f3dSLoGin     #[allow(dead_code)]
46173779f3dSLoGin     pub fn new(path: PathBuf) -> Self {
46273779f3dSLoGin         Self { path }
46373779f3dSLoGin     }
46473779f3dSLoGin 
465*d2ade6efSJomo     pub fn validate(&self, expect_file: Option<bool>) -> Result<()> {
46673779f3dSLoGin         if !self.path.exists() {
467*d2ade6efSJomo             return Err(Error::msg(format!("path {:?} not exists", self.path)));
46873779f3dSLoGin         }
46973779f3dSLoGin 
47073779f3dSLoGin         if let Some(expect_file) = expect_file {
47173779f3dSLoGin             if expect_file && !self.path.is_file() {
472*d2ade6efSJomo                 return Err(Error::msg(format!("path {:?} is not a file", self.path)));
47373779f3dSLoGin             }
47473779f3dSLoGin 
47573779f3dSLoGin             if !expect_file && !self.path.is_dir() {
476*d2ade6efSJomo                 return Err(Error::msg(format!(
477*d2ade6efSJomo                     "path {:?} is not a directory",
478*d2ade6efSJomo                     self.path
479*d2ade6efSJomo                 )));
48073779f3dSLoGin             }
48173779f3dSLoGin         }
48273779f3dSLoGin 
48373779f3dSLoGin         return Ok(());
48473779f3dSLoGin     }
48573779f3dSLoGin 
48673779f3dSLoGin     pub fn trim(&mut self) {}
48773779f3dSLoGin 
48873779f3dSLoGin     pub fn path(&self) -> &PathBuf {
48973779f3dSLoGin         &self.path
49073779f3dSLoGin     }
49173779f3dSLoGin }
49273779f3dSLoGin 
49373779f3dSLoGin /// # 在线压缩包源
49473779f3dSLoGin #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49573779f3dSLoGin pub struct ArchiveSource {
49673779f3dSLoGin     /// 压缩包的URL
new(url: String) -> Self49773779f3dSLoGin     url: String,
49873779f3dSLoGin }
49973779f3dSLoGin 
50073779f3dSLoGin impl ArchiveSource {
50173779f3dSLoGin     #[allow(dead_code)]
50273779f3dSLoGin     pub fn new(url: String) -> Self {
50373779f3dSLoGin         Self { url }
50473779f3dSLoGin     }
505*d2ade6efSJomo     pub fn validate(&self) -> Result<()> {
50673779f3dSLoGin         if self.url.is_empty() {
507*d2ade6efSJomo             return Err(Error::msg("url is empty"));
50873779f3dSLoGin         }
50973779f3dSLoGin 
51073779f3dSLoGin         // 判断是一个网址
51173779f3dSLoGin         if let Ok(url) = Url::parse(&self.url) {
51273779f3dSLoGin             if url.scheme() != "http" && url.scheme() != "https" {
513*d2ade6efSJomo                 return Err(Error::msg(format!(
514*d2ade6efSJomo                     "url {:?} is not a http/https url",
515*d2ade6efSJomo                     self.url
516*d2ade6efSJomo                 )));
51773779f3dSLoGin             }
51873779f3dSLoGin         } else {
519*d2ade6efSJomo             return Err(Error::msg(format!("url {:?} is not a valid url", self.url)));
52073779f3dSLoGin         }
52173779f3dSLoGin         return Ok(());
52273779f3dSLoGin     }
52373779f3dSLoGin 
52473779f3dSLoGin     pub fn trim(&mut self) {
52573779f3dSLoGin         self.url = self.url.trim().to_string();
52673779f3dSLoGin     }
52773779f3dSLoGin 
52873779f3dSLoGin     /// @brief 下载压缩包并把其中的文件提取至target_dir目录下
52973779f3dSLoGin     ///
53073779f3dSLoGin     ///从URL中下载压缩包到临时文件夹 target_dir/DRAGONOS_ARCHIVE_TEMP
53173779f3dSLoGin     ///原地解压,提取文件后删除下载的压缩包。如果 target_dir 非空,就直接使用
53273779f3dSLoGin     ///其中内容,不进行重复下载和覆盖
53373779f3dSLoGin     ///
53473779f3dSLoGin     /// @param target_dir 文件缓存目录
53573779f3dSLoGin     ///
53673779f3dSLoGin     /// @return 根据结果返回OK或Err
53773779f3dSLoGin     pub fn download_unzip(&self, target_dir: &CacheDir) -> Result<(), String> {
53873779f3dSLoGin         let url = Url::parse(&self.url).unwrap();
53973779f3dSLoGin         let archive_name = url.path_segments().unwrap().last().unwrap();
54073779f3dSLoGin         let path = &(target_dir.path.join("DRAGONOS_ARCHIVE_TEMP"));
54173779f3dSLoGin         //如果source目录没有临时文件夹,且不为空,说明之前成功执行过一次,那么就直接使用之前的缓存
54273779f3dSLoGin         if !path.exists()
54373779f3dSLoGin             && !target_dir.is_empty().map_err(|e| {
54473779f3dSLoGin                 format!(
54573779f3dSLoGin                     "Failed to check if target dir is empty: {}, message: {e:?}",
54673779f3dSLoGin                     target_dir.path.display()
54773779f3dSLoGin                 )
54873779f3dSLoGin             })?
54973779f3dSLoGin         {
55073779f3dSLoGin             //如果source文件夹非空,就直接使用,不再重复下载压缩文件,这里可以考虑加入交互
55173779f3dSLoGin             info!("Source files already exist. Using previous source file cache. You should clean {:?} before re-download the archive ", target_dir.path);
55273779f3dSLoGin             return Ok(());
55373779f3dSLoGin         }
55473779f3dSLoGin 
55573779f3dSLoGin         if path.exists() {
55673779f3dSLoGin             std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
55773779f3dSLoGin         }
55873779f3dSLoGin         //创建临时目录
55973779f3dSLoGin         std::fs::create_dir(path).map_err(|e| e.to_string())?;
56073779f3dSLoGin         info!("downloading {:?}", archive_name);
56173779f3dSLoGin         FileUtils::download_file(&self.url, path).map_err(|e| e.to_string())?;
56273779f3dSLoGin         //下载成功,开始尝试解压
56373779f3dSLoGin         info!("download {:?} finished, start unzip", archive_name);
56473779f3dSLoGin         let archive_file = ArchiveFile::new(&path.join(archive_name));
56573779f3dSLoGin         archive_file.unzip()?;
56673779f3dSLoGin         //删除创建的临时文件夹
56773779f3dSLoGin         std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
56873779f3dSLoGin         return Ok(());
56973779f3dSLoGin     }
57073779f3dSLoGin }
new(archive_path: &PathBuf) -> Self57173779f3dSLoGin 
57273779f3dSLoGin pub struct ArchiveFile {
57373779f3dSLoGin     archive_path: PathBuf,
57473779f3dSLoGin     archive_name: String,
57573779f3dSLoGin     archive_type: ArchiveType,
57673779f3dSLoGin }
57773779f3dSLoGin 
57873779f3dSLoGin impl ArchiveFile {
57973779f3dSLoGin     pub fn new(archive_path: &PathBuf) -> Self {
58073779f3dSLoGin         info!("archive_path: {:?}", archive_path);
58173779f3dSLoGin         //匹配压缩文件类型
58273779f3dSLoGin         let archive_name = archive_path.file_name().unwrap().to_str().unwrap();
58373779f3dSLoGin         for (regex, archivetype) in [
58473779f3dSLoGin             (Regex::new(r"^(.+)\.tar\.gz$").unwrap(), ArchiveType::TarGz),
58573779f3dSLoGin             (Regex::new(r"^(.+)\.tar\.xz$").unwrap(), ArchiveType::TarXz),
58673779f3dSLoGin             (Regex::new(r"^(.+)\.zip$").unwrap(), ArchiveType::Zip),
58773779f3dSLoGin         ] {
58873779f3dSLoGin             if regex.is_match(archive_name) {
58973779f3dSLoGin                 return Self {
59073779f3dSLoGin                     archive_path: archive_path.parent().unwrap().to_path_buf(),
59173779f3dSLoGin                     archive_name: archive_name.to_string(),
59273779f3dSLoGin                     archive_type: archivetype,
59373779f3dSLoGin                 };
59473779f3dSLoGin             }
59573779f3dSLoGin         }
59673779f3dSLoGin         Self {
59773779f3dSLoGin             archive_path: archive_path.parent().unwrap().to_path_buf(),
59873779f3dSLoGin             archive_name: archive_name.to_string(),
59973779f3dSLoGin             archive_type: ArchiveType::Undefined,
60073779f3dSLoGin         }
60173779f3dSLoGin     }
60273779f3dSLoGin 
60373779f3dSLoGin     /// @brief 对self.archive_path路径下名为self.archive_name的压缩文件(tar.gz或zip)进行解压缩
unzip(&self) -> Result<(), String>60473779f3dSLoGin     ///
60573779f3dSLoGin     /// 在此函数中进行路径和文件名有效性的判断,如果有效的话就开始解压缩,根据ArchiveType枚举类型来
60673779f3dSLoGin     /// 生成不同的命令来对压缩文件进行解压缩,暂时只支持tar.gz和zip格式,并且都是通过调用bash来解压缩
60773779f3dSLoGin     /// 没有引入第三方rust库
60873779f3dSLoGin     ///
60973779f3dSLoGin     ///
61073779f3dSLoGin     /// @return 根据结果返回OK或Err
61173779f3dSLoGin 
61273779f3dSLoGin     pub fn unzip(&self) -> Result<(), String> {
61373779f3dSLoGin         let path = &self.archive_path;
61473779f3dSLoGin         if !path.is_dir() {
61573779f3dSLoGin             return Err(format!("Archive directory {:?} is wrong", path));
61673779f3dSLoGin         }
61773779f3dSLoGin         if !path.join(&self.archive_name).is_file() {
61873779f3dSLoGin             return Err(format!(
61973779f3dSLoGin                 " {:?} is not a file",
62073779f3dSLoGin                 path.join(&self.archive_name)
62173779f3dSLoGin             ));
62273779f3dSLoGin         }
62373779f3dSLoGin         //根据压缩文件的类型生成cmd指令
62473779f3dSLoGin         match &self.archive_type {
62573779f3dSLoGin             ArchiveType::TarGz | ArchiveType::TarXz => {
62673779f3dSLoGin                 let mut cmd = Command::new("tar");
62773779f3dSLoGin                 cmd.arg("-xf").arg(&self.archive_name);
62873779f3dSLoGin                 let proc: std::process::Child = cmd
62973779f3dSLoGin                     .current_dir(path)
63073779f3dSLoGin                     .stderr(Stdio::piped())
63173779f3dSLoGin                     .stdout(Stdio::inherit())
63273779f3dSLoGin                     .spawn()
63373779f3dSLoGin                     .map_err(|e| e.to_string())?;
63473779f3dSLoGin                 let output = proc.wait_with_output().map_err(|e| e.to_string())?;
63573779f3dSLoGin                 if !output.status.success() {
63673779f3dSLoGin                     return Err(format!(
63773779f3dSLoGin                         "unzip failed, status: {:?},  stderr: {:?}",
63873779f3dSLoGin                         output.status,
63973779f3dSLoGin                         StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
64073779f3dSLoGin                     ));
64173779f3dSLoGin                 }
64273779f3dSLoGin             }
64373779f3dSLoGin 
64473779f3dSLoGin             ArchiveType::Zip => {
64573779f3dSLoGin                 let file = File::open(&self.archive_path.join(&self.archive_name))
64673779f3dSLoGin                     .map_err(|e| e.to_string())?;
64773779f3dSLoGin                 let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
64873779f3dSLoGin                 for i in 0..archive.len() {
64973779f3dSLoGin                     let mut file = archive.by_index(i).map_err(|e| e.to_string())?;
65073779f3dSLoGin                     let outpath = match file.enclosed_name() {
65173779f3dSLoGin                         Some(path) => self.archive_path.join(path),
65273779f3dSLoGin                         None => continue,
65373779f3dSLoGin                     };
65473779f3dSLoGin                     if (*file.name()).ends_with('/') {
65573779f3dSLoGin                         std::fs::create_dir_all(&outpath).map_err(|e| e.to_string())?;
65673779f3dSLoGin                     } else {
65773779f3dSLoGin                         if let Some(p) = outpath.parent() {
65873779f3dSLoGin                             if !p.exists() {
65973779f3dSLoGin                                 std::fs::create_dir_all(&p).map_err(|e| e.to_string())?;
66073779f3dSLoGin                             }
66173779f3dSLoGin                         }
66273779f3dSLoGin                         let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?;
66373779f3dSLoGin                         std::io::copy(&mut file, &mut outfile).map_err(|e| e.to_string())?;
66473779f3dSLoGin                     }
66573779f3dSLoGin                     //设置解压后权限,在Linux中Unzip会丢失权限
66673779f3dSLoGin                     #[cfg(unix)]
66773779f3dSLoGin                     {
66873779f3dSLoGin                         if let Some(mode) = file.unix_mode() {
66973779f3dSLoGin                             std::fs::set_permissions(
67073779f3dSLoGin                                 &outpath,
67173779f3dSLoGin                                 std::fs::Permissions::from_mode(mode),
67273779f3dSLoGin                             )
67373779f3dSLoGin                             .map_err(|e| e.to_string())?;
67473779f3dSLoGin                         }
67573779f3dSLoGin                     }
67673779f3dSLoGin                 }
67773779f3dSLoGin             }
67873779f3dSLoGin             _ => {
67973779f3dSLoGin                 return Err("unsupported archive type".to_string());
68073779f3dSLoGin             }
68173779f3dSLoGin         }
68273779f3dSLoGin         //删除下载的压缩包
68373779f3dSLoGin         info!("unzip successfully, removing archive ");
68473779f3dSLoGin         std::fs::remove_file(path.join(&self.archive_name)).map_err(|e| e.to_string())?;
68573779f3dSLoGin         //从解压的文件夹中提取出文件并删除下载的压缩包等价于指令"cd *;mv ./* ../../"
68673779f3dSLoGin         for entry in path.read_dir().map_err(|e| e.to_string())? {
68773779f3dSLoGin             let entry = entry.map_err(|e| e.to_string())?;
68873779f3dSLoGin             let path = entry.path();
68973779f3dSLoGin             FileUtils::move_files(&path, &self.archive_path.parent().unwrap())
69073779f3dSLoGin                 .map_err(|e| e.to_string())?;
69173779f3dSLoGin             //删除空的单独文件夹
69273779f3dSLoGin             std::fs::remove_dir_all(&path).map_err(|e| e.to_string())?;
69373779f3dSLoGin         }
69473779f3dSLoGin         return Ok(());
69573779f3dSLoGin     }
69673779f3dSLoGin }
69773779f3dSLoGin 
69873779f3dSLoGin pub enum ArchiveType {
69973779f3dSLoGin     TarGz,
70073779f3dSLoGin     TarXz,
70173779f3dSLoGin     Zip,
70273779f3dSLoGin     Undefined,
70373779f3dSLoGin }
704