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
45pub const DEFAULT_BRANCH: &str = "main";
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct Reference {
55 pub name: String,
57 pub target: Oid,
59 pub is_branch: bool,
61 pub is_tag: bool,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Signature {
68 pub name: String,
70 pub email: String,
72 pub when: SystemTime,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct Commit {
79 pub oid: Oid,
81 pub author: Signature,
83 pub committer: Signature,
85 pub message: String,
87 pub parents: Vec<Oid>,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[non_exhaustive]
94pub enum FileStatus {
95 Added,
97 Modified,
99 Deleted,
101 Renamed,
103 Copied,
105 Untracked,
107 Ignored,
109 TypeChanged,
111 Conflicted,
113}
114
115impl fmt::Display for FileStatus {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 match self {
118 Self::Added => write!(f, "added"),
119 Self::Modified => write!(f, "modified"),
120 Self::Deleted => write!(f, "deleted"),
121 Self::Renamed => write!(f, "renamed"),
122 Self::Copied => write!(f, "copied"),
123 Self::Untracked => write!(f, "untracked"),
124 Self::Ignored => write!(f, "ignored"),
125 Self::TypeChanged => write!(f, "type_changed"),
126 Self::Conflicted => write!(f, "conflicted"),
127 }
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct DiffEntry {
134 pub path: String,
136 pub old_path: Option<String>,
138 pub old_oid: Oid,
140 pub new_oid: Oid,
142 pub status: FileStatus,
144}
145
146#[derive(Debug, Clone, Default, PartialEq, Eq)]
148pub struct DiffStats {
149 pub additions: usize,
151 pub deletions: usize,
153 pub files_changed: usize,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159#[non_exhaustive]
160pub enum EntryState {
161 Staged,
163 Unstaged,
165 Untracked,
167 Conflicted,
169}
170
171impl fmt::Display for EntryState {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 Self::Staged => write!(f, "staged"),
175 Self::Unstaged => write!(f, "unstaged"),
176 Self::Untracked => write!(f, "untracked"),
177 Self::Conflicted => write!(f, "conflicted"),
178 }
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct StatusEntry {
185 pub path: String,
187 pub state: EntryState,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct IndexEntry {
194 pub path: String,
196 pub oid: Oid,
198 pub kind: EntryKind,
200 pub filemode: u32,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206#[non_exhaustive]
207pub enum EntryKind {
208 Blob,
210 Tree,
212 Submodule,
214}
215
216impl fmt::Display for EntryKind {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 match self {
219 Self::Blob => write!(f, "blob"),
220 Self::Tree => write!(f, "tree"),
221 Self::Submodule => write!(f, "submodule"),
222 }
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct TreeEntry {
229 pub name: String,
231 pub oid: Oid,
233 pub kind: EntryKind,
235 pub filemode: u32,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct Branch {
242 pub name: String,
244 pub target: Oid,
246 pub upstream: Option<String>,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct Tag {
253 pub name: String,
255 pub target: Oid,
257 pub tagger: Option<Signature>,
259 pub message: String,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
265pub struct Remote {
266 pub name: String,
268 pub url: String,
270 pub fetch_specs: Vec<String>,
272 pub push_specs: Vec<String>,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq)]
278pub struct BlameLine {
279 pub line: usize,
281 pub commit_oid: Oid,
283 pub author: Signature,
285 pub content: String,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct GrepMatch {
292 pub path: String,
294 pub line_number: Option<usize>,
296 pub line: String,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq)]
302pub struct StashEntry {
303 pub index: usize,
305 pub oid: Oid,
307 pub message: String,
309}
310
311#[derive(Debug, Clone, Default, PartialEq, Eq)]
313pub struct MergeResult {
314 pub head: Option<Oid>,
316 pub fast_forward: bool,
318 pub conflicts: Vec<String>,
320}
321
322#[derive(Debug, Clone, Default, PartialEq, Eq)]
324pub struct RebaseResult {
325 pub head: Option<Oid>,
327 pub applied: usize,
329 pub conflicts: Vec<String>,
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
335#[non_exhaustive]
336pub enum BranchFilter {
337 #[default]
339 Local,
340 Remote,
342 All,
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
348#[non_exhaustive]
349pub enum ResetMode {
350 #[default]
352 Mixed,
353 Soft,
355 Hard,
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn oid_display_debug_and_zero_detection_are_stable() {
365 let zero = Oid::from_bytes([0; 20]);
366 let mut bytes = [0; 20];
367 bytes[0] = 0xab;
368 bytes[19] = 0x05;
369 let oid = Oid::from_bytes(bytes);
370
371 assert!(zero.is_zero());
372 assert!(!oid.is_zero());
373 assert_eq!(oid.as_bytes(), &bytes);
374 assert_eq!(oid.to_string(), "ab00000000000000000000000000000000000005");
375 assert_eq!(
376 format!("{oid:?}"),
377 "Oid(ab00000000000000000000000000000000000005)"
378 );
379 }
380
381 #[test]
382 fn enum_display_values_match_public_contract() {
383 assert_eq!(FileStatus::Added.to_string(), "added");
384 assert_eq!(FileStatus::Modified.to_string(), "modified");
385 assert_eq!(FileStatus::Deleted.to_string(), "deleted");
386 assert_eq!(FileStatus::Renamed.to_string(), "renamed");
387 assert_eq!(FileStatus::Copied.to_string(), "copied");
388 assert_eq!(FileStatus::Untracked.to_string(), "untracked");
389 assert_eq!(FileStatus::Ignored.to_string(), "ignored");
390 assert_eq!(FileStatus::TypeChanged.to_string(), "type_changed");
391 assert_eq!(FileStatus::Conflicted.to_string(), "conflicted");
392
393 assert_eq!(EntryState::Staged.to_string(), "staged");
394 assert_eq!(EntryState::Unstaged.to_string(), "unstaged");
395 assert_eq!(EntryState::Untracked.to_string(), "untracked");
396 assert_eq!(EntryState::Conflicted.to_string(), "conflicted");
397
398 assert_eq!(EntryKind::Blob.to_string(), "blob");
399 assert_eq!(EntryKind::Tree.to_string(), "tree");
400 assert_eq!(EntryKind::Submodule.to_string(), "submodule");
401 }
402
403 #[test]
404 fn default_result_types_are_empty_and_non_destructive() {
405 assert_eq!(DiffStats::default().files_changed, 0);
406 assert_eq!(BranchFilter::default(), BranchFilter::Local);
407 assert_eq!(ResetMode::default(), ResetMode::Mixed);
408 assert_eq!(MergeResult::default().conflicts, Vec::<String>::new());
409 assert_eq!(RebaseResult::default().applied, 0);
410 }
411}