**Feat: Erweitert Repository-Input-Handling mit robusterer Normalisierung**
- Unterstützt Query-Parameter, Fragmente und verschiedene URL-Formate - Neue `clean_repo_input`-Funktion für konsistente Verarbeitung - Verbesserte Tests für Edge-Cases (Anführungszeichen, `.git`, Pfade)
This commit is contained in:
+16
-16
@@ -1,4 +1,4 @@
|
||||
use crate::utils::sanitize_string;
|
||||
use crate::utils::{clean_repo_input, sanitize_string};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct RepoConfig {
|
||||
impl RepoConfig {
|
||||
pub fn new(name: impl Into<String>, include_prereleases: bool) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
name: AppConfig::normalize_repo_name(&name.into()),
|
||||
include_prereleases,
|
||||
last_synced_tag: None,
|
||||
}
|
||||
@@ -50,21 +50,17 @@ pub struct AppConfig {
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Normalisiert die Repository-Eingabe, indem führende/nachgestellte Schrägstriche und GitHub-URL-Präfixe entfernt werden.
|
||||
/// Normalisiert die Repository-Eingabe, indem führende/nachgestellte Schrägstriche,
|
||||
/// Query-Parameter, Fragmente und GitHub-URL-Präfixe entfernt werden.
|
||||
pub fn normalize_repo_name(input: &str) -> String {
|
||||
let trimmed = input.trim();
|
||||
let cleaned = trimmed
|
||||
.trim_start_matches("https://github.com/")
|
||||
.trim_start_matches("http://github.com/")
|
||||
.trim_start_matches("github.com/")
|
||||
.trim_end_matches(".git")
|
||||
.trim_matches('/');
|
||||
cleaned.to_string()
|
||||
clean_repo_input(input).to_string()
|
||||
}
|
||||
|
||||
/// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes.
|
||||
pub fn add_or_update_repo(&mut self, repo: RepoConfig) {
|
||||
if let Some(existing) = self.repositories.iter_mut().find(|r| r.name.eq_ignore_ascii_case(&repo.name)) {
|
||||
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)) {
|
||||
existing.name = repo.name;
|
||||
existing.include_prereleases = repo.include_prereleases;
|
||||
} else {
|
||||
self.repositories.push(repo);
|
||||
@@ -75,20 +71,20 @@ 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| !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| 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| 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);
|
||||
}
|
||||
}
|
||||
@@ -111,6 +107,10 @@ pub fn get_config_file_path() -> PathBuf {
|
||||
pub fn load_config() -> AppConfig {
|
||||
let mut config = config_ctdra::load_config::<AppConfig>();
|
||||
|
||||
for repo in &mut config.repositories {
|
||||
repo.name = AppConfig::normalize_repo_name(&repo.name);
|
||||
}
|
||||
|
||||
if let Ok(val) = std::env::var("GITEA_URL") {
|
||||
let val = sanitize_string(&val);
|
||||
if !val.is_empty() {
|
||||
|
||||
+1
-7
@@ -246,13 +246,7 @@ impl GitHubClient {
|
||||
|
||||
/// Hilfsfunktion zum Parsen von "owner/repo" aus einer Repository-Zeichenkette.
|
||||
pub fn parse_repo_owner_name(repo: &str) -> Result<(&str, &str)> {
|
||||
let cleaned = repo
|
||||
.trim()
|
||||
.trim_start_matches("https://github.com/")
|
||||
.trim_start_matches("http://github.com/")
|
||||
.trim_start_matches("github.com/")
|
||||
.trim_end_matches(".git")
|
||||
.trim_matches('/');
|
||||
let cleaned = crate::utils::clean_repo_input(repo);
|
||||
|
||||
let parts: Vec<&str> = cleaned.split('/').collect();
|
||||
if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
|
||||
|
||||
@@ -29,3 +29,36 @@ pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String
|
||||
*value = sanitize_string(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bereinigt eine Repository-Eingabe (URL oder Kurzform), indem Query-Parameter, Fragmente,
|
||||
/// URL-Schemata/Hosts (`github.com`, `www.github.com`, `git@github.com:`), `.git`-Endungen
|
||||
/// und umgebende Schrägstriche entfernt werden.
|
||||
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('\''))
|
||||
{
|
||||
if trimmed.len() >= 2 {
|
||||
trimmed = trimmed[1..trimmed.len() - 1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
let without_query_or_fragment = match trimmed.find(|c| c == '?' || c == '#') {
|
||||
Some(idx) => &trimmed[..idx],
|
||||
None => trimmed,
|
||||
};
|
||||
|
||||
let mut cleaned = without_query_or_fragment
|
||||
.trim_start_matches("git@github.com:")
|
||||
.trim_start_matches("https://")
|
||||
.trim_start_matches("http://")
|
||||
.trim_start_matches("www.github.com/")
|
||||
.trim_start_matches("github.com/")
|
||||
.trim_matches('/');
|
||||
|
||||
if let Some(stripped) = cleaned.strip_suffix(".git") {
|
||||
cleaned = stripped.trim_matches('/');
|
||||
}
|
||||
|
||||
cleaned
|
||||
}
|
||||
|
||||
+60
-5
@@ -1,5 +1,5 @@
|
||||
use mirror_package::config::{AppConfig, RepoConfig, load_config};
|
||||
use mirror_package::utils::{sanitize_env_vars, sanitize_string};
|
||||
use mirror_package::utils::{clean_repo_input, sanitize_env_vars, sanitize_string};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
@@ -65,20 +65,72 @@ fn test_sanitize_string() {
|
||||
assert_eq!(sanitize_string(" \"world\" "), "world");
|
||||
}
|
||||
|
||||
#[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("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"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager.git?tab=readme-ov-file"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager#readme"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file#install"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("raspberrypi/rpi-imager?tab=readme-ov-file"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("\"https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file\""),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_repo_name() {
|
||||
let input = "https://github.com/owner/repo.git";
|
||||
let normalized = AppConfig::normalize_repo_name(input);
|
||||
assert_eq!(normalized, "owner/repo");
|
||||
|
||||
let input_with_query = "https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file";
|
||||
assert_eq!(
|
||||
AppConfig::normalize_repo_name(input_with_query),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_or_update_repo() {
|
||||
let mut config = AppConfig::default();
|
||||
let repo = RepoConfig::new("owner/repo", true);
|
||||
let repo = RepoConfig::new("https://github.com/owner/repo?tab=readme-ov-file", true);
|
||||
config.add_or_update_repo(repo.clone());
|
||||
assert_eq!(config.repositories.len(), 1);
|
||||
assert_eq!(config.repositories[0], repo);
|
||||
assert_eq!(config.repositories[0].name, "owner/repo");
|
||||
assert_eq!(config.repositories[0].include_prereleases, true);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -86,7 +138,7 @@ fn test_remove_repo() {
|
||||
let mut config = AppConfig::default();
|
||||
let repo = RepoConfig::new("owner/repo", true);
|
||||
config.add_or_update_repo(repo.clone());
|
||||
assert!(config.remove_repo("owner/repo"));
|
||||
assert!(config.remove_repo("https://github.com/owner/repo?tab=readme-ov-file"));
|
||||
assert!(config.repositories.is_empty());
|
||||
}
|
||||
|
||||
@@ -95,7 +147,10 @@ fn test_find_repo() {
|
||||
let mut config = AppConfig::default();
|
||||
let repo = RepoConfig::new("owner/repo", true);
|
||||
config.add_or_update_repo(repo.clone());
|
||||
assert_eq!(config.find_repo("owner/repo"), Some(&repo));
|
||||
assert_eq!(
|
||||
config.find_repo("https://github.com/owner/repo?tab=readme-ov-file"),
|
||||
Some(&RepoConfig::new("owner/repo", true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -64,6 +64,18 @@ fn test_parse_repo_owner_name() {
|
||||
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(),
|
||||
("raspberrypi", "rpi-imager")
|
||||
);
|
||||
assert_eq!(
|
||||
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install").unwrap(),
|
||||
("raspberrypi", "rpi-imager")
|
||||
);
|
||||
assert_eq!(
|
||||
parse_repo_owner_name("raspberrypi/rpi-imager?tab=readme-ov-file").unwrap(),
|
||||
("raspberrypi", "rpi-imager")
|
||||
);
|
||||
|
||||
assert!(parse_repo_owner_name("invalid_repo").is_err());
|
||||
assert!(parse_repo_owner_name("invalid/repo/extra").is_err());
|
||||
|
||||
Reference in New Issue
Block a user