1use std::fmt;
4use std::time::SystemTime;
5
6#[derive(Clone, Copy, PartialEq, Eq, Hash)]
8pub struct Oid([u8; 20]);
9
10impl Oid {
11 pub fn from_bytes(bytes: [u8; 20]) -> Self {
13 Self(bytes)
14 }
15
16 pub fn as_bytes(&self) -> &[u8; 20] {
18 &self.0
19 }
20
21 pub fn is_zero(&self) -> bool {
23 self.0 == [0u8; 20]
24 }
25}
26
27impl fmt::Display for Oid {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 for byte in &self.0 {
30 write!(f, "{byte:02x}")?;
31 }
32 Ok(())
33 }
34}
35
36impl fmt::Debug for Oid {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "Oid({self})")
39 }
40}
41
42pub type TreeHash = Oid;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Reference {
48 pub name: String,
50 pub target: Oid,
52 pub is_branch: bool,
54 pub is_tag: bool,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Signature {
61 pub name: String,
63 pub email: String,
65 pub when: SystemTime,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Commit {
72 pub oid: Oid,
74 pub author: Signature,
76 pub committer: Signature,
78 pub message: String,
80 pub parents: Vec<Oid>,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[non_exhaustive]
87pub enum FileStatus {
88 Added,
90 Modified,
92 Deleted,
94 Renamed,
96 Copied,
98 Untracked,
100 Ignored,
102 TypeChanged,
104 Conflicted,
106}
107
108impl fmt::Display for FileStatus {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 Self::Added => write!(f, "added"),
112 Self::Modified => write!(f, "modified"),
113 Self::Deleted => write!(f, "deleted"),
114 Self::Renamed => write!(f, "renamed"),
115 Self::Copied => write!(f, "copied"),
116 Self::Untracked => write!(f, "untracked"),
117 Self::Ignored => write!(f, "ignored"),
118 Self::TypeChanged => write!(f, "type_changed"),
119 Self::Conflicted => write!(f, "conflicted"),
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct DiffEntry {
127 pub path: String,
129 pub old_path: Option<String>,
131 pub old_oid: Oid,
133 pub new_oid: Oid,
135 pub status: FileStatus,
137}
138
139#[derive(Debug, Clone, Default, PartialEq, Eq)]
141pub struct DiffStats {
142 pub additions: usize,
144 pub deletions: usize,
146 pub files_changed: usize,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152#[non_exhaustive]
153pub enum EntryState {
154 Staged,
156 Unstaged,
158 Untracked,
160 Conflicted,
162}
163
164impl fmt::Display for EntryState {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 Self::Staged => write!(f, "staged"),
168 Self::Unstaged => write!(f, "unstaged"),
169 Self::Untracked => write!(f, "untracked"),
170 Self::Conflicted => write!(f, "conflicted"),
171 }
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct StatusEntry {
178 pub path: String,
180 pub state: EntryState,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct IndexEntry {
187 pub path: String,
189 pub oid: Oid,
191 pub kind: EntryKind,
193 pub filemode: u32,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199#[non_exhaustive]
200pub enum EntryKind {
201 Blob,
203 Tree,
205 Submodule,
207}
208
209impl fmt::Display for EntryKind {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 match self {
212 Self::Blob => write!(f, "blob"),
213 Self::Tree => write!(f, "tree"),
214 Self::Submodule => write!(f, "submodule"),
215 }
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct TreeEntry {
222 pub name: String,
224 pub oid: Oid,
226 pub kind: EntryKind,
228 pub filemode: u32,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct Branch {
235 pub name: String,
237 pub target: Oid,
239 pub upstream: Option<String>,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct Tag {
246 pub name: String,
248 pub target: Oid,
250 pub tagger: Option<Signature>,
252 pub message: String,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct Remote {
259 pub name: String,
261 pub url: String,
263 pub fetch_specs: Vec<String>,
265 pub push_specs: Vec<String>,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
271pub struct BlameLine {
272 pub line: usize,
274 pub commit_oid: Oid,
276 pub author: Signature,
278 pub content: String,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct GrepMatch {
285 pub path: String,
287 pub line_number: Option<usize>,
289 pub line: String,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct StashEntry {
296 pub index: usize,
298 pub oid: Oid,
300 pub message: String,
302}
303
304#[derive(Debug, Clone, Default, PartialEq, Eq)]
306pub struct MergeResult {
307 pub head: Option<Oid>,
309 pub fast_forward: bool,
311 pub conflicts: Vec<String>,
313}
314
315#[derive(Debug, Clone, Default, PartialEq, Eq)]
317pub struct RebaseResult {
318 pub head: Option<Oid>,
320 pub applied: usize,
322 pub conflicts: Vec<String>,
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
328#[non_exhaustive]
329pub enum BranchFilter {
330 #[default]
332 Local,
333 Remote,
335 All,
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
341#[non_exhaustive]
342pub enum ResetMode {
343 #[default]
345 Mixed,
346 Soft,
348 Hard,
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn oid_display_debug_and_zero_detection_are_stable() {
358 let zero = Oid::from_bytes([0; 20]);
359 let mut bytes = [0; 20];
360 bytes[0] = 0xab;
361 bytes[19] = 0x05;
362 let oid = Oid::from_bytes(bytes);
363
364 assert!(zero.is_zero());
365 assert!(!oid.is_zero());
366 assert_eq!(oid.as_bytes(), &bytes);
367 assert_eq!(oid.to_string(), "ab00000000000000000000000000000000000005");
368 assert_eq!(
369 format!("{oid:?}"),
370 "Oid(ab00000000000000000000000000000000000005)"
371 );
372 }
373
374 #[test]
375 fn enum_display_values_match_public_contract() {
376 assert_eq!(FileStatus::Added.to_string(), "added");
377 assert_eq!(FileStatus::Modified.to_string(), "modified");
378 assert_eq!(FileStatus::Deleted.to_string(), "deleted");
379 assert_eq!(FileStatus::Renamed.to_string(), "renamed");
380 assert_eq!(FileStatus::Copied.to_string(), "copied");
381 assert_eq!(FileStatus::Untracked.to_string(), "untracked");
382 assert_eq!(FileStatus::Ignored.to_string(), "ignored");
383 assert_eq!(FileStatus::TypeChanged.to_string(), "type_changed");
384 assert_eq!(FileStatus::Conflicted.to_string(), "conflicted");
385
386 assert_eq!(EntryState::Staged.to_string(), "staged");
387 assert_eq!(EntryState::Unstaged.to_string(), "unstaged");
388 assert_eq!(EntryState::Untracked.to_string(), "untracked");
389 assert_eq!(EntryState::Conflicted.to_string(), "conflicted");
390
391 assert_eq!(EntryKind::Blob.to_string(), "blob");
392 assert_eq!(EntryKind::Tree.to_string(), "tree");
393 assert_eq!(EntryKind::Submodule.to_string(), "submodule");
394 }
395
396 #[test]
397 fn default_result_types_are_empty_and_non_destructive() {
398 assert_eq!(DiffStats::default().files_changed, 0);
399 assert_eq!(BranchFilter::default(), BranchFilter::Local);
400 assert_eq!(ResetMode::default(), ResetMode::Mixed);
401 assert_eq!(MergeResult::default().conflicts, Vec::<String>::new());
402 assert_eq!(RebaseResult::default().applied, 0);
403 }
404}