1#![cfg_attr(
3 feature = "document-features",
4 cfg_attr(doc, doc = ::document_features::document_features!())
5)]
6#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
7#![deny(unsafe_code, missing_docs, rust_2018_idioms)]
8
9use std::{ops::Range, path::PathBuf};
10
11use filetime::FileTime;
12pub use git_hash as hash;
13
14pub mod file;
16
17pub mod extension;
19
20pub mod entry;
22
23mod access;
24
25mod init;
26
27pub mod decode;
29
30pub mod verify;
32
33pub mod write;
35
36#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
38#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
39pub enum Version {
40 V2 = 2,
42 V3 = 3,
44 V4 = 4,
46}
47
48#[derive(Debug, Clone, Eq, PartialEq)]
50pub struct Entry {
51 pub stat: entry::Stat,
53 pub id: git_hash::ObjectId,
55 pub flags: entry::Flags,
57 pub mode: entry::Mode,
59 path: Range<usize>,
62}
63
64#[derive(Clone)]
66pub struct File {
67 pub(crate) state: State,
69 pub(crate) path: PathBuf,
71 pub(crate) checksum: Option<git_hash::ObjectId>,
73}
74
75pub type PathStorage = Vec<u8>;
77pub type PathStorageRef = [u8];
79
80#[derive(Clone)]
85pub struct State {
86 object_hash: git_hash::Kind,
90 #[allow(dead_code)]
95 timestamp: FileTime,
96 version: Version,
97 entries: Vec<Entry>,
98 path_backing: PathStorage,
100 is_sparse: bool,
102
103 tree: Option<extension::Tree>,
105 link: Option<extension::Link>,
106 resolve_undo: Option<extension::resolve_undo::Paths>,
107 untracked: Option<extension::UntrackedCache>,
108 fs_monitor: Option<extension::FsMonitor>,
109}
110
111mod impls {
112 use std::fmt::{Debug, Formatter};
113
114 use crate::State;
115
116 impl Debug for State {
117 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
118 for entry in &self.entries {
119 writeln!(
120 f,
121 "{} {}{:?} {} {}",
122 match entry.flags.stage() {
123 0 => "BASE ",
124 1 => "OURS ",
125 2 => "THEIRS ",
126 _ => "UNKNOWN",
127 },
128 if entry.flags.is_empty() {
129 "".to_string()
130 } else {
131 format!("{:?} ", entry.flags)
132 },
133 entry.mode,
134 entry.id,
135 entry.path(self)
136 )?;
137 }
138 Ok(())
139 }
140 }
141}
142
143pub(crate) mod util {
144 use std::convert::TryInto;
145
146 #[inline]
147 pub fn var_int(data: &[u8]) -> Option<(u64, &[u8])> {
148 let (num, consumed) = git_features::decode::leb64_from_read(data).ok()?;
149 let data = &data[consumed..];
150 (num, data).into()
151 }
152
153 #[inline]
154 pub fn read_u32(data: &[u8]) -> Option<(u32, &[u8])> {
155 split_at_pos(data, 4).map(|(num, data)| (u32::from_be_bytes(num.try_into().unwrap()), data))
156 }
157
158 #[inline]
159 pub fn read_u64(data: &[u8]) -> Option<(u64, &[u8])> {
160 split_at_pos(data, 8).map(|(num, data)| (u64::from_be_bytes(num.try_into().unwrap()), data))
161 }
162
163 #[inline]
164 pub fn from_be_u32(b: &[u8]) -> u32 {
165 u32::from_be_bytes(b.try_into().unwrap())
166 }
167
168 #[inline]
169 pub fn split_at_byte_exclusive(data: &[u8], byte: u8) -> Option<(&[u8], &[u8])> {
170 if data.len() < 2 {
171 return None;
172 }
173 data.iter().enumerate().find_map(|(idx, b)| {
174 (*b == byte).then(|| {
175 if idx == 0 {
176 (&[] as &[u8], &data[1..])
177 } else {
178 let (a, b) = data.split_at(idx);
179 (a, &b[1..])
180 }
181 })
182 })
183 }
184
185 #[inline]
186 pub fn split_at_pos(data: &[u8], pos: usize) -> Option<(&[u8], &[u8])> {
187 if data.len() < pos {
188 return None;
189 }
190 data.split_at(pos).into()
191 }
192}
193
194#[test]
195fn size_of_entry() {
196 assert_eq!(std::mem::size_of::<crate::Entry>(), 80);
197
198 assert_eq!(std::mem::size_of::<crate::entry::Time>(), 8);
200 assert_eq!(std::mem::size_of::<filetime::FileTime>(), 16);
201}