1#[derive(Clone)]
3pub struct WipMetadata {
4 pub message: String,
5 pub branch: String,
6 pub task: Option<String>,
7 pub files: usize,
8 pub untracked: usize,
9}
10
11impl WipMetadata {
12 pub fn to_commit_message(&self) -> String {
14 let mut msg = format!("wip: {}", self.message);
15 msg.push_str("\n\n[wip-push]");
16 msg.push_str(&format!("\nbranch={}", self.branch));
17 if let Some(ref task) = self.task {
18 msg.push_str(&format!("\ntask={task}"));
19 }
20 msg.push_str(&format!("\nfiles={}", self.files));
21 msg.push_str(&format!("\nuntracked={}", self.untracked));
22 msg
23 }
24
25 pub fn from_commit_message(msg: &str) -> Self {
27 let mut message = String::new();
28 let mut branch = String::new();
29 let mut task = None;
30 let mut files = 0;
31 let mut untracked = 0;
32
33 let mut in_trailer = false;
34
35 for line in msg.lines() {
36 if line.trim() == "[wip-push]" {
37 in_trailer = true;
38 continue;
39 }
40
41 if in_trailer {
42 if let Some(val) = line.strip_prefix("branch=") {
43 branch = val.to_string();
44 } else if let Some(val) = line.strip_prefix("task=") {
45 task = Some(val.to_string());
46 } else if let Some(val) = line.strip_prefix("files=") {
47 files = val.parse().unwrap_or(0);
48 } else if let Some(val) = line.strip_prefix("untracked=") {
49 untracked = val.parse().unwrap_or(0);
50 }
51 } else if let Some(m) = line.strip_prefix("wip: ") {
52 message = m.to_string();
53 }
54 }
55
56 WipMetadata {
57 message,
58 branch,
59 task,
60 files,
61 untracked,
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn test_roundtrip() {
72 let meta = WipMetadata {
73 message: "fix auth flow".to_string(),
74 branch: "main".to_string(),
75 task: Some("JIRA-123".to_string()),
76 files: 3,
77 untracked: 1,
78 };
79
80 let msg = meta.to_commit_message();
81 let parsed = WipMetadata::from_commit_message(&msg);
82
83 assert_eq!(parsed.message, "fix auth flow");
84 assert_eq!(parsed.branch, "main");
85 assert_eq!(parsed.task.as_deref(), Some("JIRA-123"));
86 assert_eq!(parsed.files, 3);
87 assert_eq!(parsed.untracked, 1);
88 }
89
90 #[test]
91 fn test_parse_without_task() {
92 let meta = WipMetadata {
93 message: "wip".to_string(),
94 branch: "feature-x".to_string(),
95 task: None,
96 files: 5,
97 untracked: 0,
98 };
99
100 let msg = meta.to_commit_message();
101 let parsed = WipMetadata::from_commit_message(&msg);
102
103 assert_eq!(parsed.message, "wip");
104 assert_eq!(parsed.branch, "feature-x");
105 assert!(parsed.task.is_none());
106 assert_eq!(parsed.files, 5);
107 assert_eq!(parsed.untracked, 0);
108 }
109
110 #[test]
111 fn test_parse_minimal() {
112 let msg = "wip: stuff\n\n[wip-push]\nbranch=main\nfiles=0\nuntracked=0";
113 let parsed = WipMetadata::from_commit_message(msg);
114
115 assert_eq!(parsed.message, "stuff");
116 assert_eq!(parsed.branch, "main");
117 assert!(parsed.task.is_none());
118 assert_eq!(parsed.files, 0);
119 assert_eq!(parsed.untracked, 0);
120 }
121}