git_index/
lib.rs

1//! ## Feature Flags
2#![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
14///
15pub mod file;
16
17///
18pub mod extension;
19
20///
21pub mod entry;
22
23mod access;
24
25mod init;
26
27///
28pub mod decode;
29
30///
31pub mod verify;
32
33///
34pub mod write;
35
36/// All known versions of a git index file.
37#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
38#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
39pub enum Version {
40    /// Supports entries and various extensions.
41    V2 = 2,
42    /// Adds support for additional flags for each entry, called extended entries.
43    V3 = 3,
44    /// Supports deltified entry paths.
45    V4 = 4,
46}
47
48/// An entry in the index, identifying a non-tree item on disk.
49#[derive(Debug, Clone, Eq, PartialEq)]
50pub struct Entry {
51    /// The filesystem stat information for the file on disk.
52    pub stat: entry::Stat,
53    /// The object id for this entry's ODB representation (assuming it's up-to-date with it).
54    pub id: git_hash::ObjectId,
55    /// Additional flags for use in algorithms and for efficiently storing stage information.
56    pub flags: entry::Flags,
57    /// The kind of item this entry represents - it's not all blobs in the index anymore.
58    pub mode: entry::Mode,
59    /// The range to lookup in the path backing to obtain the entry path relative to the repository.
60    /// This costs additional memory but is probably worth it given that paths can stay in one big allocation.
61    path: Range<usize>,
62}
63
64/// An index file whose state was read from a file on disk.
65#[derive(Clone)]
66pub struct File {
67    /// The state containing the actual index data.
68    pub(crate) state: State,
69    /// The path from which the index was read or to which it is supposed to be written.
70    pub(crate) path: PathBuf,
71    /// The checksum of all bytes prior to the checksum itself.
72    pub(crate) checksum: Option<git_hash::ObjectId>,
73}
74
75/// The type to use and store paths to all entries.
76pub type PathStorage = Vec<u8>;
77/// The type to use and store paths to all entries, as reference
78pub type PathStorageRef = [u8];
79
80/// An in-memory cache of a fully parsed git index file.
81///
82/// As opposed to a snapshot, it's meant to be altered and eventually be written back to disk or converted into a tree.
83/// We treat index and its state synonymous.
84#[derive(Clone)]
85pub struct State {
86    /// The kind of object hash used when storing the underlying file.
87    ///
88    /// Empty states for example won't have a single object id, so deduction of the hash used isn't always possible.
89    object_hash: git_hash::Kind,
90    /// The time at which the state was created, indicating its freshness compared to other files on disk.
91    ///
92    /// Note that on platforms that only have a precisions of a second for this time, we will treat all entries with the
93    /// same timestamp as this as potentially changed, checking more thoroughly if a change actually happened.
94    #[allow(dead_code)]
95    timestamp: FileTime,
96    version: Version,
97    entries: Vec<Entry>,
98    /// A memory area keeping all index paths, in full length, independently of the index version.
99    path_backing: PathStorage,
100    /// True if one entry in the index has a special marker mode
101    is_sparse: bool,
102
103    // Extensions
104    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    // the reason we have our own time is half the size.
199    assert_eq!(std::mem::size_of::<crate::entry::Time>(), 8);
200    assert_eq!(std::mem::size_of::<filetime::FileTime>(), 16);
201}