From 13f98413bed381cacf31654b4971b60bda70432b Mon Sep 17 00:00:00 2001 From: n4n5 Date: Wed, 8 Jan 2025 17:46:17 +0100 Subject: [PATCH] bump --- Cargo.lock | 9 ++- Cargo.toml | 3 +- src/codeberg/platform.rs | 35 +++++++--- src/errors.rs | 31 +++++++-- src/github/mod.rs | 9 +++ src/github/platform.rs | 141 ++++++++++++++++++++++++++++++++++----- src/gitlab/platform.rs | 52 +++++++++------ src/sync.rs | 2 +- src/utils.rs | 4 +- 9 files changed, 232 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d8c36c..5eec0c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,7 +370,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git-mover" -version = "1.0.3" +version = "1.0.4" dependencies = [ "clap", "dotenv", @@ -386,6 +386,7 @@ dependencies = [ "tokio", "toml", "url", + "urlencoding", ] [[package]] @@ -1617,6 +1618,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf16_iter" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index 7a76199..6c73c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-mover" -version = "1.0.3" +version = "1.0.4" edition = "2021" keywords = ["utility", "git", "cli"] categories = ["command-line-utilities"] @@ -25,3 +25,4 @@ serde_json = "1.0.135" tokio = { version = "1", features = ["full"] } toml = "0.8.19" url = "2.5.4" +urlencoding = "2.1.3" diff --git a/src/codeberg/platform.rs b/src/codeberg/platform.rs index 09b1666..7b13ce1 100644 --- a/src/codeberg/platform.rs +++ b/src/codeberg/platform.rs @@ -2,11 +2,12 @@ use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}; use serde::{Deserialize, Serialize}; use std::pin::Pin; +use urlencoding::encode; use super::{repo::CodebergRepo, CODEBERG_URL}; use crate::{ errors::{GitMoverError, GitMoverErrorKind}, - platform::Platform, + platform::{Platform, PlatformType}, utils::Repo, }; @@ -62,7 +63,15 @@ impl Platform for CodebergPlatform { let response = request.await?; if !response.status().is_success() { - let get_repo = self.get_repo(repo_name.as_str()).await?; + let text = response.text().await?; + let get_repo = match self.get_repo(repo_name.as_str()).await { + Ok(repo) => repo, + Err(_e) => { + return Err(GitMoverError::new(GitMoverErrorKind::RepoCreation) + .with_platform(PlatformType::Codeberg) + .with_text(&text)); + } + }; let json_body_as_repo = json_body.clone().into(); if get_repo != json_body_as_repo { eprintln!( @@ -91,7 +100,7 @@ impl Platform for CodebergPlatform { "https://{}/api/v1/repos/{}/{}", CODEBERG_URL, self.get_username(), - repo_name + encode(&repo_name) ); let request = client .get(&url) @@ -103,7 +112,9 @@ impl Platform for CodebergPlatform { let response = request.await?; if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::GetRepo).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::GetRepo) + .with_platform(PlatformType::Codeberg) + .with_text(&text)); } let repo: CodebergRepo = response.json().await?; Ok(repo.into()) @@ -122,7 +133,7 @@ impl Platform for CodebergPlatform { "https://{}/api/v1/repos/{}/{}", CODEBERG_URL, self.get_username(), - repo.name + encode(&repo.name) ); let json_body = CodebergRepo { name: repo.name.to_string(), @@ -141,7 +152,9 @@ impl Platform for CodebergPlatform { let response = request.await?; if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::RepoEdition).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::RepoEdition) + .with_platform(PlatformType::Codeberg) + .with_text(&text)); } Ok(()) }) @@ -168,7 +181,9 @@ impl Platform for CodebergPlatform { let response = request.await?; if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::GetAllRepos).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::GetAllRepos) + .with_platform(PlatformType::Codeberg) + .with_text(&text)); } let text = response.text().await?; let repos: Vec = serde_json::from_str(&text)?; @@ -196,7 +211,7 @@ impl Platform for CodebergPlatform { "https://{}/api/v1/repos/{}/{}", CODEBERG_URL, self.get_username(), - name + encode(&name) ); let request = client .delete(&url) @@ -207,7 +222,9 @@ impl Platform for CodebergPlatform { let response = request.await?; if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::RepoDeletion).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::RepoDeletion) + .with_platform(PlatformType::Codeberg) + .with_text(&text)); } Ok(()) }) diff --git a/src/errors.rs b/src/errors.rs index 6cd94de..88dcd58 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,8 @@ //! Error handling for the git-mover crate. use std::{error::Error as StdError, fmt}; +use crate::platform::PlatformType; + /// Error type for the git-mover crate. #[derive(Debug)] pub struct GitMoverError { @@ -12,7 +14,11 @@ impl GitMoverError { /// Create a new error. pub(crate) fn new(kind: GitMoverErrorKind) -> Self { Self { - inner: Box::new(Inner { kind, source: None }), + inner: Box::new(Inner { + kind, + source: None, + platform: None, + }), } } @@ -24,6 +30,12 @@ impl GitMoverError { ))); self } + + /// Create a new error with a platform. + pub(crate) fn with_platform(mut self, platform: PlatformType) -> Self { + self.inner.platform = Some(platform); + self + } } /// Type alias for a boxed error. @@ -34,8 +46,12 @@ pub(crate) type BoxError = Box; struct Inner { /// Error kind. kind: GitMoverErrorKind, + /// Source error. source: Option, + + /// Platform error + platform: Option, } #[derive(Debug)] @@ -49,12 +65,15 @@ pub(crate) enum GitMoverErrorKind { /// Error related to serde. Serde, - /// Error related to the configuration. - Unimplemented, + /// Error related to Git2. + Git2, /// Error related to the RepoEdition func. RepoEdition, + /// Error related to the RepoCreation func. + RepoCreation, + /// Error related to the GetAllRepo func. GetAllRepos, @@ -86,6 +105,7 @@ impl From for GitMoverError { inner: Box::new(Inner { kind: GitMoverErrorKind::Reqwest, source: Some(Box::new(e)), + platform: None, }), } } @@ -97,6 +117,7 @@ impl From for GitMoverError { inner: Box::new(Inner { kind: GitMoverErrorKind::Serde, source: Some(Box::new(e)), + platform: None, }), } } @@ -108,6 +129,7 @@ impl From for GitMoverError { inner: Box::new(Inner { kind: GitMoverErrorKind::Platform, source: Some(Box::new(e)), + platform: None, }), } } @@ -117,8 +139,9 @@ impl From for GitMoverError { fn from(e: git2::Error) -> Self { Self { inner: Box::new(Inner { - kind: GitMoverErrorKind::Platform, + kind: GitMoverErrorKind::Git2, source: Some(Box::new(e)), + platform: None, }), } } diff --git a/src/github/mod.rs b/src/github/mod.rs index c56eaab..e58945f 100644 --- a/src/github/mod.rs +++ b/src/github/mod.rs @@ -5,3 +5,12 @@ pub(crate) mod repo; /// GitHub URL const GITHUB_URL: &str = "github.com"; + +/// GitHub API URL +const GITHUB_API_URL: &str = "api.github.com"; + +/// GitHub API Header +const GITHUB_API_HEADER: &str = "X-GitHub-Api-Version"; + +/// GitHub API Version +const GITHUB_API_VERSION: &str = "2022-11-28"; diff --git a/src/github/platform.rs b/src/github/platform.rs index 7022b9d..39611ab 100644 --- a/src/github/platform.rs +++ b/src/github/platform.rs @@ -1,9 +1,9 @@ //! Github Platform -use super::GITHUB_URL; +use super::{GITHUB_API_HEADER, GITHUB_API_URL, GITHUB_API_VERSION, GITHUB_URL}; use crate::{ errors::{GitMoverError, GitMoverErrorKind}, github::repo::RepoGithub, - platform::Platform, + platform::{Platform, PlatformType}, utils::Repo, }; use reqwest::{ @@ -12,6 +12,7 @@ use reqwest::{ }; use serde::{Deserialize, Serialize}; use std::pin::Pin; +use urlencoding::encode; /// Github Platform #[derive(Deserialize, Serialize, Default, Debug, Clone)] @@ -41,26 +42,106 @@ impl Platform for GithubPlatform { fn create_repo( &self, - _repo: Repo, + repo: Repo, ) -> Pin> + Send + '_>> { - unimplemented!("GitlabConfig::create_repo"); - Box::pin(async { Err(GitMoverError::new(GitMoverErrorKind::Unimplemented)) }) + let token = self.token.clone(); + let repo = repo.clone(); + Box::pin(async move { + let client = reqwest::Client::new(); + let url = format!("https://{}/user/repos", GITHUB_API_URL); + let request = client + .post(&url) + .header(AUTHORIZATION, format!("Bearer {}", token)) + .header(ACCEPT, "application/vnd.github+json") + .header(USER_AGENT, "reqwest") + .header(GITHUB_API_HEADER, GITHUB_API_VERSION) + .json(&repo) + .send(); + + let response = request.await?; + if !response.status().is_success() { + let text = response.text().await?; + let get_repo = match self.get_repo(repo.name.as_str()).await { + Ok(repo) => repo, + Err(_) => { + return Err(GitMoverError::new(GitMoverErrorKind::RepoCreation) + .with_platform(PlatformType::Github) + .with_text(&text)) + } + }; + if get_repo != repo { + return self.edit_repo(repo).await; + } + } + Ok(()) + }) } fn edit_repo( &self, - _repo: Repo, + repo: Repo, ) -> Pin> + Send + '_>> { - unimplemented!("GitlabConfig::edit_repo"); - Box::pin(async { Err(GitMoverError::new(GitMoverErrorKind::Unimplemented)) }) + let token = self.token.clone(); + Box::pin(async move { + let client = reqwest::Client::new(); + let url = format!( + "https://{}/repos/{}/{}", + GITHUB_API_URL, + self.username, + encode(&repo.name) + ); + let request = client + .patch(&url) + .header(AUTHORIZATION, format!("Bearer {}", token)) + .header(ACCEPT, "application/vnd.github+json") + .header(USER_AGENT, "reqwest") + .header(GITHUB_API_HEADER, GITHUB_API_VERSION) + .json(&repo) + .send(); + let response = request.await?; + if !response.status().is_success() { + let text = response.text().await?; + return Err(GitMoverError::new(GitMoverErrorKind::RepoEdition) + .with_platform(PlatformType::Github) + .with_text(&text)); + } + Ok(()) + }) } fn get_repo( &self, - _name: &str, + repo_name: &str, ) -> Pin> + Send>> { - unimplemented!("GitlabConfig::get_repo"); - Box::pin(async { Err(GitMoverError::new(GitMoverErrorKind::Unimplemented)) }) + let token = self.token.clone(); + let username = self.username.clone(); + let repo_name = repo_name.to_string(); + Box::pin(async move { + let client = Client::new(); + let url = format!( + "https://{}/repos/{}/{}", + GITHUB_API_URL, + username, + encode(&repo_name) + ); + let request = client + .get(&url) + .header(AUTHORIZATION, format!("Bearer {}", token)) + .header(ACCEPT, "application/vnd.github+json") + .header(USER_AGENT, "reqwest") + .header(GITHUB_API_HEADER, GITHUB_API_VERSION) + .send(); + let response = request.await?; + if !response.status().is_success() { + let text = response.text().await?; + return Err(GitMoverError::new(GitMoverErrorKind::GetRepo) + .with_platform(PlatformType::Github) + .with_text(&text)); + } + let text = response.text().await?; + let repo: RepoGithub = serde_json::from_str(&text)?; + Ok(repo.into()) + }) } fn get_all_repos( @@ -69,7 +150,7 @@ impl Platform for GithubPlatform { let token = self.token.clone(); Box::pin(async move { let client = Client::new(); - let url = &format!("https://api.{}/user/repos", GITHUB_URL); + let url = &format!("https://{}/user/repos", GITHUB_API_URL); let mut need_request = true; let mut page: usize = 1; let mut all_repos = vec![]; @@ -84,12 +165,14 @@ impl Platform for GithubPlatform { .header(AUTHORIZATION, format!("Bearer {}", token)) .header(ACCEPT, "application/vnd.github+json") .header(USER_AGENT, "reqwest") - .header("X-GitHub-Api-Version", "2022-11-28") + .header(GITHUB_API_HEADER, GITHUB_API_VERSION) .send(); let response = request.await?; if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::GetAllRepos).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::GetAllRepos) + .with_platform(PlatformType::Github) + .with_text(&text)); } let text = response.text().await?; let repos: Vec = serde_json::from_str(&text)?; @@ -107,9 +190,33 @@ impl Platform for GithubPlatform { fn delete_repo( &self, - _name: &str, + repo_name: &str, ) -> Pin> + Send + '_>> { - unimplemented!("GitlabConfig::delete_repo"); - Box::pin(async { Err(GitMoverError::new(GitMoverErrorKind::Unimplemented)) }) + let token = self.token.clone(); + let name = repo_name.to_string(); + Box::pin(async move { + let client = reqwest::Client::new(); + let url = format!( + "https://{}/repos/{}/{}", + GITHUB_API_URL, + self.username, + encode(&name) + ); + let request = client + .delete(&url) + .header(AUTHORIZATION, format!("Bearer {}", token)) + .header(ACCEPT, "application/vnd.github+json") + .header(USER_AGENT, "reqwest") + .header("X-GitHub-Api-Version", "2022-11-28") + .send(); + let response = request.await?; + if !response.status().is_success() { + let text = response.text().await?; + return Err(GitMoverError::new(GitMoverErrorKind::RepoDeletion) + .with_platform(PlatformType::Github) + .with_text(&text)); + } + Ok(()) + }) } } diff --git a/src/gitlab/platform.rs b/src/gitlab/platform.rs index a8368d2..3219b36 100644 --- a/src/gitlab/platform.rs +++ b/src/gitlab/platform.rs @@ -2,11 +2,13 @@ use crate::errors::GitMoverError; use crate::errors::GitMoverErrorKind; use crate::platform::Platform; +use crate::platform::PlatformType; use crate::utils::Repo; use reqwest::header::ACCEPT; use reqwest::header::CONTENT_TYPE; use serde::{Deserialize, Serialize}; use std::pin::Pin; +use urlencoding::encode; use super::repo::GitlabRepo; use super::repo::GitlabRepoEdition; @@ -29,9 +31,6 @@ impl GitlabPlatform { } } -/// Encoded slash -const SLASH: &str = "%2F"; - impl Platform for GitlabPlatform { fn get_remote_url(&self) -> &str { GITLAB_URL @@ -67,12 +66,18 @@ impl Platform for GitlabPlatform { let response = request.await?; if !response.status().is_success() { - let get_repo = self.get_repo(repo.name.as_str()).await?; + let text = response.text().await?; + let get_repo = match self.get_repo(repo.name.as_str()).await { + Ok(repo) => repo, + Err(_e) => { + return Err(GitMoverError::new(GitMoverErrorKind::RepoCreation) + .with_platform(PlatformType::Gitlab) + .with_text(&text)) + } + }; let json_body_as_repo = json_body.into(); if get_repo != json_body_as_repo { return self.edit_repo(json_body_as_repo).await; - } else { - return Ok(()); } } Ok(()) @@ -87,12 +92,11 @@ impl Platform for GitlabPlatform { let repo = repo.clone(); Box::pin(async move { let client = reqwest::Client::new(); + let repo_url = format!("{}/{}", self.get_username(), repo.name); let url = format!( - "https://{}/api/v4/projects/{}{}{}", + "https://{}/api/v4/projects/{}", GITLAB_URL, - self.get_username(), - SLASH, - repo.name + encode(&repo_url), ); let json_body = GitlabRepoEdition { description: repo.description.to_string(), @@ -109,7 +113,9 @@ impl Platform for GitlabPlatform { let response = request.await?; if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::RepoEdition).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::RepoEdition) + .with_platform(PlatformType::Gitlab) + .with_text(&text)); } Ok(()) }) @@ -133,13 +139,18 @@ impl Platform for GitlabPlatform { let response = request.await?; if !response.status().is_success() { - return Err(GitMoverError::new(GitMoverErrorKind::GetRepo)); + let text = response.text().await?; + return Err(GitMoverError::new(GitMoverErrorKind::GetRepo) + .with_platform(PlatformType::Gitlab) + .with_text(&text)); } let text = response.text().await?; let repos = serde_json::from_str::>(&text)?; match repos.into_iter().next() { Some(repo) => Ok(repo.into()), - None => Err(GitMoverError::new(GitMoverErrorKind::RepoNotFound).with_text(&text)), + None => Err(GitMoverError::new(GitMoverErrorKind::RepoNotFound) + .with_platform(PlatformType::Gitlab) + .with_text(&text)), } }) } @@ -169,7 +180,9 @@ impl Platform for GitlabPlatform { let response = request.await?; if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::GetAllRepos).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::GetAllRepos) + .with_platform(PlatformType::Gitlab) + .with_text(&text)); } let text = response.text().await?; let repos: Vec = match serde_json::from_str(&text) { @@ -196,12 +209,11 @@ impl Platform for GitlabPlatform { let name = name.to_string(); Box::pin(async move { let client = reqwest::Client::new(); + let repo_url = format!("{}/{}", self.get_username(), name); let url = format!( - "https://{}/api/v4/projects/{}{}{}", + "https://{}/api/v4/projects/{}", GITLAB_URL, - self.get_username(), - SLASH, - name + encode(&repo_url), ); let request = client .delete(&url) @@ -213,7 +225,9 @@ impl Platform for GitlabPlatform { if !response.status().is_success() { let text = response.text().await?; - return Err(GitMoverError::new(GitMoverErrorKind::RepoDeletion).with_text(&text)); + return Err(GitMoverError::new(GitMoverErrorKind::RepoDeletion) + .with_platform(PlatformType::Gitlab) + .with_text(&text)); } Ok(()) }) diff --git a/src/sync.rs b/src/sync.rs index 3abfae3..275011a 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -39,7 +39,7 @@ pub(crate) async fn sync_repos( match sync_one_repo(source_ref, destination_ref, one_repo, temp_dir_ref).await { Ok(_) => {} Err(e) => { - eprintln!("Error syncing {}: {:?}", repo_name, e); + eprintln!("Error syncing '{}': {:?}", repo_name, e); } } }); diff --git a/src/utils.rs b/src/utils.rs index 486b021..3401786 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::{fmt::Debug, sync::Arc}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tokio::join; use crate::cli::GitMoverCli; @@ -14,7 +14,7 @@ use crate::{ }; /// Repository information -#[derive(Deserialize, Debug, Default, PartialEq, Eq, Hash, Clone)] +#[derive(Deserialize, Serialize, Debug, Default, PartialEq, Eq, Hash, Clone)] pub struct Repo { /// Name of the repository pub name: String,