codealong 0.1.1

Git analytics
Documentation
use git2::{Repository, Revwalk};

use crate::analyze_opts::AnalyzeOpts;
use crate::commit_analyzer::CommitAnalyzer;
use crate::error::*;
use crate::identity::Identity;
use crate::repo::Repo;
use crate::repo_config::RepoConfig;
use crate::slog::Logger;
use crate::utils::convert_time;

pub struct RepoAnalyzer {
    repo: Repository,
    config: RepoConfig,
    logger: Logger,
}

impl RepoAnalyzer {
    pub fn new(repo: Repository, config: RepoConfig, parent_logger: &Logger) -> RepoAnalyzer {
        RepoAnalyzer {
            repo,
            logger: parent_logger.new(o!("repo" => config.repo.name.to_owned())),
            config,
        }
    }

    pub fn analyze(
        &self,
        opts: AnalyzeOpts,
    ) -> Result<impl Iterator<Item = Result<CommitAnalyzer>>> {
        let mut revwalk = self.repo.revwalk()?;
        for reference in &self.config.repo.refs {
            if let Ok(_) = self.repo.find_reference(reference) {
                revwalk.push_ref(&reference)?;
            } else {
                warn!(
                    self.logger,
                    "Could not find reference: {}, using HEAD", reference
                );
                revwalk.push_head()?;
            }
        }
        Ok(AnalyzedRevwalk {
            repo: &self.repo,
            revwalk,
            config: &self.config,
            opts,
            logger: self.logger.clone(),
        })
    }

    pub fn guess_len(&self, opts: AnalyzeOpts) -> Result<usize> {
        Ok(self.analyze(opts)?.count())
    }

    pub fn from_repo(repo: &Repo, logger: &Logger) -> Result<Self> {
        Ok(Self::new(repo.repository()?, repo.config(), logger))
    }
}

pub struct AnalyzedRevwalk<'repo> {
    repo: &'repo Repository,
    revwalk: Revwalk<'repo>,
    config: &'repo RepoConfig,
    opts: AnalyzeOpts,
    logger: Logger,
}

impl<'repo> Iterator for AnalyzedRevwalk<'repo> {
    type Item = Result<CommitAnalyzer<'repo>>;

    fn next(&mut self) -> Option<Result<CommitAnalyzer<'repo>>> {
        loop {
            let rev = self.revwalk.next();
            match rev {
                None => break None,
                Some(rev) => {
                    let oid = rev.unwrap();
                    let commit = self.repo.find_commit(oid).unwrap();

                    if let Some(ref since) = self.opts.since {
                        let commit_time = convert_time(&commit.author().when());
                        if since > &commit_time {
                            continue;
                        }
                    }

                    if !self.opts.ignore_unknown_authors
                        || self
                            .config
                            .config
                            .is_known(&Identity::from(commit.author()))
                    {
                        let analyzer =
                            CommitAnalyzer::new(self.repo, commit, self.config, &self.logger);
                        break Some(Ok(analyzer));
                    }
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::repo_info::RepoInfo;
    use crate::test::build_test_logger;

    #[test]
    fn test_analyze() -> Result<()> {
        let repo = Repository::open("./fixtures/repos/simple")?;
        let config = RepoConfig {
            repo: RepoInfo {
                refs: vec!["refs/head".to_owned()],
                ..RepoInfo::default()
            },
            ..RepoConfig::default()
        };
        let opts = AnalyzeOpts {
            ignore_unknown_authors: false,
            since: None,
        };
        let analyzer = RepoAnalyzer::new(repo, config, &build_test_logger());
        assert!(analyzer.analyze(opts)?.count() >= 4);
        Ok(())
    }

    #[test]
    fn test_ignore_unknown_authors() -> Result<()> {
        let repo = Repository::open("./fixtures/repos/simple")?;
        let config = RepoConfig {
            repo: RepoInfo {
                refs: vec!["refs/head".to_owned()],
                ..RepoInfo::default()
            },
            ..RepoConfig::default()
        };
        let opts = AnalyzeOpts {
            ignore_unknown_authors: true,
            since: None,
        };
        let analyzer = RepoAnalyzer::new(repo, config, &build_test_logger());
        assert_eq!(analyzer.analyze(opts)?.count(), 0);
        Ok(())
    }
}