index_guix/
git.rs

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
13/// Reads Guix's repo
14pub struct Index {
15    repo: ThreadSafeRepository,
16    path: PathBuf,
17}
18
19impl Index {
20    /// Specify path where to place repo checkout (e.g. `"/var/tmp"`)
21    ///
22    /// This will clone the repo on first use. It may take a while!
23    ///
24    /// <div class="warning"><a href="https://github.com/Byron/gitoxide/issues/1026">
25    /// This call may deadlock forever due to gix-lock.
26    /// </a></div>
27    #[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    /// Update the repository
51    ///
52    /// You can flip `stop_if_true` to abort the fetch.
53    ///
54    /// <div class="warning"><a href="https://github.com/Byron/gitoxide/issues/1026">
55    /// This call may deadlock forever due to gix-lock.
56    /// </a></div>
57    #[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    /// Parse all crates to be packaged in Guix
63    #[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}