From ae347d7506664bc71e49ded6794124014e65cd98 Mon Sep 17 00:00:00 2001 From: hak8or Date: Mon, 6 Oct 2025 19:17:28 -0400 Subject: [PATCH] Add repo_owner for where the migrated repos should go to Generated with Claude 4.5 Sonnet (Gemini 2.5 Pro was stubborn about wanting to refactor the world even when it was unrelated). Prompt was; ``` Given the attached rust code, and example toml configuration file; ... SNIP ... Can you add the ability to specify the owner of the migrated repo, such that if not provided then to use the user who owns the API key, or if provided then to use said owner? I think gitea refers to that as the "repo_owner" in it's swagger based API docs? Note, this would be one configuration for all the repos in the config toml file, not on a per-repo basis. ``` which didn't work since it tried to fetch a uid which doesn't exist for organizations, so lets prompt it to fix that and give it a helping hand; ``` Ah shoot, if I am using a migration to a new organization which the user who owns the API key has permissions to modify, then I am getting a 401 return code. Did you assume the target will always be a user rather than also being an organization? Also, keep in mind, I think giteas migration API wants a user string, rather than a user ID, if that's the case then I think we can remove the entire `get_user_id_by_username()` function? ``` --- example.toml | 4 ++++ src/main.rs | 40 +++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/example.toml b/example.toml index 1c2541c..2e89854 100644 --- a/example.toml +++ b/example.toml @@ -4,6 +4,10 @@ gitea_url = "https://gitmirror.hak8or.com" # Your Gitea API key (generate one from User Settings -> Applications) api_key = "API_KEY_GOES_HERE" +# Optional: specify the owner username for all migrated repos +# If not specified, uses the user who owns the API key +repo_owner = "mirror_org" + # A list of remote git repositories to mirror. repos = [ { url = "https://gitea.hak8or.com/hak8or/gitea_mirror.git" }, diff --git a/src/main.rs b/src/main.rs index abaabe7..dcd2aca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,6 +43,7 @@ struct Config { api_key: String, repos: Option>, organizations: Option>, + repo_owner: Option, // Optional owner username/org for all migrated repos } // Represents the payload for creating a migration in Gitea. @@ -50,16 +51,16 @@ struct Config { struct MigrateRepoPayload<'a> { clone_addr: &'a str, repo_name: &'a str, + repo_owner: &'a str, // Username or organization name mirror: bool, private: bool, description: &'a str, - uid: i64, // The user ID of the owner. We'll fetch this. } // Represents a user as returned by the Gitea API. #[derive(Deserialize, Debug)] struct GiteaUser { - id: i64, + login: String, } /// Entry point of the application. @@ -77,12 +78,16 @@ async fn main() -> Result<(), Box> { let config = load_config(&args.config)?; let http_client = reqwest::Client::new(); - // Fetch the Gitea user ID for the authenticated user. - let user_id = get_gitea_user_id(&http_client, &config.gitea_url, &config.api_key).await?; - info!( - "Successfully authenticated and retrieved user ID: {}", - user_id - ); + // Determine the owner (either from repo_owner or authenticated user) + let owner_name = if let Some(owner) = &config.repo_owner { + info!("Using specified repo_owner: {}", owner); + owner.clone() + } else { + info!("No repo_owner specified, fetching authenticated user"); + get_authenticated_username(&http_client, &config.gitea_url, &config.api_key).await? + }; + + info!("Using owner '{}' for all migrated repositories", owner_name); // Process repositories from the static list. if let Some(repos) = &config.repos { @@ -90,7 +95,7 @@ async fn main() -> Result<(), Box> { process_repo( &repo_config.url, repo_config.rename.as_deref(), - user_id, + &owner_name, &http_client, &config, args.dry_run, @@ -119,7 +124,7 @@ async fn main() -> Result<(), Box> { process_repo( &url, None, // No rename support for orgs - user_id, + &owner_name, &http_client, &config, args.dry_run, @@ -145,13 +150,13 @@ fn load_config(path: &Path) -> Result> { Ok(config) } -/// Fetches the authenticated user's ID from Gitea. +/// Fetches the authenticated user's login name from Gitea. #[instrument(skip(http_client, gitea_url, api_key))] -async fn get_gitea_user_id( +async fn get_authenticated_username( http_client: &reqwest::Client, gitea_url: &str, api_key: &str, -) -> Result { +) -> Result { let url = format!("{}/api/v1/user", gitea_url); let user: GiteaUser = http_client .get(&url) @@ -161,7 +166,8 @@ async fn get_gitea_user_id( .error_for_status()? .json() .await?; - Ok(user.id) + info!("Authenticated as user: {}", user.login); + Ok(user.login) } /// Checks if a repository already exists in Gitea for the user. @@ -276,11 +282,11 @@ async fn fetch_org_repos( } /// Core logic to process a single repository. -#[instrument(skip(user_id, http_client, config, dry_run))] +#[instrument(skip(owner_name, http_client, config, dry_run))] async fn process_repo( repo_url: &str, rename: Option<&str>, - user_id: i64, + owner_name: &str, http_client: &reqwest::Client, config: &Config, dry_run: bool, @@ -301,10 +307,10 @@ async fn process_repo( let payload = MigrateRepoPayload { clone_addr: repo_url, repo_name, + repo_owner: owner_name, mirror: true, private: false, // Defaulting to public, change if needed description: "", - uid: user_id, }; if let Err(e) = create_migration(http_client, config, &payload).await { error!("Failed to create migration for '{}': {}", repo_name, e);