1
use crate::git_signatures::{check_signatures, get_signatures};
2
use git2::Repository;
3
use sequoia_openpgp::Cert;
4
use serde::Serialize;
5
use std::collections::{btree_map::Entry, BTreeMap, BTreeSet};
6

            
7
type Result<T> = anyhow::Result<T>;
8

            
9
8
#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
10
pub struct AuthorData {
11
    names: BTreeSet<String>,
12
    number_of_commits: usize,
13
}
14

            
15
type Email = String;
16

            
17
4
#[derive(Serialize, PartialEq)]
18
pub struct Commit {
19
    time: i64,
20
    sha1: String,
21
    email: Option<Email>,
22
    name: Option<String>,
23
}
24

            
25
2
#[derive(Serialize, PartialEq)]
26
pub struct RepositoryAnalysis {
27
    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
28
    signatures: BTreeSet<SignatureAnalysis>,
29
    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
30
    missing_keys: BTreeSet<String>,
31
    default_branch: Option<String>,
32
    first_commit: Option<Commit>,
33
    latest_commit: Option<Commit>,
34
    number_of_commits: usize,
35
    authors: BTreeMap<Email, AuthorData>,
36
}
37

            
38
#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
39
pub struct SignatureAnalysis {
40
    handle: String,
41
    #[serde(skip_serializing_if = "BTreeSet::is_empty")]
42
    email: BTreeSet<String>,
43
}
44

            
45
1116
fn add_author(commit: &git2::Commit, authors: &mut BTreeMap<Email, AuthorData>) {
46
1116
    let author = commit.author();
47
1116
    if let (Some(name), Some(email)) = (author.name(), author.email()) {
48
1116
        let email = email.to_string();
49
1116
        let name = name.to_string();
50
1116
        match authors.entry(email) {
51
1068
            Entry::Occupied(mut e) => {
52
1068
                let e = e.get_mut();
53
1068
                e.number_of_commits += 1;
54
1068
                e.names.insert(name);
55
1068
            }
56
48
            Entry::Vacant(e) => {
57
48
                let mut names = BTreeSet::new();
58
48
                names.insert(name);
59
48
                e.insert(AuthorData {
60
48
                    number_of_commits: 1,
61
48
                    names,
62
48
                });
63
48
            }
64
        }
65
    }
66
1116
}
67

            
68
24
fn get_commit(commit: &git2::Commit) -> Commit {
69
24
    let author = commit.author();
70
24
    Commit {
71
24
        time: commit.time().seconds(),
72
24
        sha1: commit.id().to_string(),
73
24
        email: author.email().map(|e| e.to_string()),
74
24
        name: author.name().map(|e| e.to_string()),
75
24
    }
76
24
}
77

            
78
12
pub fn analyze_repository(certs: &[Cert], repo: &Repository) -> Result<RepositoryAnalysis> {
79
12
    let signatures = get_signatures(repo)?;
80
12
    let r = check_signatures(certs, signatures)?;
81
12
    let mut signature_analyses = BTreeSet::new();
82
12
    let mut authors = BTreeMap::new();
83
12
    for key in r.found_keys {
84
        let mut analysis = SignatureAnalysis {
85
            handle: format!("{}", key.0),
86
            email: BTreeSet::new(),
87
        };
88
        for id in key.1.userids() {
89
            if let Ok(Some(email)) = id.email_normalized() {
90
                analysis.email.insert(email);
91
            }
92
        }
93
        signature_analyses.insert(analysis);
94
    }
95
    // since the checkout is untouched, head is the default branch
96
12
    let mut default_branch = None;
97
12
    let mut first_commit = None;
98
12
    let mut latest_commit = None;
99
12
    let mut number_of_commits = 0;
100
12
    if let Ok(head) = repo.head() {
101
12
        default_branch = head
102
12
            .name()
103
12
            .and_then(|n| n.strip_prefix("refs/heads/"))
104
12
            .map(|n| n.to_string());
105
12
        if let Ok(mut commit) = head.peel_to_commit() {
106
12
            number_of_commits = 1;
107
12
            latest_commit = Some(get_commit(&commit));
108
12
            add_author(&commit, &mut authors);
109
1116
            while let Some(p) = commit.parents().next() {
110
1104
                number_of_commits += 1;
111
1104
                commit = p;
112
1104
                add_author(&commit, &mut authors);
113
1104
            }
114
12
            first_commit = Some(get_commit(&commit));
115
        }
116
    }
117
    //repo.head().
118
12
    Ok(RepositoryAnalysis {
119
12
        signatures: signature_analyses,
120
24
        missing_keys: r.missing_keys.iter().map(|k| format!("{}", k)).collect(),
121
12
        default_branch,
122
12
        first_commit,
123
12
        latest_commit,
124
12
        number_of_commits,
125
12
        authors,
126
12
    })
127
12
}