diff --git a/src/coordinator.rs b/src/coordinator.rs index 03f0281..ac89d4a 100644 --- a/src/coordinator.rs +++ b/src/coordinator.rs @@ -14,6 +14,9 @@ pub fn scan_package(package_name: &str, json: bool, verbose: bool) -> Result Result { Ok(PackageContext { name: package_name.to_string(), + package_base: Some(package_base.to_string()), metadata: Some(metadata), pkgbuild_content, install_script_content, @@ -131,6 +135,7 @@ pub fn build_context_prefetched( Ok(PackageContext { name: package_name.to_string(), + package_base: Some(package_base.to_string()), metadata: Some(metadata), pkgbuild_content: pkgbuild, install_script_content: install, @@ -147,6 +152,7 @@ pub fn build_context_prefetched( pub fn scan_pkgbuild(name: &str, pkgbuild_content: &str) -> ScanResult { let ctx = PackageContext { name: name.to_string(), + package_base: None, metadata: None, pkgbuild_content: Some(pkgbuild_content.to_string()), install_script_content: None, diff --git a/src/features/aur_comments_analysis/mod.rs b/src/features/aur_comments_analysis/mod.rs index 9a0e858..6b6f99c 100644 --- a/src/features/aur_comments_analysis/mod.rs +++ b/src/features/aur_comments_analysis/mod.rs @@ -66,6 +66,7 @@ mod tests { fn analyze_comments(comments: Vec<&str>) -> Vec { let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: None, diff --git a/src/features/bin_source_verification/mod.rs b/src/features/bin_source_verification/mod.rs index aea9038..555ed57 100644 --- a/src/features/bin_source_verification/mod.rs +++ b/src/features/bin_source_verification/mod.rs @@ -206,6 +206,7 @@ mod tests { fn analyze(name: &str, url: Option<&str>, pkgbuild: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: name.into(), metadata: Some(make_pkg(url)), pkgbuild_content: Some(pkgbuild.into()), diff --git a/src/features/checksum_analysis/mod.rs b/src/features/checksum_analysis/mod.rs index 641784f..8011ada 100644 --- a/src/features/checksum_analysis/mod.rs +++ b/src/features/checksum_analysis/mod.rs @@ -168,6 +168,7 @@ mod tests { fn analyze(name: &str, content: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: name.into(), metadata: None, pkgbuild_content: Some(content.into()), @@ -228,6 +229,7 @@ mod tests { #[test] fn checksum_mismatch_points_and_wording() { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: Some("source=('a.tar.gz' 'b.tar.gz')\nsha256sums=('abc123')\n".into()), @@ -282,6 +284,7 @@ mod tests { fn mismatch_emits_only_one_signal() { // Multiple arch suffixes with mismatches should only emit one P-CHECKSUM-MISMATCH let ctx = PackageContext { + package_base: None, name: "test-bin".into(), metadata: None, pkgbuild_content: Some("source_x86_64=('a' 'b' 'c')\nsha256sums_x86_64=('h1')\nsource_aarch64=('d' 'e' 'f')\nsha256sums_aarch64=('h2')\n".into()), diff --git a/src/features/git_history_analysis/mod.rs b/src/features/git_history_analysis/mod.rs index a5a750b..737c1d7 100644 --- a/src/features/git_history_analysis/mod.rs +++ b/src/features/git_history_analysis/mod.rs @@ -131,6 +131,7 @@ mod tests { fn single_commit() { let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: None, @@ -150,6 +151,7 @@ mod tests { fn malicious_diff() { let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: None, @@ -172,6 +174,7 @@ mod tests { fn author_change() { let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: None, @@ -194,6 +197,7 @@ mod tests { fn no_diff_introduced_when_prior_has_net() { let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: None, diff --git a/src/features/github_stars/mod.rs b/src/features/github_stars/mod.rs index 4788d78..fc50e88 100644 --- a/src/features/github_stars/mod.rs +++ b/src/features/github_stars/mod.rs @@ -59,6 +59,7 @@ mod tests { fn analyze_stars(stars: Option, not_found: bool) -> Vec { let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: None, diff --git a/src/features/gtfobins_analysis/mod.rs b/src/features/gtfobins_analysis/mod.rs index dc72d24..698f697 100644 --- a/src/features/gtfobins_analysis/mod.rs +++ b/src/features/gtfobins_analysis/mod.rs @@ -55,6 +55,7 @@ mod tests { fn analyze(content: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: Some(content.into()), @@ -947,6 +948,7 @@ build() { fn analyze_install(content: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: None, diff --git a/src/features/install_script_analysis/mod.rs b/src/features/install_script_analysis/mod.rs index a1d2e99..68eeb0a 100644 --- a/src/features/install_script_analysis/mod.rs +++ b/src/features/install_script_analysis/mod.rs @@ -42,6 +42,7 @@ mod tests { fn analyze(content: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: None, diff --git a/src/features/maintainer_analysis/mod.rs b/src/features/maintainer_analysis/mod.rs index 7b9fa6f..ff76d3d 100644 --- a/src/features/maintainer_analysis/mod.rs +++ b/src/features/maintainer_analysis/mod.rs @@ -104,6 +104,7 @@ mod tests { let ts = now(); let pkg = make_pkg("evil", ts - 86400); // 1 day old let ctx = PackageContext { + package_base: None, name: "evil".into(), metadata: Some(pkg.clone()), pkgbuild_content: None, @@ -124,6 +125,7 @@ mod tests { let ts = now(); let pkg = make_pkg("old-pkg", ts - 90 * 86400); // 90 days old let ctx = PackageContext { + package_base: None, name: "old-pkg".into(), metadata: Some(pkg.clone()), pkgbuild_content: None, @@ -148,6 +150,7 @@ mod tests { make_pkg("pkg3", ts - 10800), ]; let ctx = PackageContext { + package_base: None, name: "pkg1".into(), metadata: Some(pkgs[0].clone()), pkgbuild_content: None, diff --git a/src/features/metadata_analysis/mod.rs b/src/features/metadata_analysis/mod.rs index cf847f2..1ad4cf2 100644 --- a/src/features/metadata_analysis/mod.rs +++ b/src/features/metadata_analysis/mod.rs @@ -124,6 +124,7 @@ mod tests { fn analyze_meta(meta: AurPackage) -> Vec { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: Some(meta), pkgbuild_content: None, diff --git a/src/features/name_analysis/mod.rs b/src/features/name_analysis/mod.rs index 2fc8fba..99a6d18 100644 --- a/src/features/name_analysis/mod.rs +++ b/src/features/name_analysis/mod.rs @@ -192,6 +192,7 @@ mod tests { fn analyze(name: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: name.into(), metadata: None, pkgbuild_content: None, @@ -209,6 +210,7 @@ mod tests { fn analyze_with_votes(name: &str, votes: u32) -> Vec { use crate::shared::models::AurPackage; let ctx = PackageContext { + package_base: None, name: name.into(), metadata: Some(AurPackage { name: name.into(), diff --git a/src/features/orphan_takeover_analysis/mod.rs b/src/features/orphan_takeover_analysis/mod.rs index 9755dfe..a34e9ed 100644 --- a/src/features/orphan_takeover_analysis/mod.rs +++ b/src/features/orphan_takeover_analysis/mod.rs @@ -123,6 +123,7 @@ mod tests { fn same_submitter_maintainer() { let ts = now(); let ctx = PackageContext { + package_base: None, name: "pkg".into(), metadata: Some(make_pkg("alice", Some("alice"), ts - 180 * 86400)), pkgbuild_content: None, @@ -141,6 +142,7 @@ mod tests { fn no_submitter_field() { let ts = now(); let ctx = PackageContext { + package_base: None, name: "pkg".into(), metadata: Some(make_pkg("alice", None, ts - 180 * 86400)), pkgbuild_content: None, @@ -159,6 +161,7 @@ mod tests { fn submitter_changed() { let ts = now(); let ctx = PackageContext { + package_base: None, name: "pkg".into(), metadata: Some(make_pkg("bob", Some("alice"), ts - 180 * 86400)), pkgbuild_content: None, @@ -179,6 +182,7 @@ mod tests { fn orphan_takeover_composite() { let ts = now(); let ctx = PackageContext { + package_base: None, name: "pkg".into(), metadata: Some(make_pkg("attacker", Some("original"), ts - 365 * 86400)), pkgbuild_content: None, @@ -202,6 +206,7 @@ mod tests { fn new_package_no_composite() { let ts = now(); let ctx = PackageContext { + package_base: None, name: "pkg".into(), metadata: Some(make_pkg("bob", Some("alice"), ts - 30 * 86400)), pkgbuild_content: None, @@ -225,6 +230,7 @@ mod tests { fn same_git_author_no_composite() { let ts = now(); let ctx = PackageContext { + package_base: None, name: "pkg".into(), metadata: Some(make_pkg("bob", Some("alice"), ts - 365 * 86400)), pkgbuild_content: None, diff --git a/src/features/pkgbuild_analysis/mod.rs b/src/features/pkgbuild_analysis/mod.rs index 175fede..f1afa98 100644 --- a/src/features/pkgbuild_analysis/mod.rs +++ b/src/features/pkgbuild_analysis/mod.rs @@ -42,6 +42,7 @@ mod tests { fn analyze(content: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: Some(content.into()), diff --git a/src/features/pkgbuild_diff_analysis/mod.rs b/src/features/pkgbuild_diff_analysis/mod.rs index 082756a..6bd43f3 100644 --- a/src/features/pkgbuild_diff_analysis/mod.rs +++ b/src/features/pkgbuild_diff_analysis/mod.rs @@ -212,6 +212,7 @@ mod tests { fn analyze(new: &str, old: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: Some(new.to_string()), @@ -233,6 +234,7 @@ mod tests { #[test] fn no_prior_pkgbuild() { let ctx = PackageContext { + package_base: None, name: "test".into(), metadata: None, pkgbuild_content: Some("pkgname=test".into()), diff --git a/src/features/shell_analysis/mod.rs b/src/features/shell_analysis/mod.rs index 8273f87..8c93df1 100644 --- a/src/features/shell_analysis/mod.rs +++ b/src/features/shell_analysis/mod.rs @@ -557,6 +557,7 @@ mod tests { fn analyze(content: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: Some(content.into()), @@ -849,6 +850,7 @@ package() { fn analyze_install(content: &str) -> Vec { let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: None, diff --git a/src/features/source_url_analysis/mod.rs b/src/features/source_url_analysis/mod.rs index ef017bc..64baba4 100644 --- a/src/features/source_url_analysis/mod.rs +++ b/src/features/source_url_analysis/mod.rs @@ -55,6 +55,7 @@ mod tests { fn analyze(source_url: &str) -> Vec { let content = format!("pkgname=test\nsource=('{source_url}')\n"); let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: Some(content), @@ -149,6 +150,7 @@ mod tests { fn ignores_comments() { let content = "# source from https://pastebin.com/abc\nsource=('https://github.com/user/repo.tar.gz')\n"; let ctx = PackageContext { + package_base: None, name: "test-pkg".into(), metadata: None, pkgbuild_content: Some(content.into()), diff --git a/src/main.rs b/src/main.rs index 1e5dc8d..d4a5fb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,6 +83,11 @@ enum Commands { #[arg(long)] category: Option, }, + /// Remove cached git clones (all packages, or a specific one) + Clean { + /// Package name to remove from cache (omit to clear entire cache) + package: Option, + }, } fn main() { @@ -103,6 +108,7 @@ fn main() { Commands::Signals { json } => cmd_signals(json), Commands::Ignore { signal_id, category } => cmd_ignore(signal_id.as_deref(), category.as_deref()), Commands::Unignore { signal_id, category } => cmd_unignore(signal_id.as_deref(), category.as_deref()), + Commands::Clean { package } => cmd_clean(package.as_deref()), }; process::exit(exit_code); @@ -232,7 +238,12 @@ fn cmd_scan_all_installed(jobs: usize, json: bool, verbose: bool, flagged_only: .unwrap_or_default(); match clone_with_retry(name, meta, maint_pkgs) { - Ok(ctx) => Ok(coordinator::run_analysis_with_config(&ctx, &config)), + Ok(ctx) => { + let scan = coordinator::run_analysis_with_config(&ctx, &config); + let pkg_base = ctx.package_base.as_deref().unwrap_or(name); + let _ = shared::cache::clean_git_cache(Some(pkg_base)); + Ok(scan) + } Err(e) => Err(e), } } else { @@ -505,3 +516,20 @@ fn cmd_unignore(signal_id: Option<&str>, category: Option<&str>) -> i32 { } } } + +fn cmd_clean(package: Option<&str>) -> i32 { + match shared::cache::clean_git_cache(package) { + Ok(()) => { + if let Some(pkg) = package { + eprintln!("Cleaned cache for: {pkg}"); + } else { + eprintln!("Cleaned all cached AUR git repos"); + } + 0 + } + Err(e) => { + eprintln!("Error cleaning cache: {e}"); + 1 + } + } +} diff --git a/src/shared/cache.rs b/src/shared/cache.rs index 5ec8547..98d8f34 100644 --- a/src/shared/cache.rs +++ b/src/shared/cache.rs @@ -14,6 +14,25 @@ pub fn git_cache_dir() -> PathBuf { dir } +pub fn clean_git_cache(package: Option<&str>) -> std::io::Result<()> { + let git_dir = git_cache_dir(); + match package { + Some(pkg) => { + let repo_path = git_dir.join(pkg); + if repo_path.exists() { + std::fs::remove_dir_all(&repo_path)?; + } + } + None => { + if git_dir.exists() { + std::fs::remove_dir_all(&git_dir)?; + std::fs::create_dir_all(&git_dir)?; + } + } + } + Ok(()) +} + fn dirs_or_default() -> PathBuf { if let Ok(xdg) = std::env::var("XDG_CACHE_HOME") { PathBuf::from(xdg).join("traur") diff --git a/src/shared/models.rs b/src/shared/models.rs index 614a3a9..ca5b255 100644 --- a/src/shared/models.rs +++ b/src/shared/models.rs @@ -3,6 +3,7 @@ use serde::Deserialize; /// All data a feature needs to run its analysis. pub struct PackageContext { pub name: String, + pub package_base: Option, pub metadata: Option, pub pkgbuild_content: Option, pub install_script_content: Option,