Skip to main content

bim/
git.rs

1use chrono::{DateTime, TimeZone, Utc};
2use git2::Repository;
3use std::path::Path;
4
5pub struct GitRepo {
6    repo: Repository,
7}
8
9impl GitRepo {
10    pub fn open(path: &Path) -> Option<Self> {
11        Repository::discover(path).ok().map(|repo| Self { repo })
12    }
13
14    pub fn last_modified(&self, path: &Path) -> Option<DateTime<Utc>> {
15        let relative_path = self.make_relative(path)?;
16
17        let mut revwalk = self.repo.revwalk().ok()?;
18        revwalk.push_head().ok()?;
19        revwalk.set_sorting(git2::Sort::TIME).ok()?;
20
21        for oid in revwalk.flatten() {
22            let commit = self.repo.find_commit(oid).ok()?;
23            let tree = commit.tree().ok()?;
24
25            if tree.get_path(Path::new(&relative_path)).is_ok() {
26                let time = commit.time();
27                let datetime = Utc.timestamp_opt(time.seconds(), 0).single()?;
28                return Some(datetime);
29            }
30        }
31
32        None
33    }
34
35    fn make_relative(&self, path: &Path) -> Option<String> {
36        let workdir = self.repo.workdir()?;
37        let abs_path = if path.is_absolute() {
38            path.to_path_buf()
39        } else {
40            std::env::current_dir().ok()?.join(path)
41        };
42
43        abs_path
44            .strip_prefix(workdir)
45            .ok()
46            .map(|p| p.to_string_lossy().to_string())
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_open_non_git_directory() {
56        let repo = GitRepo::open(Path::new("/tmp"));
57        // This may or may not be a git repo depending on the system
58        // Just test that it doesn't panic
59        let _ = repo;
60    }
61}