Skip to main content

vcs_runner/
types.rs

1/// Whether a jj commit is the current working copy.
2#[cfg(feature = "jj-parse")]
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
4pub enum WorkingCopy {
5    #[default]
6    Background,
7    Current,
8}
9
10/// Whether a jj commit has unresolved conflicts.
11#[cfg(feature = "jj-parse")]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
13pub enum ConflictState {
14    #[default]
15    Clean,
16    Conflicted,
17}
18
19#[cfg(feature = "jj-parse")]
20impl ConflictState {
21    pub fn is_conflicted(self) -> bool {
22        matches!(self, Self::Conflicted)
23    }
24}
25
26/// Whether a jj commit is empty (no file changes).
27#[cfg(feature = "jj-parse")]
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
29pub enum ContentState {
30    #[default]
31    HasContent,
32    Empty,
33}
34
35#[cfg(feature = "jj-parse")]
36impl ContentState {
37    pub fn is_empty(self) -> bool {
38        matches!(self, Self::Empty)
39    }
40}
41
42/// A jj log entry parsed from jj template JSON output.
43///
44/// Not directly deserializable from jj's JSON — use [`crate::parse_log_output`]
45/// which handles the raw string booleans and populates the enum fields.
46#[cfg(feature = "jj-parse")]
47#[derive(Debug, Clone)]
48pub struct LogEntry {
49    pub commit_id: String,
50    pub change_id: String,
51    pub author_name: String,
52    pub author_email: String,
53    pub description: String,
54    pub parents: Vec<String>,
55    pub local_bookmarks: Vec<String>,
56    pub remote_bookmarks: Vec<String>,
57    pub working_copy: WorkingCopy,
58    pub conflict: ConflictState,
59    pub content: ContentState,
60}
61
62#[cfg(feature = "jj-parse")]
63impl LogEntry {
64    /// The first line of the commit description.
65    pub fn summary(&self) -> &str {
66        self.description.lines().next().unwrap_or("")
67    }
68}
69
70/// Sync status of a bookmark relative to its remote.
71#[cfg(feature = "jj-parse")]
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub enum RemoteStatus {
74    /// Bookmark exists only locally (no non-git remote tracking branch).
75    Local,
76    /// Remote exists but local and remote have diverged.
77    Unsynced,
78    /// Local and remote point to the same commit.
79    Synced,
80}
81
82#[cfg(feature = "jj-parse")]
83impl RemoteStatus {
84    pub fn has_remote(self) -> bool {
85        !matches!(self, Self::Local)
86    }
87
88    pub fn is_synced(self) -> bool {
89        matches!(self, Self::Synced)
90    }
91}
92
93/// A jj bookmark with sync status.
94#[cfg(feature = "jj-parse")]
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct Bookmark {
97    pub name: String,
98    pub commit_id: String,
99    pub change_id: String,
100    pub remote: RemoteStatus,
101}
102
103/// A git remote.
104#[cfg(feature = "jj-parse")]
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct GitRemote {
107    pub name: String,
108    pub url: String,
109}
110
111#[cfg(all(test, feature = "jj-parse"))]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn conflict_state() {
117        assert!(!ConflictState::Clean.is_conflicted());
118        assert!(ConflictState::Conflicted.is_conflicted());
119    }
120
121    #[test]
122    fn content_state() {
123        assert!(!ContentState::HasContent.is_empty());
124        assert!(ContentState::Empty.is_empty());
125    }
126
127    #[test]
128    fn working_copy_default() {
129        assert_eq!(WorkingCopy::default(), WorkingCopy::Background);
130    }
131
132    #[test]
133    fn remote_status_local() {
134        assert!(!RemoteStatus::Local.has_remote());
135        assert!(!RemoteStatus::Local.is_synced());
136    }
137
138    #[test]
139    fn remote_status_unsynced() {
140        assert!(RemoteStatus::Unsynced.has_remote());
141        assert!(!RemoteStatus::Unsynced.is_synced());
142    }
143
144    #[test]
145    fn remote_status_synced() {
146        assert!(RemoteStatus::Synced.has_remote());
147        assert!(RemoteStatus::Synced.is_synced());
148    }
149
150    #[test]
151    fn log_entry_summary() {
152        let entry = LogEntry {
153            commit_id: "abc".into(),
154            change_id: "xyz".into(),
155            author_name: "A".into(),
156            author_email: "a@b".into(),
157            description: "first line\nsecond line".into(),
158            parents: vec![],
159            local_bookmarks: vec![],
160            remote_bookmarks: vec![],
161            working_copy: WorkingCopy::Background,
162            conflict: ConflictState::Clean,
163            content: ContentState::HasContent,
164        };
165        assert_eq!(entry.summary(), "first line");
166    }
167
168    #[test]
169    fn log_entry_summary_empty_description() {
170        let entry = LogEntry {
171            commit_id: "abc".into(),
172            change_id: "xyz".into(),
173            author_name: "A".into(),
174            author_email: "a@b".into(),
175            description: String::new(),
176            parents: vec![],
177            local_bookmarks: vec![],
178            remote_bookmarks: vec![],
179            working_copy: WorkingCopy::Background,
180            conflict: ConflictState::Clean,
181            content: ContentState::HasContent,
182        };
183        assert_eq!(entry.summary(), "");
184    }
185}