Skip to main content

gitstack/
event.rs

1use chrono::{DateTime, Local};
2
3/// Gitイベントの種類
4#[derive(Debug, Clone, PartialEq)]
5#[allow(dead_code)]
6pub enum GitEventKind {
7    /// コミット
8    Commit,
9    /// マージコミット
10    Merge,
11    /// ブランチ切り替え(reflogから検出)
12    BranchSwitch,
13}
14
15/// Gitイベント
16#[derive(Debug, Clone)]
17#[allow(dead_code)]
18pub struct GitEvent {
19    /// イベントの種類
20    pub kind: GitEventKind,
21    /// コミットハッシュ(短縮形)
22    pub short_hash: String,
23    /// コミットメッセージ(1行目)
24    pub message: String,
25    /// 作者名
26    pub author: String,
27    /// タイムスタンプ
28    pub timestamp: DateTime<Local>,
29    /// 変更ファイル数(追加)
30    pub files_added: usize,
31    /// 変更ファイル数(削除)
32    pub files_deleted: usize,
33    /// 親コミットのハッシュ一覧(グラフ表示用)
34    pub parent_hashes: Vec<String>,
35    /// ブランチラベル(このコミットを指しているブランチ名)
36    pub branch_labels: Vec<String>,
37}
38
39impl GitEvent {
40    /// 新しいコミットイベントを作成
41    pub fn commit(
42        short_hash: String,
43        message: String,
44        author: String,
45        timestamp: DateTime<Local>,
46        files_added: usize,
47        files_deleted: usize,
48    ) -> Self {
49        Self {
50            kind: GitEventKind::Commit,
51            short_hash,
52            message,
53            author,
54            timestamp,
55            files_added,
56            files_deleted,
57            parent_hashes: Vec::new(),
58            branch_labels: Vec::new(),
59        }
60    }
61
62    /// 新しいマージイベントを作成
63    pub fn merge(
64        short_hash: String,
65        message: String,
66        author: String,
67        timestamp: DateTime<Local>,
68    ) -> Self {
69        Self {
70            kind: GitEventKind::Merge,
71            short_hash,
72            message,
73            author,
74            timestamp,
75            files_added: 0,
76            files_deleted: 0,
77            parent_hashes: Vec::new(),
78            branch_labels: Vec::new(),
79        }
80    }
81
82    /// 親コミットを設定
83    pub fn with_parents(mut self, parents: Vec<String>) -> Self {
84        self.parent_hashes = parents;
85        self
86    }
87
88    /// ブランチラベルを設定
89    pub fn with_labels(mut self, labels: Vec<String>) -> Self {
90        self.branch_labels = labels;
91        self
92    }
93
94    /// ラベルがあるかどうか
95    pub fn has_labels(&self) -> bool {
96        !self.branch_labels.is_empty()
97    }
98
99    /// 相対時間を取得(例: "14m ago", "2h ago", "3d ago")
100    #[allow(dead_code)]
101    pub fn relative_time(&self) -> String {
102        let now = Local::now();
103        let duration = now.signed_duration_since(self.timestamp);
104
105        if duration.num_minutes() < 1 {
106            "just now".to_string()
107        } else if duration.num_minutes() < 60 {
108            format!("{}m ago", duration.num_minutes())
109        } else if duration.num_hours() < 24 {
110            format!("{}h ago", duration.num_hours())
111        } else if duration.num_days() < 30 {
112            format!("{}d ago", duration.num_days())
113        } else {
114            format!("{}mo ago", duration.num_days() / 30)
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use chrono::Duration;
123
124    fn create_test_timestamp() -> DateTime<Local> {
125        Local::now()
126    }
127
128    #[test]
129    fn test_git_event_kind_commit_is_distinct() {
130        assert_ne!(GitEventKind::Commit, GitEventKind::Merge);
131        assert_ne!(GitEventKind::Commit, GitEventKind::BranchSwitch);
132    }
133
134    #[test]
135    fn test_git_event_commit_creates_commit_kind() {
136        let event = GitEvent::commit(
137            "abc1234".to_string(),
138            "feat: add feature".to_string(),
139            "author".to_string(),
140            create_test_timestamp(),
141            2,
142            1,
143        );
144        assert_eq!(event.kind, GitEventKind::Commit);
145    }
146
147    #[test]
148    fn test_git_event_commit_stores_hash() {
149        let event = GitEvent::commit(
150            "abc1234".to_string(),
151            "message".to_string(),
152            "author".to_string(),
153            create_test_timestamp(),
154            0,
155            0,
156        );
157        assert_eq!(event.short_hash, "abc1234");
158    }
159
160    #[test]
161    fn test_git_event_commit_stores_message() {
162        let event = GitEvent::commit(
163            "abc1234".to_string(),
164            "feat: new feature".to_string(),
165            "author".to_string(),
166            create_test_timestamp(),
167            0,
168            0,
169        );
170        assert_eq!(event.message, "feat: new feature");
171    }
172
173    #[test]
174    fn test_git_event_commit_stores_author() {
175        let event = GitEvent::commit(
176            "abc1234".to_string(),
177            "message".to_string(),
178            "John Doe".to_string(),
179            create_test_timestamp(),
180            0,
181            0,
182        );
183        assert_eq!(event.author, "John Doe");
184    }
185
186    #[test]
187    fn test_git_event_commit_stores_file_counts() {
188        let event = GitEvent::commit(
189            "abc1234".to_string(),
190            "message".to_string(),
191            "author".to_string(),
192            create_test_timestamp(),
193            5,
194            3,
195        );
196        assert_eq!(event.files_added, 5);
197        assert_eq!(event.files_deleted, 3);
198    }
199
200    #[test]
201    fn test_git_event_merge_creates_merge_kind() {
202        let event = GitEvent::merge(
203            "abc1234".to_string(),
204            "Merge PR #1".to_string(),
205            "author".to_string(),
206            create_test_timestamp(),
207        );
208        assert_eq!(event.kind, GitEventKind::Merge);
209    }
210
211    #[test]
212    fn test_git_event_merge_has_zero_file_counts() {
213        let event = GitEvent::merge(
214            "abc1234".to_string(),
215            "Merge PR #1".to_string(),
216            "author".to_string(),
217            create_test_timestamp(),
218        );
219        assert_eq!(event.files_added, 0);
220        assert_eq!(event.files_deleted, 0);
221    }
222
223    #[test]
224    fn test_relative_time_just_now() {
225        let event = GitEvent::commit(
226            "abc1234".to_string(),
227            "message".to_string(),
228            "author".to_string(),
229            Local::now(),
230            0,
231            0,
232        );
233        assert_eq!(event.relative_time(), "just now");
234    }
235
236    #[test]
237    fn test_relative_time_minutes() {
238        let event = GitEvent::commit(
239            "abc1234".to_string(),
240            "message".to_string(),
241            "author".to_string(),
242            Local::now() - Duration::minutes(14),
243            0,
244            0,
245        );
246        assert_eq!(event.relative_time(), "14m ago");
247    }
248
249    #[test]
250    fn test_relative_time_hours() {
251        let event = GitEvent::commit(
252            "abc1234".to_string(),
253            "message".to_string(),
254            "author".to_string(),
255            Local::now() - Duration::hours(2),
256            0,
257            0,
258        );
259        assert_eq!(event.relative_time(), "2h ago");
260    }
261
262    #[test]
263    fn test_relative_time_days() {
264        let event = GitEvent::commit(
265            "abc1234".to_string(),
266            "message".to_string(),
267            "author".to_string(),
268            Local::now() - Duration::days(3),
269            0,
270            0,
271        );
272        assert_eq!(event.relative_time(), "3d ago");
273    }
274}