Skip to main content

git_policy_cli/index/
index_stat.rs

1//! What:
2//!  The nine 32-bit stat fields an index entry and the untracked-cache extension store, and the Git varint.
3//! Why:
4//!  Both file layouts write the same fields (the entry puts the mode between inode and owner), and both use Git's
5//!  offset varint for lengths; one definition keeps the readers and writers of the in-process index update in step.
6//!
7//! In TS you'd write (pseudocode):
8//! ```ts
9//! type StatData = { ctimeSec: number; ctimeNsec: number; mtimeSec: number; mtimeNsec: number; dev: number; ino: number; uid: number; gid: number; size: number };
10//! ```
11
12/// The stat fields of one file as an index stores them (each truncated to 32 bits); all zero is the "never statted" value.
13#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
14pub struct StatData {
15    /// Change time, seconds.
16    pub ctime_sec: u32,
17    /// Change time, nanoseconds.
18    pub ctime_nsec: u32,
19    /// Modification time, seconds.
20    pub mtime_sec: u32,
21    /// Modification time, nanoseconds.
22    pub mtime_nsec: u32,
23    /// Device.
24    pub dev: u32,
25    /// Inode.
26    pub ino: u32,
27    /// Owner.
28    pub uid: u32,
29    /// Group.
30    pub gid: u32,
31    /// Size.
32    pub size: u32,
33}
34
35/// A big-endian `u32` at `at`.
36pub fn be32(bytes: &[u8], at: usize) -> Option<u32> {
37    let quad: &[u8] = bytes.get(at..at.checked_add(4)?)?;
38    return Some(u32::from_be_bytes([quad[0], quad[1], quad[2], quad[3]]));
39}
40
41impl StatData {
42    /// The fields in the order the untracked cache stores them (36 bytes), as `stat_data_to_disk`.
43    pub fn to_cache_bytes(&self, out: &mut Vec<u8>) {
44        for value in [self.ctime_sec, self.ctime_nsec, self.mtime_sec, self.mtime_nsec, self.dev, self.ino, self.uid, self.gid, self.size] {
45            out.extend_from_slice(&value.to_be_bytes());
46        }
47    }
48
49    /// The fields from 36 bytes in the untracked cache's order (`stat_data_from_disk`).
50    pub fn from_cache_bytes(bytes: &[u8]) -> Option<StatData> {
51        return Some(StatData {
52            ctime_sec: be32(bytes, 0)?,
53            ctime_nsec: be32(bytes, 4)?,
54            mtime_sec: be32(bytes, 8)?,
55            mtime_nsec: be32(bytes, 12)?,
56            dev: be32(bytes, 16)?,
57            ino: be32(bytes, 20)?,
58            uid: be32(bytes, 24)?,
59            gid: be32(bytes, 28)?,
60            size: be32(bytes, 32)?,
61        });
62    }
63}
64
65/// The bytes of one stat record in the untracked cache.
66pub const CACHE_STAT_LENGTH: usize = 36;
67
68/// Append `value` as Git's offset varint (`encode_varint` in `varint.c`).
69pub fn encode_varint(value: u64, out: &mut Vec<u8>) {
70    let mut reversed: Vec<u8> = vec![(value & 127) as u8];
71    let mut rest: u64 = value >> 7;
72    while rest > 0 {
73        rest -= 1;
74        reversed.push(128 | (rest & 127) as u8);
75        rest >>= 7;
76    }
77    reversed.reverse();
78    out.extend_from_slice(reversed.as_slice());
79}
80
81/// The varint at the start of `bytes` and the bytes after it (`decode_varint`), or `None` when it is cut off or overflows.
82pub fn decode_varint(bytes: &[u8]) -> Option<(u64, &[u8])> {
83    let mut used: usize = 1;
84    let mut byte: u8 = *bytes.first()?;
85    let mut value: u64 = u64::from(byte & 127);
86    while byte & 128 != 0 {
87        byte = *bytes.get(used)?;
88        used += 1;
89        value = value.checked_add(1)?;
90        if value > (u64::MAX >> 7) {
91            return None;
92        }
93        value = (value << 7) | u64::from(byte & 127);
94    }
95    return Some((value, &bytes[used..]));
96}
97
98#[cfg(test)]
99#[path = "index_stat_tests.rs"]
100mod tests;