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 let _ = repo;
60 }
61}