diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..76fcadb --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.82" diff --git a/src/cli.rs b/src/cli.rs index 4b32007..8ede0d8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,7 +9,7 @@ use anyhow::{bail, Result}; #[command(author, version, about)] /// A tool for cloning all available repositories in a GitLab instance struct Cli { - /// The GitLab instance URL for fetch repositories (example: https://gitlab.local/) + /// The GitLab instance URL for fetch repositories (example: <https://gitlab.local>) #[arg( long, value_parser, @@ -27,7 +27,7 @@ struct Cli { )] ft: String, - /// The GitLab instance URL for backup repositories (example: https://backup-gitlab.local/) + /// The GitLab instance URL for backup repositories (example: <https://backup-gitlab.local>) #[arg( long, value_parser, @@ -123,19 +123,19 @@ struct Cli { #[arg(long, env = "GTLBSTR_UPLOAD_SSH")] upload_ssh: bool, - /// Force download repositories by insecure protocol. Does not work with the download_ssh flag + /// Force download repositories by insecure protocol. Does not work with the `download_ssh` flag #[arg(long, env = "GTLBSTR_DOWNLOAD_FORCE_HTTP")] download_force_http: bool, - /// Force download repositories by secure protocol. Does not work with the download_ssh flag + /// Force download repositories by secure protocol. Does not work with the `download_ssh` flag #[arg(long, env = "GTLBSTR_DOWNLOAD_FORCE_HTTPS")] download_force_https: bool, - /// Force upload repositories by insecure protocol. Does not work with the upload_ssh flag + /// Force upload repositories by insecure protocol. Does not work with the `upload_ssh` flag #[arg(long, env = "GTLBSTR_UPLOAD_FORCE_HTTP")] upload_force_http: bool, - /// Force upload repositories by secure protocol. Does not work with the upload_ssh flag + /// Force upload repositories by secure protocol. Does not work with the `upload_ssh` flag #[arg(long, env = "GTLBSTR_UPLOAD_FORCE_HTTPS")] upload_force_https: bool, @@ -172,7 +172,7 @@ pub fn run() -> Result<()> { }; tracing_subscriber::fmt().with_max_level(log_level).init(); - let fetch_gl = FetchGitlabOptions::new(cli.fu, cli.ft)?; + let fetch_gl = FetchGitlabOptions::new(&cli.fu, &cli.ft)?; let patterns = if cli.exclude.is_some() && cli.include.is_some() { bail!("You cannot use the --include and --exclude flag together"); @@ -184,11 +184,7 @@ pub fn run() -> Result<()> { let upl_err = "For upload to another gitlab, you must specify both the --bt and --bu flags"; let backup_gl = if let (Some(url), Some(token)) = (&cli.bu, &cli.bt) { - Some(BackupGitlabOptions::new( - url.clone(), - token.clone(), - cli.bg.clone(), - )?) + Some(BackupGitlabOptions::new(url, token, cli.bg.clone())?) } else { if cli.bu.is_some() || cli.bt.is_some() { bail!(upl_err); diff --git a/src/cloner.rs b/src/cloner.rs index f6a4c5a..7b8eb46 100644 --- a/src/cloner.rs +++ b/src/cloner.rs @@ -19,9 +19,12 @@ pub struct FetchGitlabOptions { } impl FetchGitlabOptions { - pub fn new(url: String, token: String) -> Result<Self> { - let url = Url::parse(&url)?; - Ok(Self { url, token }) + pub fn new(url: &str, token: &str) -> Result<Self> { + let url = Url::parse(url)?; + Ok(Self { + url, + token: token.to_string(), + }) } } @@ -33,9 +36,13 @@ pub struct BackupGitlabOptions { } impl BackupGitlabOptions { - pub fn new(url: String, token: String, group: Option<String>) -> Result<Self> { - let url = Url::parse(&url)?; - Ok(Self { url, token, group }) + pub fn new(url: &str, token: &str, group: Option<String>) -> Result<Self> { + let url = Url::parse(url)?; + Ok(Self { + url, + token: token.to_string(), + group, + }) } } @@ -99,9 +106,7 @@ fn make_git_path( ) -> String { if let Some(auth) = git_http_auth { let parts: Vec<&str> = project.http_url_to_repo.split("://").collect(); - if parts.len() != 2 { - panic!("project with incorrect http path") - } + assert!(parts.len() == 2, "project with incorrect http path"); let protocol = match force_protocol { ForceProtocol::No => parts[0], ForceProtocol::Http => "http", @@ -165,7 +170,7 @@ async fn clone_project( let mut project_groups: Vec<types::Group> = Vec::new(); for group in &path[..path.len() - 1] { - last_group = last_group + &group; + last_group += group; let g_info = { let mut groups_info = groups_info.lock().await; @@ -186,7 +191,7 @@ async fn clone_project( .await?; let remote = make_git_path(&backup_project, backup_git_http_auth, backup_force_protocol); - git::push_backup(format!("{}/{}", dst, p_path), remote).await?; + git::push_backup(format!("{dst}/{p_path}"), remote).await?; Ok(()) } @@ -240,7 +245,7 @@ pub async fn clone(p: CloneParams) -> Result<()> { } if let Some(patterns) = p.patterns { - projects = filter_projects(projects, patterns, p.limit)? + projects = filter_projects(projects, patterns, p.limit)?; } if projects.is_empty() { @@ -254,7 +259,7 @@ pub async fn clone(p: CloneParams) -> Result<()> { }; if p.clear_dst { - clear_dst(&dst) + clear_dst(&dst); } let backup_data = if let Some(backup) = p.backup { @@ -298,7 +303,7 @@ pub async fn clone(p: CloneParams) -> Result<()> { println!( "Backup group: {} (id: {}, path: {})", g.name, g.id, g.full_path - ) + ); }; } println!("Local out dir: {}", &dst); diff --git a/src/git.rs b/src/git.rs index c59fecd..daac049 100644 --- a/src/git.rs +++ b/src/git.rs @@ -57,7 +57,7 @@ async fn update(path: &String, only_master: bool) -> Result<()> { let branches_out = git(vec!["-C", path, "branch", "-la"]).await?; let branches = branches_out .split('\n') - .map(|v| v.trim()) + .map(str::trim) .filter(|v| !v.is_empty()) .filter(|v| !v.starts_with("remotes/origin/HEAD")) .filter(|v| !v.starts_with("remotes/backup")); @@ -104,17 +104,17 @@ async fn add_remote_backup(path: &String, remote: String) -> Result<()> { async fn push_all_remote_backup(path: String) -> Result<()> { if let Err(e) = git(vec!["-C", &path, "push", "-u", "backup", "--all"]).await { - error!("{}", e) + error!("{}", e); }; if let Err(e) = git(vec!["-C", &path, "push", "-u", "backup", "--tags"]).await { - error!("{}", e) + error!("{}", e); }; Ok(()) } pub async fn fetch(src: String, dst: String, only_master: bool) -> Result<()> { match check_status(&dst).await { - Ok(_) => (), + Ok(()) => (), Err(_) => clone(&src, &dst).await?, }; update(&dst, only_master).await diff --git a/src/gitlab/client.rs b/src/gitlab/client.rs index 097120c..112ed15 100644 --- a/src/gitlab/client.rs +++ b/src/gitlab/client.rs @@ -31,10 +31,10 @@ impl Client { http = http.timeout(Duration::from_secs(timeout.into())); } let http = http.build()?; - let limit = if let Some(opp) = opp { opp } else { 100 }; + let limit = opp.unwrap_or(100); let token = token.to_string(); - url.set_path(&format!("api/{}", API_VERSION)); + url.set_path(&format!("api/{API_VERSION}")); Ok(Client { url, @@ -68,7 +68,7 @@ impl Client { .header("PRIVATE-TOKEN", &self.token); if let Some(json) = json { - req = req.json(&json) + req = req.json(&json); } req @@ -89,13 +89,13 @@ impl Client { pub async fn get_project(&self, path: String) -> reqwest::Result<types::Project> { let path = urlencoding::encode(&path); - self.request(Method::GET, format!("projects/{}", path), None, None::<()>) + self.request(Method::GET, format!("projects/{path}"), None, None::<()>) .await? .json::<types::Project>() .await } - fn exist<T>(&self, resp: reqwest::Result<T>) -> reqwest::Result<Option<T>> { + fn check_exists<T>(resp: reqwest::Result<T>) -> reqwest::Result<Option<T>> { match resp { Ok(p) => Ok(Some(p)), Err(e) => { @@ -110,7 +110,7 @@ impl Client { } pub async fn project_exist(&self, path: String) -> reqwest::Result<Option<types::Project>> { - self.exist(self.get_project(path).await) + Client::check_exists(self.get_project(path).await) } /// Parse value of Link header @@ -130,9 +130,7 @@ impl Client { .split(';') .nth(next_page_link_position) .expect(&invalid_link_msg); - if link.len() < 13 { - panic!("{}", invalid_link_msg); - } + assert!(link.len() >= 13, "{}", invalid_link_msg); link[1..link.len() - 1].to_string() } @@ -147,7 +145,7 @@ impl Client { let method = match group { None => "projects".to_owned(), - Some(group) => format!("groups/{}/projects", group), + Some(group) => format!("groups/{group}/projects"), }; let mut next_page_link_position = 0; @@ -167,13 +165,13 @@ impl Client { } else { let mut query = format!("order_by=id&sort=asc&per_page={}", &self.limit); if only_owned { - query += "&owned=true" + query += "&owned=true"; } if only_membership { - query += "&only_membership=true" + query += "&only_membership=true"; } if method != "projects" { - query += "&include_subgroups=true" + query += "&include_subgroups=true"; } query }; @@ -186,17 +184,13 @@ impl Client { projects.append(&mut resp.json::<Vec<types::Project>>().await?); - match headers.get("x-next-page") { - None => break, - Some(has_next_page) => { - if has_next_page - .to_str() - .expect("Invalid x-next-page header") - .is_empty() - { - break; - } - } + let has_next_page = match headers.get("x-next-page") { + Some(h) => !h.to_str().expect("Invalid x-next-page header").is_empty(), + None => false, + }; + + if !has_next_page { + break; } let Some(link_header) = headers.get("link") else { @@ -285,14 +279,14 @@ impl Client { pub async fn get_group(&self, path: &str) -> reqwest::Result<types::Group> { let path = urlencoding::encode(path); - self.request(Method::GET, format!("groups/{}", path), None, None::<()>) + self.request(Method::GET, format!("groups/{path}"), None, None::<()>) .await? .json::<types::Group>() .await } pub async fn group_exist(&self, path: &str) -> reqwest::Result<Option<types::Group>> { - self.exist(self.get_group(path).await) + Client::check_exists(self.get_group(path).await) } pub async fn make_subgroup( @@ -351,7 +345,7 @@ impl Client { } match self - .project_exist(format!("{}/{}", current_namespace, project_slug)) + .project_exist(format!("{current_namespace}/{project_slug}")) .await? { Some(p) => self.update_project(&p, project_info).await, diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 185d8a6..0a5689f 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -13,24 +13,25 @@ mod tests { const GITLAB_HOST: &str = "https://gitlab.com/"; fn check_local(updated_data: Option<&String>) { - println!("-- check local dir"); fn check_file_data(path: String, data: &str) { let mut file = File::open(path).unwrap(); let mut content = String::new(); file.read_to_string(&mut content).unwrap(); let content = content.trim(); - println!("-- content: {}, expected: {}", content, data); + println!("-- content: {content}, expected: {data}"); assert!(content.trim() == data); } - let prefix = format!("{}/gitlobster_test/example", OUT_DIR); - let p1_path = format!("{}/project_1", prefix); - let p1_file_path = format!("{}/project_1", p1_path); + println!("-- check local dir"); + + let prefix = format!("{OUT_DIR}/gitlobster_test/example"); + let p1_path = format!("{prefix}/project_1"); + let p1_file_path = format!("{p1_path}/project_1"); let files = vec![ (p1_file_path.clone(), "branch1"), - (format!("{}/project_2/project_2", prefix), "main"), - (format!("{}/subgroup_1/project_3/project_3", prefix), "main"), + (format!("{prefix}/project_2/project_2"), "main"), + (format!("{prefix}/subgroup_1/project_3/project_3"), "main"), ]; // check files in default branches @@ -39,32 +40,32 @@ mod tests { } // check the second branch in project_1 - let git_cmd = format!("git -C {} checkout branch2", p1_path); + let git_cmd = format!("git -C {p1_path} checkout branch2"); Exec::shell(git_cmd).join().unwrap(); check_file_data(p1_file_path, "branch2"); // check updated file if need if let Some(data) = updated_data { - check_file_data(format!("{}/updating", p1_path), data.as_str()); + check_file_data(format!("{p1_path}/updating"), data.as_str()); }; - let git_cmd = format!("git -C {} checkout branch1", p1_path); + let git_cmd = format!("git -C {p1_path} checkout branch1"); Exec::shell(git_cmd).join().unwrap(); } fn check_backup(start_time: DateTime<Utc>, gitlab_token: &str, updated_data: Option<&String>) { - println!("-- check backup"); - #[derive(Deserialize)] struct Project { description: String, name: String, } - let url_prefix = format!("{}api/v4/projects", GITLAB_HOST); + println!("-- check backup"); + + let url_prefix = format!("{GITLAB_HOST}api/v4/projects"); let project_path = "gitlobster_test%2Fupload4%2Fgitlobster_test%2Fexample"; - let p1_name = format!("{}%2Fproject_1", project_path); - let p2_name = format!("{}%2Fproject_2", project_path); - let p3_name = format!("{}%2Fsubgroup_1%2Fproject_3", project_path); + let p1_name = format!("{project_path}%2Fproject_1"); + let p2_name = format!("{project_path}%2Fproject_2"); + let p3_name = format!("{project_path}%2Fsubgroup_1%2Fproject_3"); let mut files = vec![ (&p1_name, "branch1", "project_1", "branch1"), (&p1_name, "branch2", "project_1", "branch2"), @@ -80,13 +81,12 @@ mod tests { let client = rqw::Client::new(); for (project, branch, file, data) in files { let url = format!( - "{}/{}/repository/files/{}/raw?ref={}&access_token={}", - url_prefix, project, file, branch, gitlab_token + "{url_prefix}/{project}/repository/files/{file}/raw?ref={branch}&access_token={gitlab_token}" ); let resp = client.get(url).send().unwrap().error_for_status().unwrap(); let content = resp.text().unwrap(); let content = content.trim(); - println!("-- content: {}, expected: {}", content, data); + println!("-- content: {content}, expected: {data}"); assert!(content == data); } @@ -97,7 +97,7 @@ mod tests { (p3_name, "project_3"), ]; for (project, project_name) in projects { - let url = format!("{}/{}?access_token={}", url_prefix, project, gitlab_token); + let url = format!("{url_prefix}/{project}?access_token={gitlab_token}"); let resp = client.get(url).send().unwrap().error_for_status().unwrap(); let p = resp.json::<Project>().unwrap(); let d_time_str = p.description.split(" 🦞 Synced: ").last().unwrap(); @@ -112,8 +112,7 @@ mod tests { let _ = fs::remove_dir_all(OUT_DIR); let url = format!( - "{}api/v4/groups/gitlobster_test%2Fupload4%2Fgitlobster_test?access_token={}", - GITLAB_HOST, gitlab_token + "{GITLAB_HOST}api/v4/groups/gitlobster_test%2Fupload4%2Fgitlobster_test?access_token={gitlab_token}" ); let _ = rqw::Client::new().delete(url).send(); } @@ -121,20 +120,19 @@ mod tests { fn run_gitlobster(gitlab_token: &str, enable_ssh: bool) -> ExitStatus { let mut cmd = format!( "cargo run -- \ - --ft={} \ - --fu={} \ - --bt={} \ - --bu={} \ + --ft={gitlab_token} \ + --fu={GITLAB_HOST} \ + --bt={gitlab_token} \ + --bu={GITLAB_HOST} \ --bg=gitlobster_test/upload4 \ --only-owned \ --include='^gitlobster_test/example' \ --concurrency-limit=1 \ - -d {} \ + -d {OUT_DIR} \ -vv", - gitlab_token, GITLAB_HOST, gitlab_token, GITLAB_HOST, OUT_DIR, ); if enable_ssh { - cmd = format!("{} --download-ssh --upload-ssh", cmd) + cmd += " --download-ssh --upload-ssh"; } Exec::shell(cmd).join().unwrap() } @@ -143,8 +141,7 @@ mod tests { let project = "gitlobster_test%2Fexample%2Fproject_1"; let file = "updating"; let url = format!( - "{}api/v4/projects/{}/repository/files/{}?access_token={}", - GITLAB_HOST, project, file, gitlab_token + "{GITLAB_HOST}api/v4/projects/{project}/repository/files/{file}?access_token={gitlab_token}" ); let id = Uuid::new_v4().to_string(); let data = format!( @@ -152,10 +149,9 @@ mod tests { "branch": "branch2", "author_email": "gitlobster@lowit.ru", "author_name": "Mr. Gitlobster", - "content": "{}", + "content": "{id}", "commit_message": "update" - }}"#, - id + }}"# ); rqw::Client::new() .put(url)