bim 0.1.0

A content-first static site generator for blogs, digital gardens, and knowledge bases
Documentation
use chrono::{DateTime, TimeZone, Utc};
use git2::Repository;
use std::path::Path;

pub struct GitRepo {
    repo: Repository,
}

impl GitRepo {
    pub fn open(path: &Path) -> Option<Self> {
        Repository::discover(path).ok().map(|repo| Self { repo })
    }

    pub fn last_modified(&self, path: &Path) -> Option<DateTime<Utc>> {
        let relative_path = self.make_relative(path)?;

        let mut revwalk = self.repo.revwalk().ok()?;
        revwalk.push_head().ok()?;
        revwalk.set_sorting(git2::Sort::TIME).ok()?;

        for oid in revwalk.flatten() {
            let commit = self.repo.find_commit(oid).ok()?;
            let tree = commit.tree().ok()?;

            if tree.get_path(Path::new(&relative_path)).is_ok() {
                let time = commit.time();
                let datetime = Utc.timestamp_opt(time.seconds(), 0).single()?;
                return Some(datetime);
            }
        }

        None
    }

    fn make_relative(&self, path: &Path) -> Option<String> {
        let workdir = self.repo.workdir()?;
        let abs_path = if path.is_absolute() {
            path.to_path_buf()
        } else {
            std::env::current_dir().ok()?.join(path)
        };

        abs_path
            .strip_prefix(workdir)
            .ok()
            .map(|p| p.to_string_lossy().to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_open_non_git_directory() {
        let repo = GitRepo::open(Path::new("/tmp"));
        // This may or may not be a git repo depending on the system
        // Just test that it doesn't panic
        let _ = repo;
    }
}