Skip to main content

devcap_core/
model.rs

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}