char/sorcery

static-files based git repo viewer

git clone https://git.t4t.associates/char/sorcery

Charlotte Sombetter performance with large reposf6e267c

main
2.0 KiB66 linesraw
1use std::collections::BTreeMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use anyhow::{Context, Result};
6
7#[derive(Clone, Debug)]
8pub struct Repository {
9    pub user: String,
10    pub name: String,
11    pub path: PathBuf,
12    pub description: Option<String>,
13}
14
15/// Discover git repositories exactly two levels beneath `root`:
16/// `root/user/repo` or `root/user/repo.git`.
17pub fn discover(root: &Path) -> Result<Vec<Repository>> {
18    let root = root
19        .canonicalize()
20        .with_context(|| format!("canonicalizing repository root {}", root.display()))?;
21    let mut found = BTreeMap::new();
22
23    for user in sorted_dirs(&root)? {
24        let Some(user_name) = user.file_name().and_then(|n| n.to_str()) else {
25            continue;
26        };
27        if user_name.starts_with('.') {
28            continue;
29        }
30        for path in sorted_dirs(&user)? {
31            let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
32                continue;
33            };
34            if file_name.starts_with('.') {
35                continue;
36            }
37            let name = file_name.strip_suffix(".git").unwrap_or(file_name);
38            let Ok(repo) = gix::open(&path) else {
39                continue;
40            };
41            if name.is_empty() {
42                continue;
43            }
44            let description = crate::generate::description(&repo);
45            found.entry((user_name.to_owned(), name.to_owned())).or_insert(Repository {
46                user: user_name.to_owned(),
47                name: name.to_owned(),
48                path,
49                description,
50            });
51        }
52    }
53
54    Ok(found.into_values().collect())
55}
56
57fn sorted_dirs(path: &Path) -> Result<Vec<PathBuf>> {
58    let mut dirs = fs::read_dir(path)
59        .with_context(|| format!("reading {}", path.display()))?
60        .filter_map(|entry| entry.ok())
61        .filter_map(|entry| entry.file_type().ok()?.is_dir().then_some(entry.path()))
62        .collect::<Vec<_>>();
63    dirs.sort();
64    Ok(dirs)
65}
66