Skip to main content

wip_git/
metadata.rs

1/// Metadata stored in the [wip-push] trailer block of the commit message
2#[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    /// Build the full commit message with trailer
13    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    /// Parse metadata from a commit message
26    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}