char/sorcery

static-files based git repo viewer

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

Charlotte Somexperiment: support sha-256 oids in git repos42f80d8

main
2.6 KiB94 linesraw
1use std::fs;
2use std::ops::Deref;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5use std::sync::{Arc, OnceLock};
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use anyhow::{Result, ensure};
9
10use crate::highlight::Cache;
11
12/// A fresh directory under the system temp dir, self-removes on drop.
13pub struct TempDir(PathBuf);
14
15impl TempDir {
16    pub fn new(label: &str) -> Self {
17        let nanos = SystemTime::now()
18            .duration_since(UNIX_EPOCH)
19            .expect("clock before epoch")
20            .as_nanos();
21        let path = std::env::temp_dir().join(format!(
22            "sorcery-{label}-{}-{nanos}",
23            std::process::id(),
24        ));
25        fs::create_dir_all(&path).expect("creating temp dir");
26        TempDir(path)
27    }
28}
29
30impl Deref for TempDir {
31    type Target = Path;
32
33    fn deref(&self) -> &Path {
34        &self.0
35    }
36}
37
38impl Drop for TempDir {
39    fn drop(&mut self) {
40        let _ = fs::remove_dir_all(&self.0);
41    }
42}
43
44/// Runs git in `repo` isolated from the developer's own config (signing,
45/// hooks, templates) and returns trimmed stdout.
46pub fn git(repo: &Path, args: &[&str]) -> Result<String> {
47    let output = Command::new("git")
48        .current_dir(repo)
49        .args(args)
50        .env("GIT_CONFIG_GLOBAL", "/dev/null")
51        .env("GIT_CONFIG_NOSYSTEM", "1")
52        .env("GIT_AUTHOR_NAME", "test")
53        .env("GIT_AUTHOR_EMAIL", "test@example")
54        .env("GIT_COMMITTER_NAME", "test")
55        .env("GIT_COMMITTER_EMAIL", "test@example")
56        .output()?;
57    ensure!(
58        output.status.success(),
59        "git {}: {}",
60        args.join(" "),
61        String::from_utf8_lossy(&output.stderr),
62    );
63    Ok(String::from_utf8(output.stdout)?.trim().to_owned())
64}
65
66pub fn init_repo(repo: &Path) -> Result<()> {
67    fs::create_dir_all(repo)?;
68    git(repo, &["init", "-q", "-b", "main"])?;
69    Ok(())
70}
71
72pub fn init_sha256_repo(repo: &Path) -> Result<()> {
73    fs::create_dir_all(repo)?;
74    git(
75        repo,
76        &["init", "-q", "-b", "main", "--object-format=sha256"],
77    )?;
78    Ok(())
79}
80
81/// Stages everything and commits it, returning the new commit's hex id.
82pub fn commit(repo: &Path, message: &str) -> Result<String> {
83    git(repo, &["add", "-A"])?;
84    git(repo, &["commit", "-qm", message])?;
85    git(repo, &["rev-parse", "HEAD"])
86}
87
88/// `Cache::new` claims a process-wide slot, so all tests share one.
89pub fn grammar_cache() -> Arc<Cache> {
90    static CACHE: OnceLock<Arc<Cache>> = OnceLock::new();
91    CACHE
92        .get_or_init(|| Arc::new(Cache::new(std::env::temp_dir().join("sorcery-test-grammars"))))
93        .clone()
94}