1use std::collections::HashSet;
2use std::fmt;
3
4use chrono::{DateTime, Local};
5use serde::Serialize;
6
7#[derive(Debug, Clone, Default, Serialize)]
8pub struct DiffStat {
9 pub files_changed: u32,
10 pub insertions: u32,
11 pub deletions: u32,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
15pub enum RepoOrigin {
16 #[serde(rename = "github")]
17 GitHub,
18 #[serde(rename = "gitlab")]
19 GitLab,
20 #[serde(rename = "bitbucket")]
21 Bitbucket,
22 #[serde(rename = "gitlab-self-hosted")]
23 GitLabSelfHosted,
24 #[serde(untagged)]
25 Custom(String),
26}
27
28impl fmt::Display for RepoOrigin {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 RepoOrigin::GitHub => write!(f, "GitHub"),
32 RepoOrigin::GitLab => write!(f, "GitLab"),
33 RepoOrigin::Bitbucket => write!(f, "Bitbucket"),
34 RepoOrigin::GitLabSelfHosted => write!(f, "GitLab Self-Hosted"),
35 RepoOrigin::Custom(host) => write!(f, "{host}"),
36 }
37 }
38}
39
40#[derive(Debug, Serialize)]
41pub struct Commit {
42 pub hash: String,
43 pub message: String,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub commit_type: Option<String>,
46 #[serde(rename = "timestamp")]
47 pub time: DateTime<Local>,
48 pub relative_time: String,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub url: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub diff_stat: Option<DiffStat>,
53}
54
55#[derive(Debug, Serialize)]
56pub struct BranchLog {
57 pub name: String,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub url: Option<String>,
60 pub commits: Vec<Commit>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub diff_stat: Option<DiffStat>,
63}
64
65#[derive(Debug, Serialize)]
66pub struct ProjectLog {
67 pub project: String,
68 pub path: String,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub origin: Option<RepoOrigin>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub remote_url: Option<String>,
73 pub branches: Vec<BranchLog>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub diff_stat: Option<DiffStat>,
76}
77
78impl BranchLog {
79 pub fn latest_activity(&self) -> Option<&str> {
80 self.commits.first().map(|c| c.relative_time.as_str())
81 }
82}
83
84impl ProjectLog {
85 pub fn total_commits(&self) -> usize {
86 let mut seen = HashSet::new();
87 self.branches
88 .iter()
89 .flat_map(|b| &b.commits)
90 .filter(|c| seen.insert(&c.hash))
91 .count()
92 }
93
94 pub fn latest_activity(&self) -> Option<&str> {
95 self.branches
96 .iter()
97 .flat_map(|b| b.commits.first())
98 .max_by_key(|c| c.time)
99 .map(|c| c.relative_time.as_str())
100 }
101}