Skip to content

Commit 1ce7156

Browse files
committed
feat(alpm-utils): Add orphan detection to alpm-utils
Adds is_orphan() function and AlpmExt trait with find_orphans() method for detecting packages installed as dependencies that are no longer required by any other package. Signed-off-by: Josephine Pfeiffer <[email protected]>
1 parent 4f58a5d commit 1ce7156

File tree

1 file changed

+73
-2
lines changed

1 file changed

+73
-2
lines changed

alpm-utils/src/db.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
1-
use alpm::{AlpmList, AlpmListMut, Db, Package, Result};
1+
use alpm::{Alpm, AlpmList, AlpmListMut, Db, Package, PackageReason, Result};
22

33
use crate::AsTarg;
44

5-
/// Extention for AlpmList<Db>
5+
/// Check if a package is an orphan.
6+
pub fn is_orphan(pkg: &Package) -> bool {
7+
pkg.reason() == PackageReason::Depend
8+
&& pkg.required_by().is_empty()
9+
&& pkg.optional_for().is_empty()
10+
}
11+
12+
/// Extension trait for Alpm providing orphan detection.
13+
pub trait OrphanExt {
14+
/// Find all orphan packages in the local database.
15+
fn find_orphans(&self) -> impl Iterator<Item = &Package>;
16+
}
17+
18+
impl OrphanExt for Alpm {
19+
fn find_orphans(&self) -> impl Iterator<Item = &Package> {
20+
self.localdb().pkgs().iter().filter(|pkg| is_orphan(pkg))
21+
}
22+
}
23+
24+
/// Extension trait for `AlpmList<Db>`.
625
pub trait DbListExt<'a> {
726
/// Similar to find_satisfier() but expects a Target instead of a &str.
827
fn find_target_satisfier<T: AsTarg>(&self, target: T) -> Option<&'a Package>;
@@ -62,3 +81,55 @@ impl<'a> DbListExt<'a> for AlpmList<'_, &'a Db> {
6281
pkg.ok_or(alpm::Error::PkgNotFound)
6382
}
6483
}
84+
85+
#[cfg(test)]
86+
mod tests {
87+
use super::*;
88+
89+
fn test_handle() -> Alpm {
90+
Alpm::new("/", "../alpm/tests/db").unwrap()
91+
}
92+
93+
#[test]
94+
fn test_is_orphan_dependency_with_no_dependents() {
95+
let handle = test_handle();
96+
let localdb = handle.localdb();
97+
98+
// argon2 is installed as dependency (REASON=1) and has no required_by in test db
99+
let pkg = localdb.pkg("argon2").unwrap();
100+
assert_eq!(pkg.reason(), PackageReason::Depend);
101+
102+
// Whether it's an orphan depends on if anything requires it
103+
let result = is_orphan(&pkg);
104+
// The function should return true if no packages require or optionally depend on it
105+
assert!(result == (pkg.required_by().is_empty() && pkg.optional_for().is_empty()));
106+
}
107+
108+
#[test]
109+
fn test_is_orphan_explicit_package() {
110+
let handle = test_handle();
111+
let localdb = handle.localdb();
112+
113+
// Find a package that's explicitly installed (if any in test db)
114+
for pkg in localdb.pkgs().iter() {
115+
if pkg.reason() == PackageReason::Explicit {
116+
// Explicit packages should never be orphans
117+
assert!(!is_orphan(&pkg));
118+
return;
119+
}
120+
}
121+
}
122+
123+
#[test]
124+
fn test_find_orphans_iterator() {
125+
let handle = test_handle();
126+
127+
// find_orphans should return an iterator
128+
let orphans: Vec<_> = handle.find_orphans().collect();
129+
130+
// All returned packages should satisfy is_orphan
131+
for pkg in &orphans {
132+
assert!(is_orphan(pkg));
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)