From d247777667d98eab001025a6a7fb6b8bb1b883bd Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Fri, 11 Sep 2026 23:10:19 +0200 Subject: [PATCH 1/8] =?UTF-8?q?Docs:=20Entfernt=20unn=C3=B6tige=20Security?= =?UTF-8?q?-Scan-Badges=20aus=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 0b82cae..dbb7f5c 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ [![Main Release & Publish](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions/workflows/main.yaml/badge.svg?branch=main)](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions?workflow=main.yaml) [![Testing Build, Publish & Preview Release](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions/workflows/testing.yaml/badge.svg?branch=testing)](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions?workflow=testing.yaml) -[![Security Scans](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions/workflows/security-scan.yaml/badge.svg?branch=main)](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions?workflow=security-scan.yaml) -[![TruffleHog Secret Scan](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions/workflows/trufflehog-scan.yaml/badge.svg?branch=main)](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions?workflow=trufflehog-scan.yaml) Automatisiertes Werkzeug zum Extrahieren, Herunterladen und Spiegeln vorkompilierter Linux-Pakete aus GitHub-Releases in eine selbstgehostete Gitea- / Forgejo-Paket-Registry. From 64a0aaa772f4313807759f27179a518a50b21e58 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Fri, 11 Sep 2026 23:22:32 +0200 Subject: [PATCH 2/8] Fix: Aktiviert `allowCustomCrateRegistries` in `renovate.json` --- renovate.json | 1 + 1 file changed, 1 insertion(+) diff --git a/renovate.json b/renovate.json index 717bd4d..9cc3c16 100644 --- a/renovate.json +++ b/renovate.json @@ -3,6 +3,7 @@ "extends": ["config:recommended"], "timezone": "Europe/Berlin", "schedule": ["before 6am on monday"], + "allowCustomCrateRegistries": true, "baseBranchPatterns": [ "dev" ], From 0f01f53391e134b65c279263f2aaf2577c8b522e Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 12 Sep 2026 09:00:36 +0000 Subject: [PATCH 3/8] chore(deps): update ghcr.io/renovatebot/renovate docker tag to v44.82.3 --- .gitea/workflows/renovate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/renovate.yaml b/.gitea/workflows/renovate.yaml index cd3e060..ee3f20e 100644 --- a/.gitea/workflows/renovate.yaml +++ b/.gitea/workflows/renovate.yaml @@ -9,7 +9,7 @@ jobs: renovate: name: Dependency-Updates prüfen & Pull Requests erstellen runs-on: ubuntu-latest - container: ghcr.io/renovatebot/renovate:44.82.0 + container: ghcr.io/renovatebot/renovate:44.82.3 steps: - name: Renovate ausführen run: renovate From 5ac35bb37049d3cdc316ce82debd0d4c863dbd53 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Sun, 13 Sep 2026 00:20:01 +0200 Subject: [PATCH 4/8] =?UTF-8?q?Feat:=20F=C3=BCgt=20code-quality-Workflow?= =?UTF-8?q?=20f=C3=BCr=20automatische=20Formatierung=20und=20fixes=20hinzu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced `code-quality.yaml` to automate Rust code formatting and Clippy fixes in the `dev` branch. - Updated `jsonSchemas.xml` to include schema paths for new Gitea workflows. --- .gitea/workflows/code-quality.yaml | 54 ++++++++++++++++++++++++++++++ .idea/jsonSchemas.xml | 3 ++ 2 files changed, 57 insertions(+) create mode 100644 .gitea/workflows/code-quality.yaml diff --git a/.gitea/workflows/code-quality.yaml b/.gitea/workflows/code-quality.yaml new file mode 100644 index 0000000..8c35d11 --- /dev/null +++ b/.gitea/workflows/code-quality.yaml @@ -0,0 +1,54 @@ +name: Code Quality (Auto-Format & Clippy-Fix) + +on: + push: + branches: + - dev + workflow_dispatch: + +jobs: + fix: + name: Formatierung & Clippy automatisch beheben + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v7 + with: + ref: ${{ gitea.ref_name || github.ref_name }} + token: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }} + + - name: Install Rust Toolchain + uses: actions-rust-lang/setup-rust-toolchain@v2 + with: + toolchain: stable + components: clippy, rustfmt + cache: false + + - name: Cache Cargo-Abhängigkeiten & Build-Artefakte + uses: actions/cache@v6 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} + restore-keys: | + cargo-${{ runner.os }}- + + - name: Formatierung automatisch beheben + run: cargo fmt + + - name: Clippy-Fixes automatisch anwenden + run: cargo clippy --fix --allow-dirty --allow-staged --all-targets + + - name: Änderungen committen & pushen + run: | + if [ -n "$(git status --porcelain)" ]; then + git config user.name "Gitea-Bot" + git config user.email "no-reply@creativedragonslayer.de" + git add -A + git commit -m "Style: Automatische Formatierung & Clippy-Fixes" + git push origin HEAD:${{ gitea.ref_name || github.ref_name }} + else + echo "Keine Formatierungs- oder Clippy-Änderungen." + fi diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml index 2c9f918..2ed321d 100644 --- a/.idea/jsonSchemas.xml +++ b/.idea/jsonSchemas.xml @@ -26,6 +26,9 @@ + + From 074d5a7ff2f0a7378a25f31738f47afa44c6b630 Mon Sep 17 00:00:00 2001 From: Gitea-Bot Date: Sat, 12 Sep 2026 22:21:10 +0000 Subject: [PATCH 5/8] Style: Automatische Formatierung & Clippy-Fixes --- src/config.rs | 19 ++++++++--- src/gitea.rs | 33 +++++++++++++++---- src/github.rs | 21 ++++++++---- src/main.rs | 74 ++++++++++++++++++++++++++++++++++--------- src/pipeline.rs | 56 +++++++++++++++++++------------- src/utils.rs | 18 +++++------ tests/config_tests.rs | 61 +++++++++++++++++++++++++++-------- tests/github_tests.rs | 28 +++++++++++----- 8 files changed, 224 insertions(+), 86 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9c2bafe..a9e78f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,7 +59,11 @@ impl AppConfig { /// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes. pub fn add_or_update_repo(&mut self, mut repo: RepoConfig) { repo.name = Self::normalize_repo_name(&repo.name); - if let Some(existing) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&repo.name)) { + if let Some(existing) = self + .repositories + .iter_mut() + .find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&repo.name)) + { existing.name = repo.name; existing.include_prereleases = repo.include_prereleases; } else { @@ -71,20 +75,27 @@ impl AppConfig { pub fn remove_repo(&mut self, repo_name: &str) -> bool { let normalized = Self::normalize_repo_name(repo_name); let before_len = self.repositories.len(); - self.repositories.retain(|r| !Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)); + self.repositories + .retain(|r| !Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)); self.repositories.len() < before_len } /// Sucht ein Repository anhand des Namens. pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> { let normalized = Self::normalize_repo_name(repo_name); - self.repositories.iter().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)) + self.repositories + .iter() + .find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)) } /// Aktualisiert den zuletzt synchronisierten Tag für ein bestimmtes Repository. pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) { let normalized = Self::normalize_repo_name(repo_name); - if let Some(repo) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)) { + if let Some(repo) = self + .repositories + .iter_mut() + .find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)) + { repo.last_synced_tag = Some(tag); } } diff --git a/src/gitea.rs b/src/gitea.rs index 2de5ea3..db4e987 100644 --- a/src/gitea.rs +++ b/src/gitea.rs @@ -1,5 +1,5 @@ use crate::github::PackageType; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use std::path::Path; use tokio::fs::File; @@ -12,7 +12,11 @@ pub struct GiteaConfig { } impl GiteaConfig { - pub fn new(base_url: impl Into, token: impl Into, owner: impl Into) -> Self { + pub fn new( + base_url: impl Into, + token: impl Into, + owner: impl Into, + ) -> Self { let mut base_url = base_url.into(); while base_url.ends_with('/') { base_url.pop(); @@ -44,17 +48,29 @@ pub fn get_target_upload_urls( match pkg_type { PackageType::Debian => { if prerelease { - vec![format!("{}/api/packages/{}/debian/pool/testing/main/upload", base, owner)] + vec![format!( + "{}/api/packages/{}/debian/pool/testing/main/upload", + base, owner + )] } else { vec![ - format!("{}/api/packages/{}/debian/pool/stable/main/upload", base, owner), - format!("{}/api/packages/{}/debian/pool/testing/main/upload", base, owner), + format!( + "{}/api/packages/{}/debian/pool/stable/main/upload", + base, owner + ), + format!( + "{}/api/packages/{}/debian/pool/testing/main/upload", + base, owner + ), ] } } PackageType::Rpm => { if prerelease { - vec![format!("{}/api/packages/{}/rpm/testing/upload", base, owner)] + vec![format!( + "{}/api/packages/{}/rpm/testing/upload", + base, owner + )] } else { vec![format!("{}/api/packages/{}/rpm/stable/upload", base, owner)] } @@ -82,7 +98,10 @@ impl GiteaClient { let mut headers = reqwest::header::HeaderMap::new(); headers.insert( reqwest::header::USER_AGENT, - reqwest::header::HeaderValue::from_static(concat!("mirror-package/", env!("CARGO_PKG_VERSION"))), + reqwest::header::HeaderValue::from_static(concat!( + "mirror-package/", + env!("CARGO_PKG_VERSION") + )), ); let client = reqwest::Client::builder() diff --git a/src/github.rs b/src/github.rs index 231a8cd..da98fc5 100644 --- a/src/github.rs +++ b/src/github.rs @@ -91,7 +91,10 @@ impl GitHubClient { let mut headers = reqwest::header::HeaderMap::new(); headers.insert( reqwest::header::USER_AGENT, - reqwest::header::HeaderValue::from_static(concat!("mirror-package/", env!("CARGO_PKG_VERSION"))), + reqwest::header::HeaderValue::from_static(concat!( + "mirror-package/", + env!("CARGO_PKG_VERSION") + )), ); headers.insert( reqwest::header::ACCEPT, @@ -111,7 +114,6 @@ impl GitHubClient { self.token.as_deref() } - /// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo"). /// /// Wenn `history` false ist, werden nur die neuesten Releases abgerufen. @@ -139,7 +141,10 @@ impl GitHubClient { req = req.bearer_auth(token); } - let resp = req.send().await.with_context(|| format!("Failed to send request to {}", url))?; + let resp = req + .send() + .await + .with_context(|| format!("Failed to send request to {}", url))?; let status = resp.status(); if !status.is_success() { @@ -153,10 +158,12 @@ impl GitHubClient { ); } - let raw_releases: Vec = resp - .json() - .await - .with_context(|| format!("Failed to parse GitHub releases JSON for {}/{}", owner, name))?; + let raw_releases: Vec = resp.json().await.with_context(|| { + format!( + "Failed to parse GitHub releases JSON for {}/{}", + owner, name + ) + })?; if raw_releases.is_empty() { break; diff --git a/src/main.rs b/src/main.rs index 7c8176e..c017766 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use anyhow::Result; use clap::Parser; -use logger_ctdra::{log, set_log_level, LogLevel}; +use logger_ctdra::{LogLevel, log, set_log_level}; use mirror_package::cli::{Cli, Commands, ConfigAction}; use mirror_package::config::{ - get_config_file_path, init_config_path, load_config, modify_config, AppConfig, RepoConfig, + AppConfig, RepoConfig, get_config_file_path, init_config_path, load_config, modify_config, }; -use mirror_package::pipeline::{sync_single_repository, SyncOptions, SyncReport}; +use mirror_package::pipeline::{SyncOptions, SyncReport, sync_single_repository}; #[tokio::main] async fn main() -> Result<()> { @@ -65,7 +65,9 @@ async fn main() -> Result<()> { let mut report = SyncReport::default(); for repo in repos_to_sync { - if let Err(e) = sync_single_repository(&repo, &app_config, &options, &mut report).await { + if let Err(e) = + sync_single_repository(&repo, &app_config, &options, &mut report).await + { log( "sync", &format!("Failed to sync repository '{}': {:#}", repo.name, e), @@ -128,23 +130,38 @@ async fn main() -> Result<()> { log( "config", - &format!("Repository '{}' removed from configuration.", normalized_name), + &format!( + "Repository '{}' removed from configuration.", + normalized_name + ), LogLevel::Info, ); } Commands::List => { - println!("Configured repositories (Config file: {:?}):", get_config_file_path()); + println!( + "Configured repositories (Config file: {:?}):", + get_config_file_path() + ); if app_config.repositories.is_empty() { - println!(" (None configured yet. Use 'mirror-package add ' to add one.)"); + println!( + " (None configured yet. Use 'mirror-package add ' to add one.)" + ); } else { - println!("{:<40} {:<15} {:<20}", "REPOSITORY", "PRE-RELEASES", "LAST SYNCED TAG"); + println!( + "{:<40} {:<15} {:<20}", + "REPOSITORY", "PRE-RELEASES", "LAST SYNCED TAG" + ); println!("{:-<40} {:-<15} {:-<20}", "", "", ""); for repo in &app_config.repositories { println!( "{:<40} {:<15} {:<20}", repo.name, - if repo.include_prereleases { "yes" } else { "no" }, + if repo.include_prereleases { + "yes" + } else { + "no" + }, repo.last_synced_tag.as_deref().unwrap_or("-") ); } @@ -154,17 +171,40 @@ async fn main() -> Result<()> { Commands::Config(config_args) => match config_args.action { None | Some(ConfigAction::Show) => { println!("Configuration location: {:?}", get_config_file_path()); - println!("Gitea URL: {}", app_config.gitea_url.as_deref().unwrap_or("(not configured)")); - println!("Registry Owner: {}", app_config.registry_owner.as_deref().unwrap_or("(not configured)")); + println!( + "Gitea URL: {}", + app_config + .gitea_url + .as_deref() + .unwrap_or("(not configured)") + ); + println!( + "Registry Owner: {}", + app_config + .registry_owner + .as_deref() + .unwrap_or("(not configured)") + ); println!( "Gitea Token: {}", - if app_config.gitea_token.is_some() { "******** (set)" } else { "(not configured)" } + if app_config.gitea_token.is_some() { + "******** (set)" + } else { + "(not configured)" + } ); println!( "GitHub Token: {}", - if app_config.github_token.is_some() { "******** (set)" } else { "(not configured, public access only)" } + if app_config.github_token.is_some() { + "******** (set)" + } else { + "(not configured, public access only)" + } + ); + println!( + "Repositories: {} configured", + app_config.repositories.len() ); - println!("Repositories: {} configured", app_config.repositories.len()); } Some(ConfigAction::Set { @@ -188,7 +228,11 @@ async fn main() -> Result<()> { } })?; - log("config", "Configuration successfully updated.", LogLevel::Info); + log( + "config", + "Configuration successfully updated.", + LogLevel::Info, + ); } }, } diff --git a/src/pipeline.rs b/src/pipeline.rs index 9b0e0d2..fc50742 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,11 +1,11 @@ -use crate::config::{modify_config, AppConfig, RepoConfig}; -use crate::gitea::{get_target_upload_urls, GiteaClient, GiteaConfig, UploadStatus}; +use crate::config::{AppConfig, RepoConfig, modify_config}; +use crate::gitea::{GiteaClient, GiteaConfig, UploadStatus, get_target_upload_urls}; use crate::github::GitHubClient; -use anyhow::{bail, Context, Result}; -use logger_ctdra::{log, LogLevel}; +use anyhow::{Context, Result, bail}; +use logger_ctdra::{LogLevel, log}; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; -use tokio::fs::{remove_file, File}; +use tokio::fs::{File, remove_file}; use tokio::io::AsyncWriteExt; /// Zusammenfassende Statistiken eines Synchronisationsvorgangs. @@ -52,7 +52,9 @@ pub async fn sync_single_repository( let gitea_token = app_config.gitea_token.as_deref().unwrap_or_default(); let registry_owner = app_config.registry_owner.as_deref().unwrap_or_default(); - if !options.dry_run && (gitea_url.is_empty() || gitea_token.is_empty() || registry_owner.is_empty()) { + if !options.dry_run + && (gitea_url.is_empty() || gitea_token.is_empty() || registry_owner.is_empty()) + { bail!( "Missing Gitea configuration. Ensure --gitea-url, --gitea-token, and --registry-owner are provided or configured." ); @@ -61,12 +63,8 @@ pub async fn sync_single_repository( let github_client = GitHubClient::new(app_config.github_token.clone()) .context("Failed to initialize GitHub client")?; - let gitea_client = GiteaClient::new(GiteaConfig::new( - gitea_url, - gitea_token, - registry_owner, - )) - .context("Failed to initialize Gitea client")?; + let gitea_client = GiteaClient::new(GiteaConfig::new(gitea_url, gitea_token, registry_owner)) + .context("Failed to initialize Gitea client")?; let releases = github_client .fetch_releases(repo_name, options.history, include_prereleases) @@ -97,7 +95,11 @@ pub async fn sync_single_repository( for release in &releases { report.releases_processed += 1; - let release_type_str = if release.prerelease { "pre-release" } else { "stable release" }; + let release_type_str = if release.prerelease { + "pre-release" + } else { + "stable release" + }; log( "sync", @@ -156,9 +158,10 @@ pub async fn sync_single_repository( } // Asset in temporäre Datei herunterladen - let temp_file_path = download_to_temp_file(&github_client, &asset.download_url, &asset.name) - .await - .with_context(|| format!("Failed to download asset '{}'", asset.name))?; + let temp_file_path = + download_to_temp_file(&github_client, &asset.download_url, &asset.name) + .await + .with_context(|| format!("Failed to download asset '{}'", asset.name))?; // Asset auf alle Ziel-Distributions-URLs hochladen for url in &target_urls { @@ -168,7 +171,10 @@ pub async fn sync_single_repository( LogLevel::Debug, ); - match gitea_client.upload_file(&temp_file_path, url, options.dry_run).await { + match gitea_client + .upload_file(&temp_file_path, url, options.dry_run) + .await + { Ok(UploadStatus::Uploaded) => { log( "upload", @@ -220,14 +226,13 @@ pub async fn sync_single_repository( } // Zuletzt synchronisierten Tag persistieren, wenn kein Dry-Run - if !options.dry_run { - if let Some(tag) = latest_synced_tag { + if !options.dry_run + && let Some(tag) = latest_synced_tag { let name_copy = repo_name.to_string(); let _ = modify_config(move |cfg| { cfg.update_last_synced_tag(&name_copy, tag); }); } - } report.repositories_processed += 1; Ok(()) @@ -254,7 +259,10 @@ async fn download_to_temp_file( log( "download", - &format!("Downloading '{}' to temporary path {:?}...", filename, temp_path), + &format!( + "Downloading '{}' to temporary path {:?}...", + filename, temp_path + ), LogLevel::Debug, ); @@ -263,7 +271,11 @@ async fn download_to_temp_file( .await .with_context(|| format!("Failed to create temp file {:?}", temp_path))?; - while let Some(chunk) = response.chunk().await.context("Error reading download stream")? { + while let Some(chunk) = response + .chunk() + .await + .context("Error reading download stream")? + { file.write_all(&chunk) .await .context("Error writing chunk to temp file")?; diff --git a/src/utils.rs b/src/utils.rs index 317d8e9..149bf33 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,3 @@ - /// Sanitizes a string by removing surrounding quotes (single or double). /// /// # Arguments @@ -12,7 +11,10 @@ pub fn sanitize_string(input: &str) -> String { let trimmed = input.trim(); if let Some(stripped) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')) { stripped.to_string() - } else if let Some(stripped) = trimmed.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { + } else if let Some(stripped) = trimmed + .strip_prefix('\'') + .and_then(|s| s.strip_suffix('\'')) + { stripped.to_string() } else { trimmed.to_string() @@ -25,7 +27,7 @@ pub fn sanitize_string(input: &str) -> String { /// /// * `env_vars` - A reference to a mutable map of environment variables. pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap) { - for (_key, value) in env_vars.iter_mut() { + for value in env_vars.values_mut() { *value = sanitize_string(value); } } @@ -35,15 +37,13 @@ pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap &str { let mut trimmed = input.trim(); - if (trimmed.starts_with('"') && trimmed.ends_with('"')) - || (trimmed.starts_with('\'') && trimmed.ends_with('\'')) - { - if trimmed.len() >= 2 { + if ((trimmed.starts_with('"') && trimmed.ends_with('"')) + || (trimmed.starts_with('\'') && trimmed.ends_with('\''))) + && trimmed.len() >= 2 { trimmed = trimmed[1..trimmed.len() - 1].trim(); } - } - let without_query_or_fragment = match trimmed.find(|c| c == '?' || c == '#') { + let without_query_or_fragment = match trimmed.find(['?', '#']) { Some(idx) => &trimmed[..idx], None => trimmed, }; diff --git a/tests/config_tests.rs b/tests/config_tests.rs index 972e675..5895675 100644 --- a/tests/config_tests.rs +++ b/tests/config_tests.rs @@ -5,14 +5,23 @@ use std::collections::HashMap; #[test] fn test_sanitize_env_vars() { let mut env_vars = HashMap::new(); - env_vars.insert("GITEA_URL".to_string(), "\"https://gitea.example.com\"".to_string()); + env_vars.insert( + "GITEA_URL".to_string(), + "\"https://gitea.example.com\"".to_string(), + ); env_vars.insert("GITEA_TOKEN".to_string(), "\"token123\"".to_string()); env_vars.insert("REGISTRY_OWNER".to_string(), "\"owner\"".to_string()); - env_vars.insert("GITHUB_TOKEN".to_string(), "\"github_token123\"".to_string()); + env_vars.insert( + "GITHUB_TOKEN".to_string(), + "\"github_token123\"".to_string(), + ); sanitize_env_vars(&mut env_vars); - assert_eq!(env_vars.get("GITEA_URL").unwrap(), "https://gitea.example.com"); + assert_eq!( + env_vars.get("GITEA_URL").unwrap(), + "https://gitea.example.com" + ); assert_eq!(env_vars.get("GITEA_TOKEN").unwrap(), "token123"); assert_eq!(env_vars.get("REGISTRY_OWNER").unwrap(), "owner"); assert_eq!(env_vars.get("GITHUB_TOKEN").unwrap(), "github_token123"); @@ -31,7 +40,10 @@ fn test_load_config_with_env_vars() { // Only assert if the environment variables are set if std::env::var("GITEA_URL").is_ok() { - assert_eq!(config.gitea_url, Some("https://gitea.example.com".to_string())); + assert_eq!( + config.gitea_url, + Some("https://gitea.example.com".to_string()) + ); assert_eq!(config.gitea_token, Some("token123".to_string())); assert_eq!(config.registry_owner, Some("owner".to_string())); assert_eq!(config.github_token, Some("github_token123".to_string())); @@ -40,8 +52,14 @@ fn test_load_config_with_env_vars() { #[test] fn test_sanitize_string() { - assert_eq!(sanitize_string("\"https://gitea.example.com\""), "https://gitea.example.com"); - assert_eq!(sanitize_string("'https://gitea.example.com'"), "https://gitea.example.com"); + assert_eq!( + sanitize_string("\"https://gitea.example.com\""), + "https://gitea.example.com" + ); + assert_eq!( + sanitize_string("'https://gitea.example.com'"), + "https://gitea.example.com" + ); assert_eq!(sanitize_string("\"token123\""), "token123"); assert_eq!(sanitize_string("'token123'"), "token123"); assert_eq!(sanitize_string("\"owner\""), "owner"); @@ -68,11 +86,23 @@ fn test_sanitize_string() { #[test] fn test_clean_repo_input() { assert_eq!(clean_repo_input("owner/repo"), "owner/repo"); - assert_eq!(clean_repo_input("https://github.com/owner/repo"), "owner/repo"); - assert_eq!(clean_repo_input("http://github.com/owner/repo"), "owner/repo"); + assert_eq!( + clean_repo_input("https://github.com/owner/repo"), + "owner/repo" + ); + assert_eq!( + clean_repo_input("http://github.com/owner/repo"), + "owner/repo" + ); assert_eq!(clean_repo_input("github.com/owner/repo"), "owner/repo"); - assert_eq!(clean_repo_input("https://github.com/owner/repo.git"), "owner/repo"); - assert_eq!(clean_repo_input("https://github.com/owner/repo/"), "owner/repo"); + assert_eq!( + clean_repo_input("https://github.com/owner/repo.git"), + "owner/repo" + ); + assert_eq!( + clean_repo_input("https://github.com/owner/repo/"), + "owner/repo" + ); assert_eq!( clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file"), "raspberrypi/rpi-imager" @@ -123,14 +153,14 @@ fn test_add_or_update_repo() { config.add_or_update_repo(repo.clone()); assert_eq!(config.repositories.len(), 1); assert_eq!(config.repositories[0].name, "owner/repo"); - assert_eq!(config.repositories[0].include_prereleases, true); + assert!(config.repositories[0].include_prereleases); // Updating existing repo with a URL with query param let repo_updated = RepoConfig::new("https://github.com/owner/repo#readme", false); config.add_or_update_repo(repo_updated); assert_eq!(config.repositories.len(), 1); assert_eq!(config.repositories[0].name, "owner/repo"); - assert_eq!(config.repositories[0].include_prereleases, false); + assert!(!config.repositories[0].include_prereleases); } #[test] @@ -159,5 +189,8 @@ fn test_update_last_synced_tag() { let repo = RepoConfig::new("owner/repo", true); config.add_or_update_repo(repo.clone()); config.update_last_synced_tag("owner/repo", "v1.0.0".to_string()); - assert_eq!(config.find_repo("owner/repo").unwrap().last_synced_tag, Some("v1.0.0".to_string())); -} \ No newline at end of file + assert_eq!( + config.find_repo("owner/repo").unwrap().last_synced_tag, + Some("v1.0.0".to_string()) + ); +} diff --git a/tests/github_tests.rs b/tests/github_tests.rs index 16ad8dc..c8264ca 100644 --- a/tests/github_tests.rs +++ b/tests/github_tests.rs @@ -41,7 +41,10 @@ fn test_package_classification() { ); // Nicht unterstützte Paketformate sollten ignoriert werden - assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.AppImage"), None); + assert_eq!( + PackageType::from_filename("rpi-imager-1.8.5.AppImage"), + None + ); assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.dmg"), None); assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.exe"), None); assert_eq!(PackageType::from_filename("source-code.tar.gz"), None); @@ -57,19 +60,25 @@ fn test_parse_repo_owner_name() { ("raspberrypi", "rpi-imager") ); assert_eq!( - parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher").unwrap(), + parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher") + .unwrap(), ("Heroic-Games-Launcher", "HeroicGamesLauncher") ); assert_eq!( - parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher.git").unwrap(), + parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher.git") + .unwrap(), ("Heroic-Games-Launcher", "HeroicGamesLauncher") ); assert_eq!( - parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file").unwrap(), + parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file") + .unwrap(), ("raspberrypi", "rpi-imager") ); assert_eq!( - parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install").unwrap(), + parse_repo_owner_name( + "https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install" + ) + .unwrap(), ("raspberrypi", "rpi-imager") ); assert_eq!( @@ -86,14 +95,17 @@ async fn test_github_client_empty_token() { use mirror_package::github::GitHubClient; // Test that a client with an empty or whitespace token sanitizes it to None - let client = GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token"); + let client = + GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token"); assert_eq!(client.token(), None); - let client_whitespace = GitHubClient::new(Some(" ".to_string())).expect("Failed to create client with whitespace token"); + let client_whitespace = GitHubClient::new(Some(" ".to_string())) + .expect("Failed to create client with whitespace token"); assert_eq!(client_whitespace.token(), None); // Test with a valid token - let client_with_token = GitHubClient::new(Some("valid_token".to_string())).expect("Failed to create client with valid token"); + let client_with_token = GitHubClient::new(Some("valid_token".to_string())) + .expect("Failed to create client with valid token"); assert_eq!(client_with_token.token(), Some("valid_token")); // Test with no token From db42d73adbf53127c473f4f0d9622af44cdd3d6d Mon Sep 17 00:00:00 2001 From: Gitea-Bot Date: Sat, 12 Sep 2026 22:22:15 +0000 Subject: [PATCH 6/8] Style: Automatische Formatierung & Clippy-Fixes --- src/pipeline.rs | 13 +++++++------ src/utils.rs | 7 ++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index fc50742..9f2e673 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -227,12 +227,13 @@ pub async fn sync_single_repository( // Zuletzt synchronisierten Tag persistieren, wenn kein Dry-Run if !options.dry_run - && let Some(tag) = latest_synced_tag { - let name_copy = repo_name.to_string(); - let _ = modify_config(move |cfg| { - cfg.update_last_synced_tag(&name_copy, tag); - }); - } + && let Some(tag) = latest_synced_tag + { + let name_copy = repo_name.to_string(); + let _ = modify_config(move |cfg| { + cfg.update_last_synced_tag(&name_copy, tag); + }); + } report.repositories_processed += 1; Ok(()) diff --git a/src/utils.rs b/src/utils.rs index 149bf33..5679b7f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -39,9 +39,10 @@ pub fn clean_repo_input(input: &str) -> &str { let mut trimmed = input.trim(); if ((trimmed.starts_with('"') && trimmed.ends_with('"')) || (trimmed.starts_with('\'') && trimmed.ends_with('\''))) - && trimmed.len() >= 2 { - trimmed = trimmed[1..trimmed.len() - 1].trim(); - } + && trimmed.len() >= 2 + { + trimmed = trimmed[1..trimmed.len() - 1].trim(); + } let without_query_or_fragment = match trimmed.find(['?', '#']) { Some(idx) => &trimmed[..idx], From 475f0f9903421fb03e348756508c71cac7df88a7 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Sun, 13 Sep 2026 01:17:04 +0200 Subject: [PATCH 7/8] Fix: Verschiebt allowCustomCrateRegistries in die globale Renovate-Config Die Option ist repo-only nicht erlaubt und wurde in der Renovate-Workflow- Umgebung (RENOVATE_ALLOW_CUSTOM_CRATE_REGISTRIES) gesetzt. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0156cWd2mGWNQU1kBbBPWpD7 --- .gitea/workflows/renovate.yaml | 1 + renovate.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/renovate.yaml b/.gitea/workflows/renovate.yaml index ee3f20e..bc2f748 100644 --- a/.gitea/workflows/renovate.yaml +++ b/.gitea/workflows/renovate.yaml @@ -19,6 +19,7 @@ jobs: RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }} RENOVATE_AUTODISCOVER: "false" + RENOVATE_ALLOW_CUSTOM_CRATE_REGISTRIES: "true" RENOVATE_GIT_AUTHOR: "Renovate Bot " RENOVATE_HOST_RULES: >- [{"hostType":"cargo","matchHost":"${{ gitea.server_url || github.server_url }}","token":"${{ secrets.RENOVATE_TOKEN }}"}] diff --git a/renovate.json b/renovate.json index 9cc3c16..717bd4d 100644 --- a/renovate.json +++ b/renovate.json @@ -3,7 +3,6 @@ "extends": ["config:recommended"], "timezone": "Europe/Berlin", "schedule": ["before 6am on monday"], - "allowCustomCrateRegistries": true, "baseBranchPatterns": [ "dev" ], From 513eae640f1604e0a39fe081d03f7afb24d5db77 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Sun, 13 Sep 2026 11:39:21 +0200 Subject: [PATCH 8/8] =?UTF-8?q?Feat:=20Baut=20Pakete=20nur=20bei=20tats?= =?UTF-8?q?=C3=A4chlichen=20Code-=C3=84nderungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fügt einen vorgeschalteten detect-changes-Job ein, der mittels dorny/paths-filter@v4 prüft, ob sich Programmcode (src/, Cargo.toml, Cargo.lock, scripts/, .cargo/ bzw. die jeweilige Workflow-Datei selbst) geändert hat. Der Build-/Publish-/Release-Job wird nur noch ausgeführt, wenn das der Fall ist; bei reinen Config- oder Workflow-Änderungen schließt der Workflow erfolgreich ohne Build/Release ab. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01TcFzG54jW33nvp1TawCZJV --- .gitea/workflows/main.yaml | 25 +++++++++++++++++++++++++ .gitea/workflows/testing.yaml | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/.gitea/workflows/main.yaml b/.gitea/workflows/main.yaml index b903d4f..2c827ab 100644 --- a/.gitea/workflows/main.yaml +++ b/.gitea/workflows/main.yaml @@ -6,8 +6,33 @@ on: - main jobs: + detect-changes: + name: Erkenne relevante Code-Änderungen + runs-on: ubuntu-latest + outputs: + code_changed: ${{ steps.filter.outputs.code }} + steps: + - name: Checkout Repository + uses: actions/checkout@v7 + + - name: Prüfe auf Änderungen am Programmcode + uses: dorny/paths-filter@v4 + id: filter + with: + filters: | + code: + - 'src/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'scripts/**' + - '.cargo/**' + - 'Dockerfile' + - '.gitea/workflows/main.yaml' + release-and-publish: name: Build, Publish Packages (Stable) & Create Release + needs: detect-changes + if: needs.detect-changes.outputs.code_changed == 'true' runs-on: ubuntu-latest env: CARGO_BINSTALL_VERSION: "1.23.0" diff --git a/.gitea/workflows/testing.yaml b/.gitea/workflows/testing.yaml index dbdcc45..3968e5b 100644 --- a/.gitea/workflows/testing.yaml +++ b/.gitea/workflows/testing.yaml @@ -6,8 +6,33 @@ on: - testing jobs: + detect-changes: + name: Erkenne relevante Code-Änderungen + runs-on: ubuntu-latest + outputs: + code_changed: ${{ steps.filter.outputs.code }} + steps: + - name: Checkout Repository + uses: actions/checkout@v7 + + - name: Prüfe auf Änderungen am Programmcode + uses: dorny/paths-filter@v4 + id: filter + with: + filters: | + code: + - 'src/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'scripts/**' + - '.cargo/**' + - 'Dockerfile' + - '.gitea/workflows/testing.yaml' + build-and-publish: name: Build, Publish Packages (Testing) & Create Preview Release + needs: detect-changes + if: needs.detect-changes.outputs.code_changed == 'true' runs-on: ubuntu-latest env: CARGO_BINSTALL_VERSION: "1.23.0"