1use std::path::PathBuf;
2use gix::bstr::ByteSlice;
3use gix::remote::fetch::Shallow;
4use std::path::Path;
5use std::sync::atomic::AtomicBool;
6use gix::clone::PrepareFetch;
7use gix::{create, open, ThreadSafeRepository};
8use crate::Package;
9use crate::GUIX_REPO_URL;
10
11pub type Error = Box<dyn std::error::Error + Send + Sync>;
12
13pub struct Index {
15 repo: ThreadSafeRepository,
16 path: PathBuf,
17}
18
19impl Index {
20 #[inline]
28 pub fn new(local_temp_dir: impl AsRef<Path>) -> Result<Self, Error> {
29 let path = local_temp_dir.as_ref().join("git.savannah.gnu.org-git-guix.git");
30 if !path.exists() {
31 let stop = AtomicBool::new(false);
32 Self::do_fetch(&path, &stop)?;
33 }
34 let repo = ThreadSafeRepository::open_opts(&path, open::Options::isolated())?;
35
36 Ok(Self {
37 repo, path,
38 })
39 }
40
41 #[inline(never)]
42 #[cold]
43 fn do_fetch(path: &Path, stop: &AtomicBool) -> Result<(), Error> {
44 let mut pre_fetch = PrepareFetch::new(GUIX_REPO_URL, path, create::Kind::Bare, create::Options::default(), open::Options::isolated())?
45 .with_shallow(Shallow::DepthAtRemote(1.try_into().unwrap()));
46 pre_fetch.fetch_only(gix::progress::Discard, stop)?;
47 Ok(())
48 }
49
50 #[inline]
58 pub fn update(&self, stop_if_true: Option<&AtomicBool>) -> Result<(), Error> {
59 Self::do_fetch(&self.path, stop_if_true.unwrap_or(&AtomicBool::new(false)))
60 }
61
62 #[inline(never)]
64 pub fn list_all(&self) -> Result<Vec<(String, Vec<Package<String>>)>, Error> {
65 let repo = self.repo.to_thread_local();
66 let head = repo.head_commit()?;
67 let root = head.tree()?;
68 let packages_dir = root.lookup_entry_by_path("gnu/packages")?.ok_or("missing gnu/packages")?.object()?.peel_to_tree()?;
69
70 packages_dir.iter().filter_map(|entry| {
71 let entry = match entry {
72 Ok(entry) => entry,
73 Err(e) => return Some(Err(Error::from(e))),
74 };
75 let name = entry.filename().to_str().ok()?.strip_suffix(".scm")?.to_string();
76 Some(Ok((name, entry)))
77 }).map(|res| {
78 let (name, entry) = res?;
79 let blob = entry.object()?.peel_to_kind(gix::objs::Kind::Blob)?;
80 let scm = String::from_utf8_lossy(&blob.data);
81 if scm.trim_start().is_empty() {
82 return Ok((name, vec![]));
83 }
84 let packages = crate::parse_scm(&scm)?.map(|p| Package {
85 name: p.name.to_owned(),
86 version: p.version.to_owned(),
87 }).collect();
88 Ok((name, packages))
89 })
90 .filter(|r| r.as_ref().map_or(true, |(_, pkgs)| !pkgs.is_empty()))
91 .collect()
92 }
93}