1use std::borrow::Cow;
19use std::collections::{BTreeMap, BTreeSet, HashMap};
20use std::fs;
21use std::io::{self, Write};
22use std::path::{Path, PathBuf};
23
24use crate::hash::verify_trailer;
25
26use crate::config::ConfigSet;
27use crate::error::{Error, Result};
28use crate::lockfile::pid_path_for_lock;
29use crate::objects::{parse_tree, HashAlgo, ObjectId, ObjectKind, TreeEntry};
30use crate::odb::Odb;
31use crate::repo::Repository;
32use crate::resolve_undo::{self, write_resolve_undo_payload, ResolveUndoRecord};
33use crate::rev_parse;
34use crate::untracked_cache;
35
36fn gitattributes_index_path(path: &[u8]) -> bool {
37 path == b".gitattributes" || path.ends_with(b"/.gitattributes")
38}
39
40pub const MODE_REGULAR: u32 = 0o100644;
42pub const MODE_EXECUTABLE: u32 = 0o100755;
44pub const MODE_SYMLINK: u32 = 0o120000;
46pub const MODE_GITLINK: u32 = 0o160000;
48pub const MODE_TREE: u32 = 0o040000;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct StatMatchPolicy {
54 pub trust_ctime: bool,
56 pub check_stat: bool,
58 pub use_nanosec: bool,
60}
61
62impl Default for StatMatchPolicy {
63 fn default() -> Self {
64 Self {
65 trust_ctime: true,
66 check_stat: true,
67 use_nanosec: false,
68 }
69 }
70}
71
72impl StatMatchPolicy {
73 #[must_use]
75 pub fn from_config(config: Option<&ConfigSet>) -> Self {
76 let mut policy = Self::default();
77 let Some(cfg) = config else {
78 return policy;
79 };
80 if let Some(trust) = cfg.get_bool("core.trustctime").and_then(|r| r.ok()) {
81 policy.trust_ctime = trust;
82 }
83 if let Some(raw) = cfg.get("core.checkstat") {
84 policy.check_stat = match raw.to_ascii_lowercase().as_str() {
85 "minimal" => false,
86 "default" => true,
87 other => cfg
88 .get_bool("core.checkstat")
89 .and_then(|r| r.ok())
90 .unwrap_or(!other.eq_ignore_ascii_case("minimal")),
91 };
92 }
93 if let Some(use_ns) = cfg.get_bool("core.usenanosec").and_then(|r| r.ok()) {
94 policy.use_nanosec = use_ns;
95 }
96 policy
97 }
98}
99
100#[must_use]
105pub(crate) fn metadata_times(meta: &fs::Metadata) -> ((u32, u32), (u32, u32)) {
106 #[cfg(unix)]
107 {
108 use std::os::unix::fs::MetadataExt;
109 (
110 (meta.ctime() as u32, meta.ctime_nsec() as u32),
111 (meta.mtime() as u32, meta.mtime_nsec() as u32),
112 )
113 }
114 #[cfg(not(unix))]
115 {
116 use filetime::FileTime;
117 let mtime_ft = FileTime::from_last_modification_time(meta);
118 let ctime_ft = FileTime::from_creation_time(meta).unwrap_or(mtime_ft);
119 (
120 filetime_to_index_pair(ctime_ft),
121 filetime_to_index_pair(mtime_ft),
122 )
123 }
124}
125
126#[cfg(not(unix))]
127#[must_use]
128fn filetime_to_index_pair(ft: filetime::FileTime) -> (u32, u32) {
129 let secs = ft.unix_seconds();
130 if secs.is_negative() {
131 (0, 0)
132 } else {
133 (secs as u32, ft.nanoseconds())
134 }
135}
136
137#[must_use]
139pub fn munge_index_size(st_size: u64) -> u32 {
140 let sd_size = st_size as u32;
141 if sd_size == 0 && st_size != 0 {
142 0x8000_0000
143 } else {
144 sd_size
145 }
146}
147
148#[must_use]
150pub fn index_entries_stat_equivalent(
151 a: &IndexEntry,
152 b: &IndexEntry,
153 policy: StatMatchPolicy,
154) -> bool {
155 IndexStatFields::of_entry(a).equivalent_to(&IndexStatFields::of_entry(b), policy)
156}
157
158pub(crate) const INDEX_EXT_SPARSE_DIRECTORIES: u32 = u32::from_be_bytes(*b"sdir");
160pub(crate) const INDEX_EXT_UNTRACKED: u32 = u32::from_be_bytes(*b"UNTR");
162pub(crate) const INDEX_EXT_FSMONITOR: u32 = u32::from_be_bytes(*b"FSMN");
164const PARALLEL_TRAILER_VERIFY_MIN_BYTES: usize = 256 * 1024;
168
169const FSMONITOR_EXT_VERSION1: u32 = 1;
170const FSMONITOR_EXT_VERSION2: u32 = 2;
172pub(crate) const INDEX_EXT_RESOLVE_UNDO: u32 = u32::from_be_bytes(*b"REUC");
174pub(crate) const INDEX_EXT_LINK: u32 = u32::from_be_bytes(*b"link");
176
177#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct IndexEntry {
180 pub ctime_sec: u32,
182 pub ctime_nsec: u32,
184 pub mtime_sec: u32,
186 pub mtime_nsec: u32,
188 pub dev: u32,
190 pub ino: u32,
192 pub mode: u32,
194 pub uid: u32,
196 pub gid: u32,
198 pub size: u32,
200 pub oid: ObjectId,
202 pub flags: u16,
204 pub flags_extended: Option<u16>,
206 pub path: Vec<u8>,
208 pub base_index_pos: u32,
210}
211
212impl IndexEntry {
213 #[must_use]
215 pub fn stage(&self) -> u8 {
216 ((self.flags >> 12) & 0x3) as u8
217 }
218
219 #[must_use]
221 pub fn stat_cache_unset(&self) -> bool {
222 self.size == 0 && self.mtime_sec == 0 && self.mtime_nsec == 0
223 }
224
225 pub(crate) fn set_stage(&mut self, stage: u8) {
226 self.flags = (self.flags & 0x0FFF) | ((stage as u16 & 0x3) << 12);
227 }
228
229 #[must_use]
231 pub fn assume_unchanged(&self) -> bool {
232 self.flags & 0x8000 != 0
233 }
234
235 #[must_use]
237 pub fn skip_worktree(&self) -> bool {
238 self.flags_extended
239 .map(|f| f & 0x4000 != 0)
240 .unwrap_or(false)
241 }
242
243 pub fn set_assume_unchanged(&mut self, value: bool) {
245 if value {
246 self.flags |= 0x8000;
247 } else {
248 self.flags &= !0x8000;
249 }
250 }
251
252 pub fn set_skip_worktree(&mut self, value: bool) {
254 let fe = self.flags_extended.get_or_insert(0);
255 if value {
256 *fe |= 0x4000;
257 } else {
258 *fe &= !0x4000;
259 if *fe == 0 {
260 self.flags_extended = None;
261 }
262 }
263 }
264
265 #[must_use]
267 pub fn intent_to_add(&self) -> bool {
268 self.flags_extended
269 .map(|f| f & 0x2000 != 0)
270 .unwrap_or(false)
271 }
272
273 pub fn set_intent_to_add(&mut self, value: bool) {
275 let fe = self.flags_extended.get_or_insert(0);
276 if value {
277 *fe |= 0x2000;
278 } else {
279 *fe &= !0x2000;
280 if *fe == 0 {
281 self.flags_extended = None;
282 }
283 }
284 }
285
286 #[must_use]
288 pub fn is_sparse_directory_placeholder(&self) -> bool {
289 self.mode == MODE_TREE && self.stage() == 0 && self.skip_worktree()
290 }
291
292 const FLAG_EXT_OVERLAY_TREE_SKIP: u16 = 0x8000;
294 const FLAG_EXT_FSMONITOR_VALID: u16 = 0x1000;
296 const FLAG_EXT_ON_DISK: u16 = 0x2000 | 0x4000;
299
300 fn disk_flags_extended(fe: u16) -> u16 {
302 fe & Self::FLAG_EXT_ON_DISK
303 }
304
305 #[must_use]
307 pub fn overlay_tree_skip_output(&self) -> bool {
308 self.flags_extended
309 .is_some_and(|fe| fe & Self::FLAG_EXT_OVERLAY_TREE_SKIP != 0)
310 }
311
312 fn set_overlay_tree_skip_output(&mut self, value: bool) {
313 let fe = self.flags_extended.get_or_insert(0);
314 if value {
315 *fe |= Self::FLAG_EXT_OVERLAY_TREE_SKIP;
316 } else {
317 *fe &= !Self::FLAG_EXT_OVERLAY_TREE_SKIP;
318 if *fe == 0 {
319 self.flags_extended = None;
320 }
321 }
322 }
323
324 #[must_use]
326 pub fn fsmonitor_valid(&self) -> bool {
327 self.flags_extended
328 .is_some_and(|fe| fe & Self::FLAG_EXT_FSMONITOR_VALID != 0)
329 }
330
331 pub fn set_fsmonitor_valid(&mut self, value: bool) {
333 let fe = self.flags_extended.get_or_insert(0);
334 if value {
335 *fe |= Self::FLAG_EXT_FSMONITOR_VALID;
336 } else {
337 *fe &= !Self::FLAG_EXT_FSMONITOR_VALID;
338 if *fe == 0 {
339 self.flags_extended = None;
340 }
341 }
342 }
343}
344
345#[derive(Debug, Clone)]
361pub struct Index {
362 pub version: u32,
364 pub(crate) entries: Vec<IndexEntry>,
366 pub(crate) entries_sorted: bool,
368 pub sparse_directories: bool,
370 pub untracked_cache: Option<untracked_cache::UntrackedCache>,
372 pub fsmonitor_last_update: Option<String>,
374 pub(crate) fsmonitor_dirty: Option<crate::ewah_bitmap::EwahBitmap>,
376 pub(crate) fsmonitor_raw_extension: Option<Vec<u8>>,
378 pub resolve_undo: Option<BTreeMap<Vec<u8>, ResolveUndoRecord>>,
380 pub(crate) split_link: Option<crate::split_index::SplitIndexLink>,
382 pub cache_tree_root: Option<ObjectId>,
384 pub cache_tree: Option<CacheTreeNode>,
386 pub hash_algo: HashAlgo,
390 pub source_mtime: Option<(u32, u32)>,
392 pub(crate) defer_untracked_cache_invalidation: bool,
394 pub(crate) skip_racy_smudge_on_write: bool,
396 pub(crate) content_verified: HashMap<Vec<u8>, ObjectId>,
401 pub(crate) fsmonitor_refresh_ran: bool,
403 pub(crate) suppress_pre_write_index_housekeeping: bool,
405}
406
407impl Default for Index {
408 fn default() -> Self {
409 Self::new()
410 }
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
415pub struct CacheTreeNode {
416 pub name: Vec<u8>,
418 pub entry_count: i32,
420 pub oid: Option<ObjectId>,
422 pub children: Vec<CacheTreeNode>,
424}
425
426impl CacheTreeNode {
427 #[must_use]
429 pub fn valid(
430 name: Vec<u8>,
431 entry_count: i32,
432 oid: ObjectId,
433 children: Vec<CacheTreeNode>,
434 ) -> Self {
435 Self {
436 name,
437 entry_count,
438 oid: Some(oid),
439 children,
440 }
441 }
442
443 pub fn invalidate(&mut self) {
445 self.entry_count = -1;
446 self.oid = None;
447 }
448
449 #[must_use]
451 pub fn is_valid(&self) -> bool {
452 self.entry_count >= 0 && self.oid.is_some()
453 }
454}
455
456pub use crate::index_eoie_ieot::IndexParseContext;
457
458#[derive(Debug, Clone, Copy)]
460pub struct IndexLoadOptions {
461 pub expand_sparse_directories: bool,
463 pub parse_context: IndexParseContext,
465}
466
467impl Default for IndexLoadOptions {
468 fn default() -> Self {
469 Self {
470 expand_sparse_directories: true,
471 parse_context: IndexParseContext::default(),
472 }
473 }
474}
475
476const INDEX_ENV_INVALID_FALLBACK: u32 = 3;
478const INDEX_CONFIG_INVALID_FALLBACK: u32 = 3;
480const INDEX_FORMAT_LB: u32 = 2;
482const INDEX_FORMAT_UB: u32 = 4;
485pub(crate) const INDEX_EXT_CACHE_TREE: u32 = 0x5452_4545;
487
488pub(crate) fn parse_cache_tree(data: &[u8], hash_len: usize) -> Option<CacheTreeNode> {
490 let (node, pos) = parse_cache_tree_node(data, 0, hash_len)?;
491 if pos == data.len() {
492 Some(node)
493 } else {
494 None
495 }
496}
497
498fn parse_cache_tree_node(
499 data: &[u8],
500 mut pos: usize,
501 hash_len: usize,
502) -> Option<(CacheTreeNode, usize)> {
503 let name_end = data.get(pos..)?.iter().position(|&b| b == 0)? + pos;
504 let name = data[pos..name_end].to_vec();
505 pos = name_end + 1;
506
507 let (entry_count, consumed) = parse_signed_int_prefix(&data[pos..])?;
508 pos += consumed;
509 if data.get(pos) != Some(&b' ') {
510 return None;
511 }
512 pos += 1;
513 let (subtree_count, consumed) = parse_signed_int_prefix(&data[pos..])?;
514 if subtree_count < 0 {
515 return None;
516 }
517 pos += consumed;
518 if data.get(pos) != Some(&b'\n') {
519 return None;
520 }
521 pos += 1;
522
523 let oid = if entry_count >= 0 {
524 if data.len().saturating_sub(pos) < hash_len {
525 return None;
526 }
527 let oid = ObjectId::from_bytes(&data[pos..pos + hash_len]).ok()?;
528 pos += hash_len;
529 Some(oid)
530 } else {
531 None
532 };
533
534 let mut children = Vec::with_capacity(subtree_count as usize);
535 for _ in 0..subtree_count {
536 let (child, next) = parse_cache_tree_node(data, pos, hash_len)?;
537 children.push(child);
538 pos = next;
539 }
540
541 Some((
542 CacheTreeNode {
543 name,
544 entry_count,
545 oid,
546 children,
547 },
548 pos,
549 ))
550}
551
552fn parse_signed_int_prefix(data: &[u8]) -> Option<(i32, usize)> {
553 let mut j = 0usize;
554 while j < data.len() && data[j] == b' ' {
555 j += 1;
556 }
557 let start = j;
558 if j < data.len() && data[j] == b'-' {
559 j += 1;
560 }
561 let digit_start = j;
562 while j < data.len() && data[j].is_ascii_digit() {
563 j += 1;
564 }
565 if j == digit_start {
566 return None;
567 }
568 let s = std::str::from_utf8(&data[start..j]).ok()?;
569 let v: i32 = s.parse().ok()?;
570 Some((v, j))
571}
572
573fn serialize_cache_tree_node(node: &CacheTreeNode, out: &mut Vec<u8>) {
574 out.extend_from_slice(&node.name);
575 out.push(0);
576 out.extend_from_slice(node.entry_count.to_string().as_bytes());
577 out.push(b' ');
578 out.extend_from_slice(node.children.len().to_string().as_bytes());
579 out.push(b'\n');
580 if node.entry_count >= 0 {
581 if let Some(oid) = node.oid {
582 out.extend_from_slice(oid.as_bytes());
583 }
584 }
585 for child in &node.children {
586 serialize_cache_tree_node(child, out);
587 }
588}
589
590fn format_cache_tree_node(node: &CacheTreeNode, parent_path: &str, out: &mut String) {
591 let path = if node.name.is_empty() {
592 String::new()
593 } else {
594 let name = String::from_utf8_lossy(&node.name);
595 format!("{parent_path}{name}/")
596 };
597 if node.is_valid() {
598 if let Some(oid) = node.oid {
599 out.push_str(&format!(
600 "{} {} ({} entries, {} subtrees)\n",
601 oid,
602 path,
603 node.entry_count,
604 node.children.len()
605 ));
606 }
607 } else {
608 out.push_str(&format!(
609 "{:<40} {} ({} subtrees)\n",
610 "invalid",
611 path,
612 node.children.len()
613 ));
614 }
615 for child in &node.children {
616 format_cache_tree_node(child, &path, out);
617 }
618}
619
620fn dump_one_line(node: &CacheTreeNode, pfx: &str, out: &mut String) {
622 if node.entry_count < 0 {
623 out.push_str(&format!(
624 "{:<40} {} ({} subtrees)\n",
625 "invalid",
626 pfx,
627 node.children.len()
628 ));
629 } else {
630 let oid = node.oid.unwrap_or_else(ObjectId::zero);
631 out.push_str(&format!(
632 "{} {} ({} entries, {} subtrees)\n",
633 oid,
634 pfx,
635 node.entry_count,
636 node.children.len()
637 ));
638 }
639}
640
641fn dump_cache_tree_pair(
644 it: &CacheTreeNode,
645 reference: &CacheTreeNode,
646 pfx: &str,
647 out: &mut String,
648) {
649 dump_one_line(it, pfx, out);
650
651 for child in &it.children {
652 let Some(ref_child) = reference.children.iter().find(|c| c.name == child.name) else {
653 continue;
654 };
655 let name = String::from_utf8_lossy(&child.name);
656 let child_pfx = format!("{pfx}{name}/");
657 dump_cache_tree_pair(child, ref_child, &child_pfx, out);
658 }
659}
660
661pub fn get_index_format_from_env() -> Option<u32> {
667 let val = crate::environment::Environment::empty().var("GIT_INDEX_VERSION")?;
668 if val.is_empty() {
669 return None;
670 }
671 match val.parse::<u32>() {
672 Ok(v) if (INDEX_FORMAT_LB..=INDEX_FORMAT_UB).contains(&v) => Some(v),
673 _ => Some(INDEX_ENV_INVALID_FALLBACK),
674 }
675}
676
677pub(crate) fn parse_fsmonitor_extension(
679 ext_data: &[u8],
680) -> Result<(String, crate::ewah_bitmap::EwahBitmap)> {
681 if ext_data.len() < 4 + 1 + 4 {
682 return Err(Error::IndexFsmonitorExtensionTruncated);
683 }
684 let hdr_version = u32::from_be_bytes(
685 ext_data[0..4]
686 .try_into()
687 .map_err(|_| Error::IndexFsmonitorExtensionTruncated)?,
688 );
689 let mut pos = 4usize;
690 let token = match hdr_version {
691 FSMONITOR_EXT_VERSION1 => {
692 if ext_data.len() < pos + 8 + 4 {
693 return Err(Error::IndexFsmonitorExtensionTruncated);
694 }
695 let ts = u64::from_be_bytes(
696 ext_data[pos..pos + 8]
697 .try_into()
698 .map_err(|_| Error::IndexFsmonitorExtensionTruncated)?,
699 );
700 pos += 8;
701 ts.to_string()
702 }
703 FSMONITOR_EXT_VERSION2 => {
704 let nul = ext_data[pos..]
705 .iter()
706 .position(|&b| b == 0)
707 .ok_or(Error::IndexFsmonitorExtensionTruncated)?;
708 let token = String::from_utf8_lossy(&ext_data[pos..pos + nul]).into_owned();
709 pos += nul + 1;
710 token
711 }
712 v => return Err(Error::IndexFsmonitorExtensionBadVersion(v)),
713 };
714 if ext_data.len() < pos + 4 {
715 return Err(Error::IndexFsmonitorExtensionTruncated);
716 }
717 let ewah_size = u32::from_be_bytes(
718 ext_data[pos..pos + 4]
719 .try_into()
720 .map_err(|_| Error::IndexFsmonitorExtensionTruncated)?,
721 ) as usize;
722 pos += 4;
723 if ext_data.len() < pos + ewah_size {
724 return Err(Error::IndexFsmonitorExtensionTruncated);
725 }
726 let ewah_slice = &ext_data[pos..pos + ewah_size];
727 let (dirty, consumed) = crate::ewah_bitmap::EwahBitmap::deserialize_prefix(ewah_slice)
728 .ok_or(Error::IndexFsmonitorExtensionEwahInvalid)?;
729 if consumed != ewah_size {
730 return Err(Error::IndexFsmonitorExtensionEwahInvalid);
731 }
732 Ok((token, dirty))
733}
734
735pub(crate) fn apply_fsmonitor_dirty_to_entries(
737 entries: &mut [IndexEntry],
738 dirty: &crate::ewah_bitmap::EwahBitmap,
739) {
740 for entry in entries.iter_mut() {
741 entry.set_fsmonitor_valid(true);
742 }
743 dirty.each_set_bit(|pos| {
744 if let Some(entry) = entries.get_mut(pos) {
745 entry.set_fsmonitor_valid(false);
746 }
747 });
748}
749
750impl Index {
751 #[must_use]
755 pub fn new() -> Self {
756 let version = get_index_format_from_env().unwrap_or(2);
757 Self {
758 version,
759 entries: Vec::new(),
760 entries_sorted: true,
761 sparse_directories: false,
762 untracked_cache: None,
763 fsmonitor_last_update: None,
764 fsmonitor_dirty: None,
765 fsmonitor_raw_extension: None,
766 resolve_undo: None,
767 split_link: None,
768 cache_tree_root: None,
769 cache_tree: None,
770 hash_algo: HashAlgo::Sha1,
771 source_mtime: None,
772 defer_untracked_cache_invalidation: false,
773 skip_racy_smudge_on_write: false,
774 content_verified: HashMap::new(),
775 fsmonitor_refresh_ran: false,
776 suppress_pre_write_index_housekeeping: false,
777 }
778 }
779
780 #[must_use]
785 pub fn gitattributes_snapshot(&self) -> Self {
786 let mut snap = Self::new();
787 snap.version = self.version;
788 snap.hash_algo = self.hash_algo;
789 for entry in &self.entries {
790 if entry.stage() != 0 {
791 continue;
792 }
793 if gitattributes_index_path(&entry.path) {
794 snap.add_or_replace(entry.clone());
795 }
796 }
797 snap
798 }
799
800 #[must_use]
808 pub fn empty(version: u32) -> Self {
809 Self {
810 version,
811 entries: Vec::new(),
812 entries_sorted: true,
813 sparse_directories: false,
814 untracked_cache: None,
815 fsmonitor_last_update: None,
816 fsmonitor_dirty: None,
817 fsmonitor_raw_extension: None,
818 resolve_undo: None,
819 split_link: None,
820 cache_tree_root: None,
821 cache_tree: None,
822 hash_algo: HashAlgo::Sha1,
823 source_mtime: None,
824 defer_untracked_cache_invalidation: false,
825 skip_racy_smudge_on_write: false,
826 content_verified: HashMap::new(),
827 fsmonitor_refresh_ran: false,
828 suppress_pre_write_index_housekeeping: false,
829 }
830 }
831
832 pub fn new_with_config(
837 config_index_version: Option<&str>,
838 config_many_files: Option<&str>,
839 ) -> Self {
840 if let Some(v) = get_index_format_from_env() {
841 return Self {
842 version: v,
843 entries: Vec::new(),
844 entries_sorted: true,
845 sparse_directories: false,
846 untracked_cache: None,
847 fsmonitor_last_update: None,
848 fsmonitor_dirty: None,
849 fsmonitor_raw_extension: None,
850 resolve_undo: None,
851 split_link: None,
852 cache_tree_root: None,
853 cache_tree: None,
854 hash_algo: HashAlgo::default(),
855 source_mtime: None,
856 defer_untracked_cache_invalidation: false,
857 skip_racy_smudge_on_write: false,
858 content_verified: HashMap::new(),
859 fsmonitor_refresh_ran: false,
860 suppress_pre_write_index_housekeeping: false,
861 };
862 }
863
864 let many_files = config_truthy(config_many_files);
865 let mut version = if many_files { 4 } else { 2 };
866
867 if let Some(val) = config_index_version {
868 let trimmed = val.trim();
869 if !trimmed.is_empty() {
870 match trimmed.parse::<u32>() {
871 Ok(v) if (INDEX_FORMAT_LB..=INDEX_FORMAT_UB).contains(&v) => {
872 version = v;
873 }
874 _ => {
875 version = INDEX_CONFIG_INVALID_FALLBACK;
876 }
877 }
878 }
879 }
880
881 Self {
882 version,
883 entries: Vec::new(),
884 entries_sorted: true,
885 sparse_directories: false,
886 untracked_cache: None,
887 fsmonitor_last_update: None,
888 fsmonitor_dirty: None,
889 fsmonitor_raw_extension: None,
890 resolve_undo: None,
891 split_link: None,
892 cache_tree_root: None,
893 cache_tree: None,
894 hash_algo: HashAlgo::default(),
895 source_mtime: None,
896 defer_untracked_cache_invalidation: false,
897 skip_racy_smudge_on_write: false,
898 content_verified: HashMap::new(),
899 fsmonitor_refresh_ran: false,
900 suppress_pre_write_index_housekeeping: false,
901 }
902 }
903
904 #[must_use]
909 pub fn new_from_config(config: &ConfigSet) -> Self {
910 if let Some(v) = get_index_format_from_env() {
911 return Self {
912 version: v,
913 entries: Vec::new(),
914 entries_sorted: true,
915 sparse_directories: false,
916 untracked_cache: None,
917 fsmonitor_last_update: None,
918 fsmonitor_dirty: None,
919 fsmonitor_raw_extension: None,
920 resolve_undo: None,
921 split_link: None,
922 cache_tree_root: None,
923 cache_tree: None,
924 hash_algo: HashAlgo::default(),
925 source_mtime: None,
926 defer_untracked_cache_invalidation: false,
927 skip_racy_smudge_on_write: false,
928 content_verified: HashMap::new(),
929 fsmonitor_refresh_ran: false,
930 suppress_pre_write_index_housekeeping: false,
931 };
932 }
933
934 let many_files = config
935 .get_bool("feature.manyFiles")
936 .and_then(|r| r.ok())
937 .unwrap_or(false);
938 let mut version = if many_files { 4 } else { 2 };
939
940 if let Some(val) = config.get("index.version") {
941 let trimmed = val.trim();
942 if !trimmed.is_empty() {
943 match trimmed.parse::<u32>() {
944 Ok(v) if (INDEX_FORMAT_LB..=INDEX_FORMAT_UB).contains(&v) => {
945 version = v;
946 }
947 _ => {
948 version = INDEX_CONFIG_INVALID_FALLBACK;
949 }
950 }
951 }
952 }
953
954 Self {
955 version,
956 entries: Vec::new(),
957 entries_sorted: true,
958 sparse_directories: false,
959 untracked_cache: None,
960 fsmonitor_last_update: None,
961 fsmonitor_dirty: None,
962 fsmonitor_raw_extension: None,
963 resolve_undo: None,
964 split_link: None,
965 cache_tree_root: None,
966 cache_tree: None,
967 hash_algo: HashAlgo::default(),
968 source_mtime: None,
969 defer_untracked_cache_invalidation: false,
970 skip_racy_smudge_on_write: false,
971 content_verified: HashMap::new(),
972 fsmonitor_refresh_ran: false,
973 suppress_pre_write_index_housekeeping: false,
974 }
975 }
976
977 pub fn load(path: &Path) -> Result<Self> {
985 match read_index_file(path) {
986 Ok((data, mtime)) => {
987 let mut idx = Self::parse(&data)?;
988 idx.source_mtime = Some(mtime);
989 Ok(idx)
990 }
991 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(Self {
992 sparse_directories: false,
993 ..Self::new()
994 }),
995 Err(e) => Err(Error::Io(e)),
996 }
997 }
998
999 pub fn load_expand_sparse(path: &Path, odb: &Odb) -> Result<Self> {
1004 let mut idx = match read_index_file(path) {
1005 Ok((data, mtime)) => Self {
1006 source_mtime: Some(mtime),
1007 ..Self::parse_with_algo(&data, odb.hash_algo())?
1008 },
1009 Err(e) if e.kind() == io::ErrorKind::NotFound => Self {
1010 sparse_directories: false,
1011 hash_algo: odb.hash_algo(),
1012 ..Self::new()
1013 },
1014 Err(e) => return Err(Error::Io(e)),
1015 };
1016 idx.expand_sparse_directory_placeholders(
1017 odb,
1018 crate::path_validate::ProtectOptions::default(),
1019 )?;
1020 Ok(idx)
1021 }
1022
1023 pub fn load_expand_sparse_with_config(
1025 path: &Path,
1026 odb: &Odb,
1027 config: &ConfigSet,
1028 ) -> Result<Self> {
1029 Self::load_expand_sparse_optional_with_context(
1030 path,
1031 odb,
1032 Some(config),
1033 IndexParseContext::default(),
1034 )
1035 }
1036
1037 pub fn load_expand_sparse_optional(path: &Path, odb: &Odb) -> Result<Self> {
1041 Self::load_expand_sparse_optional_with_context(
1042 path,
1043 odb,
1044 None,
1045 IndexParseContext::default(),
1046 )
1047 }
1048
1049 pub fn load_expand_sparse_optional_with_context(
1051 path: &Path,
1052 odb: &Odb,
1053 config: Option<&ConfigSet>,
1054 parse_context: IndexParseContext,
1055 ) -> Result<Self> {
1056 let mut idx =
1057 Self::load_preserving_sparse_optional_with_context(path, odb, config, parse_context)?;
1058 let protect = config
1059 .map(crate::path_validate::ProtectOptions::from_config)
1060 .unwrap_or_default();
1061 idx.expand_sparse_directory_placeholders(odb, protect)?;
1062 Ok(idx)
1063 }
1064
1065 pub(crate) fn load_preserving_sparse_optional_with_context(
1068 path: &Path,
1069 odb: &Odb,
1070 config: Option<&ConfigSet>,
1071 parse_context: IndexParseContext,
1072 ) -> Result<Self> {
1073 let algo = odb.hash_algo();
1074 let idx = match read_index_file(path) {
1075 Ok((data, mtime)) => Self {
1076 source_mtime: Some(mtime),
1077 ..Self::parse_with_algo_and_config(&data, algo, config, parse_context)?
1078 },
1079 Err(e) if e.kind() == io::ErrorKind::NotFound => Self {
1080 hash_algo: algo,
1081 ..Self::new()
1082 },
1083 Err(e) => return Err(Error::Io(e)),
1084 };
1085 Ok(idx)
1086 }
1087
1088 #[must_use]
1090 pub fn has_sparse_directory_placeholders(&self) -> bool {
1091 self.entries
1092 .iter()
1093 .any(IndexEntry::is_sparse_directory_placeholder)
1094 }
1095
1096 pub fn expand_sparse_directory_placeholders(
1101 &mut self,
1102 odb: &Odb,
1103 protect: crate::path_validate::ProtectOptions,
1104 ) -> Result<()> {
1105 self.expand_sparse_directories_matching(odb, protect, |_| true)
1106 }
1107
1108 pub(crate) fn expand_sparse_directories_matching(
1110 &mut self,
1111 odb: &Odb,
1112 protect: crate::path_validate::ProtectOptions,
1113 mut expand: impl FnMut(&IndexEntry) -> bool,
1114 ) -> Result<()> {
1115 if !self.has_sparse_directory_placeholders() {
1116 return Ok(());
1117 }
1118 let mut out: Vec<IndexEntry> = Vec::with_capacity(self.entries.len());
1119 for entry in self.entries.drain(..) {
1120 if entry.is_sparse_directory_placeholder() && expand(&entry) {
1121 let prefix = trim_trailing_slash_bytes(&entry.path);
1122 let blobs = flatten_tree_blobs(odb, &entry.oid, prefix, protect)?;
1123 #[cfg(test)]
1124 odb.hot_path_metrics()
1125 .record_sparse_expanded_rows(blobs.len());
1126 out.extend(blobs);
1127 } else {
1128 out.push(entry);
1129 }
1130 }
1131 self.entries = out;
1132 self.sparse_directories = self.has_sparse_directory_placeholders();
1133 self.sort();
1134 Ok(())
1135 }
1136
1137 pub fn try_collapse_sparse_directories(
1143 &mut self,
1144 odb: &Odb,
1145 head_tree: &ObjectId,
1146 patterns: &[String],
1147 cone_mode: bool,
1148 enable_sparse_index: bool,
1149 protect: crate::path_validate::ProtectOptions,
1150 ) -> Result<()> {
1151 if !enable_sparse_index || !cone_mode {
1152 self.sparse_directories = false;
1153 return Ok(());
1154 }
1155
1156 self.ensure_canonical_order();
1157 let mut prefixes = BTreeSet::<Vec<u8>>::new();
1158 for e in &self.entries {
1159 if e.stage() != 0 || e.mode == MODE_TREE || !e.skip_worktree() {
1160 continue;
1161 }
1162 collect_directory_prefixes(&e.path, &mut prefixes);
1163 }
1164
1165 let mut collapsed_any = false;
1166 let mut ordered: Vec<Vec<u8>> = prefixes.into_iter().collect();
1168 ordered.sort_by_key(|p| std::cmp::Reverse(p.len()));
1169
1170 for pref in ordered {
1171 let pref_str = String::from_utf8_lossy(&pref);
1172 if directory_in_cone(&pref_str, patterns, cone_mode) {
1173 continue;
1174 }
1175 let Some(subtree_oid) = tree_oid_for_prefix(odb, head_tree, &pref)? else {
1176 continue;
1177 };
1178 let expected = collect_sparse_aware_expected_blobs(
1179 odb,
1180 &subtree_oid,
1181 &pref,
1182 patterns,
1183 cone_mode,
1184 &self.entries,
1185 protect,
1186 )?;
1187 if expected.is_empty() {
1188 continue;
1189 }
1190 let mut directory = pref.clone();
1191 directory.push(b'/');
1192 let start = self.entries.partition_point(|e| e.path < directory);
1193 let end =
1194 start + self.entries[start..].partition_point(|e| e.path.starts_with(&directory));
1195 let rows = &self.entries[start..end];
1196 if rows.iter().any(|e| e.stage() != 0) {
1197 continue;
1198 }
1199 let mut matched: Vec<_> = rows
1200 .iter()
1201 .filter(|e| e.mode != MODE_TREE)
1202 .cloned()
1203 .collect();
1204 if matched.len() != expected.len() {
1205 continue;
1206 }
1207 matched.sort_by(|a, b| a.path.cmp(&b.path));
1208 let mut exp_sorted = expected;
1209 exp_sorted.sort_by(|a, b| a.path.cmp(&b.path));
1210 if !matched
1211 .iter()
1212 .zip(exp_sorted.iter())
1213 .all(|(a, b)| a.path == b.path && a.oid == b.oid && a.mode == b.mode)
1214 {
1215 continue;
1216 }
1217 if !matched.iter().all(|e| e.skip_worktree()) {
1218 continue;
1219 }
1220 if matched.iter().any(|e| e.mode == MODE_GITLINK)
1224 || exp_sorted.iter().any(|e| e.mode == MODE_GITLINK)
1225 {
1226 continue;
1227 }
1228
1229 let mut path_with_slash = pref.clone();
1230 if !path_with_slash.ends_with(b"/") {
1231 path_with_slash.push(b'/');
1232 }
1233 self.entries
1234 .retain(|e| e.stage() != 0 || !path_under_prefix(&e.path, &pref));
1235 let mut placeholder = IndexEntry {
1236 ctime_sec: 0,
1237 ctime_nsec: 0,
1238 mtime_sec: 0,
1239 mtime_nsec: 0,
1240 dev: 0,
1241 ino: 0,
1242 mode: MODE_TREE,
1243 uid: 0,
1244 gid: 0,
1245 size: 0,
1246 oid: subtree_oid,
1247 flags: path_with_slash.len().min(0xFFF) as u16,
1248 flags_extended: Some(0),
1249 path: path_with_slash,
1250 base_index_pos: 0,
1251 };
1252 placeholder.set_skip_worktree(true);
1253 self.add_or_replace(placeholder);
1254 collapsed_any = true;
1255 }
1256
1257 if collapsed_any {
1258 self.sort();
1259 self.sparse_directories = true;
1260 }
1261 self.sparse_directories = self.has_sparse_directory_placeholders();
1262 Ok(())
1263 }
1264
1265 pub fn parse(data: &[u8]) -> Result<Self> {
1271 Self::parse_with_algo(data, detect_index_hash_algo(data))
1275 }
1276
1277 pub fn parse_with_algo(data: &[u8], hash_algo: HashAlgo) -> Result<Self> {
1281 Self::parse_with_algo_and_config(data, hash_algo, None, IndexParseContext::default())
1282 }
1283
1284 pub fn parse_with_algo_and_config(
1286 data: &[u8],
1287 hash_algo: HashAlgo,
1288 config: Option<&ConfigSet>,
1289 parse_context: IndexParseContext,
1290 ) -> Result<Self> {
1291 const INDEX_HEADER_LEN: usize = 12;
1292
1293 let hash_len = hash_algo.len();
1294 if data.len() < INDEX_HEADER_LEN + hash_len {
1295 return Err(Error::IndexError(
1296 "file too short for index header and checksum".to_owned(),
1297 ));
1298 }
1299 let (body, checksum) = data.split_at(data.len() - hash_len);
1300 if body.len() < INDEX_HEADER_LEN {
1301 return Err(Error::IndexError(
1302 "file too short for index header and checksum".to_owned(),
1303 ));
1304 }
1305 let trailer_error = |e: crate::hash::TrailerMismatch| {
1306 Error::IndexError(format!(
1307 "{} checksum mismatch: {e}",
1308 hash_algo.name().to_uppercase()
1309 ))
1310 };
1311 if checksum.iter().all(|&b| b == 0) {
1312 return Self::parse_body(body, hash_algo, config, parse_context);
1313 }
1314 let verify_in_parallel = data.len() >= PARALLEL_TRAILER_VERIFY_MIN_BYTES
1315 && parse_context.thread_policy.threads_override != Some(1);
1316 if !verify_in_parallel {
1317 verify_trailer(hash_algo, data).map_err(trailer_error)?;
1318 return Self::parse_body(body, hash_algo, config, parse_context);
1319 }
1320 std::thread::scope(|scope| {
1323 let verifier = scope.spawn(|| verify_trailer(hash_algo, data));
1324 let parsed = Self::parse_body(body, hash_algo, config, parse_context);
1325 verifier
1326 .join()
1327 .map_err(|_| Error::Message("index checksum worker thread panicked".to_owned()))?
1328 .map_err(trailer_error)?;
1329 parsed
1330 })
1331 }
1332
1333 fn parse_body(
1335 body: &[u8],
1336 hash_algo: HashAlgo,
1337 config: Option<&ConfigSet>,
1338 parse_context: IndexParseContext,
1339 ) -> Result<Self> {
1340 use crate::index_eoie_ieot::{
1341 index_load_worker_count, parallel_index_load_allowed, parse_entries_parallel,
1342 parse_entries_serial, parse_index_extensions, read_eoie_extension, read_ieot_extension,
1343 ParsedIndexExtensions,
1344 };
1345
1346 let hash_len = hash_algo.len();
1347 let magic = &body[..4];
1348 if magic != b"DIRC" {
1349 return Err(Error::IndexError("bad magic: expected DIRC".to_owned()));
1350 }
1351 let version = u32::from_be_bytes(
1352 body[4..8]
1353 .try_into()
1354 .map_err(|_| Error::IndexError("cannot read version".to_owned()))?,
1355 );
1356 if version != 2 && version != 3 && version != 4 {
1357 return Err(Error::IndexError(format!(
1358 "unsupported index version {version}"
1359 )));
1360 }
1361 let count = u32::from_be_bytes(
1362 body[8..12]
1363 .try_into()
1364 .map_err(|_| Error::IndexError("cannot read entry count".to_owned()))?,
1365 );
1366
1367 let workers = index_load_worker_count(parse_context.thread_policy, config, count as usize);
1368 let extension_offset = if workers > 1 {
1369 read_eoie_extension(body, hash_algo)
1370 } else {
1371 None
1372 };
1373
1374 let eoie_payload = 4 + hash_len;
1375 let eoie_with_header = 8 + eoie_payload;
1376 let extensions_end = body
1377 .len()
1378 .checked_sub(eoie_with_header)
1379 .filter(|_| extension_offset.is_some())
1380 .unwrap_or(body.len());
1381
1382 let try_parallel = extension_offset.is_some()
1383 && workers > 1
1384 && parallel_index_load_allowed(parse_context.thread_policy, config, count as usize);
1385
1386 let (entries, ext_fields): (Vec<IndexEntry>, ParsedIndexExtensions) = if try_parallel {
1387 let Some(ext_off) = extension_offset else {
1388 return Err(Error::IndexError(
1389 "parallel index load missing EOIE offset".to_owned(),
1390 ));
1391 };
1392 let ieot = read_ieot_extension(body, ext_off, extensions_end);
1393 let mut loaded_entries = if let Some(ref ieot) = ieot {
1394 parse_entries_parallel(body, version, count, hash_len, ieot, workers)?
1395 } else {
1396 parse_entries_serial(body, version, count, hash_len, 12)?.0
1397 };
1398 let ext_fields =
1399 parse_index_extensions(body, ext_off, extensions_end, hash_algo, None)?;
1400 if let Some(dirty) = ext_fields.fsmonitor_dirty.as_ref() {
1401 apply_fsmonitor_dirty_to_entries(&mut loaded_entries, dirty);
1402 }
1403 (loaded_entries, ext_fields)
1404 } else {
1405 let (mut entries, pos) = parse_entries_serial(body, version, count, hash_len, 12)?;
1406 let ext_fields =
1407 parse_index_extensions(body, pos, body.len(), hash_algo, Some(&mut entries))?;
1408 (entries, ext_fields)
1409 };
1410
1411 Ok(Self {
1412 version,
1413 entries,
1414 entries_sorted: true,
1415 sparse_directories: ext_fields.sparse_directories,
1416 untracked_cache: ext_fields.untracked_cache,
1417 fsmonitor_last_update: ext_fields.fsmonitor_last_update,
1418 fsmonitor_dirty: ext_fields.fsmonitor_dirty,
1419 fsmonitor_raw_extension: ext_fields.fsmonitor_raw_extension,
1420 resolve_undo: ext_fields.resolve_undo,
1421 split_link: ext_fields.split_link,
1422 cache_tree_root: ext_fields.cache_tree_root,
1423 cache_tree: ext_fields.cache_tree,
1424 hash_algo,
1425 source_mtime: None,
1426 defer_untracked_cache_invalidation: false,
1427 skip_racy_smudge_on_write: false,
1428 content_verified: HashMap::new(),
1429 fsmonitor_refresh_ran: false,
1430 suppress_pre_write_index_housekeeping: false,
1431 })
1432 }
1433
1434 pub fn write(&mut self, path: &Path) -> Result<()> {
1440 let git_dir = path.parent();
1441 let config = git_dir.and_then(|d| {
1442 ConfigSet::load(&crate::environment::Environment::empty(), Some(d), true).ok()
1443 });
1444 let skip_hash = index_skip_hash_for_write(config.as_ref());
1445 self.write_to_path(path, skip_hash)
1446 }
1447
1448 pub fn write_with_config(&mut self, path: &Path, config: &ConfigSet) -> Result<()> {
1454 self.write_with_config_and_policy(path, config, crate::parallel::ThreadPolicy::default())
1455 }
1456
1457 pub fn write_with_config_and_policy(
1459 &mut self,
1460 path: &Path,
1461 config: &ConfigSet,
1462 policy: crate::parallel::ThreadPolicy,
1463 ) -> Result<()> {
1464 let skip_hash = index_skip_hash_for_write(Some(config));
1465 self.write_to_path_with_config_and_policy(path, skip_hash, Some(config), policy)
1466 }
1467
1468 pub fn write_to_path(&mut self, path: &Path, skip_hash: bool) -> Result<()> {
1472 self.write_to_path_with_config(path, skip_hash, None)
1473 }
1474
1475 pub(crate) fn write_to_path_with_config(
1477 &mut self,
1478 path: &Path,
1479 skip_hash: bool,
1480 config: Option<&ConfigSet>,
1481 ) -> Result<()> {
1482 self.write_to_path_with_config_and_policy(
1483 path,
1484 skip_hash,
1485 config,
1486 crate::parallel::ThreadPolicy::default(),
1487 )
1488 }
1489
1490 pub(crate) fn write_to_path_with_config_and_policy(
1491 &mut self,
1492 path: &Path,
1493 skip_hash: bool,
1494 config: Option<&ConfigSet>,
1495 policy: crate::parallel::ThreadPolicy,
1496 ) -> Result<()> {
1497 self.write_to_path_with_config_and_lock_policy(path, skip_hash, config, None, policy)
1498 }
1499
1500 pub(crate) fn write_to_path_with_config_and_lock_policy(
1501 &mut self,
1502 path: &Path,
1503 skip_hash: bool,
1504 config: Option<&ConfigSet>,
1505 existing_lock: Option<&mut IndexLock>,
1506 policy: crate::parallel::ThreadPolicy,
1507 ) -> Result<()> {
1508 self.write_to_path_with_config_and_lock_mode_policy(
1509 path,
1510 skip_hash,
1511 config,
1512 existing_lock,
1513 true,
1514 policy,
1515 )
1516 }
1517
1518 pub(crate) fn write_to_path_with_config_and_lock_mode_policy(
1519 &mut self,
1520 path: &Path,
1521 skip_hash: bool,
1522 config: Option<&ConfigSet>,
1523 existing_lock: Option<&mut IndexLock>,
1524 commit: bool,
1525 policy: crate::parallel::ThreadPolicy,
1526 ) -> Result<()> {
1527 let body = self.serialize_index_body(config, policy)?;
1528
1529 if let Some(lock) = existing_lock {
1530 if lock.index_path != path {
1531 return Err(Error::Message(
1532 "index lock path does not match write target".into(),
1533 ));
1534 }
1535 let checksum: Vec<u8> = if skip_hash {
1536 vec![0u8; self.hash_algo.len()]
1537 } else {
1538 hash_index_body(self.hash_algo, &body)
1539 };
1540 lock.write_index_contents(&body, &checksum)?;
1541 if commit {
1542 lock.commit_replace_index()?;
1543 }
1544 return Ok(());
1545 }
1546
1547 let tmp_path = path.with_extension("lock");
1548 let pid_path = pid_path_for_lock(&tmp_path);
1549 let lockfile_pid_enabled = lockfile_pid_enabled(path, config);
1550
1551 use crate::lockfile::{LockFile, LockWait};
1552 let wait = LockWait::files_ref_lock(config);
1553 let (tmp_path, mut lock_file) =
1554 LockFile::acquire(&tmp_path, wait).and_then(LockFile::into_open_file)?;
1555
1556 let mut wrote_pid_file = false;
1557 if lockfile_pid_enabled {
1558 if let Err(e) = write_lock_pid_file(&pid_path) {
1559 let _ = fs::remove_file(&tmp_path);
1560 return Err(Error::Io(e));
1561 }
1562 wrote_pid_file = true;
1563 }
1564
1565 if let Err(e) =
1566 write_index_body_and_trailer(&mut lock_file, self.hash_algo, skip_hash, &body)
1567 {
1568 let _ = fs::remove_file(&tmp_path);
1569 if wrote_pid_file {
1570 let _ = fs::remove_file(&pid_path);
1571 }
1572 return Err(Error::Io(e));
1573 }
1574 drop(lock_file);
1575
1576 if let Err(e) = fs::rename(&tmp_path, path) {
1577 let _ = fs::remove_file(&tmp_path);
1578 if wrote_pid_file {
1579 let _ = fs::remove_file(&pid_path);
1580 }
1581 return Err(Error::Io(e));
1582 }
1583 if wrote_pid_file {
1584 let _ = fs::remove_file(&pid_path);
1585 }
1586 Ok(())
1587 }
1588
1589 fn serialize_index_body(
1590 &mut self,
1591 config: Option<&ConfigSet>,
1592 policy: crate::parallel::ThreadPolicy,
1593 ) -> Result<Vec<u8>> {
1594 self.ensure_canonical_order();
1595 let mut body = Vec::new();
1596 self.serialize_into_with_options(&mut body, config, policy)?;
1597 Ok(body)
1598 }
1599
1600 #[cfg(test)]
1601 pub(crate) fn serialize_into(&self, out: &mut Vec<u8>) -> Result<()> {
1605 self.serialize_into_with_options(out, None, crate::parallel::ThreadPolicy::default())
1606 }
1607
1608 pub(crate) fn serialize_into_with_options(
1609 &self,
1610 out: &mut Vec<u8>,
1611 config: Option<&ConfigSet>,
1612 policy: crate::parallel::ThreadPolicy,
1613 ) -> Result<()> {
1614 use crate::index_eoie_ieot::{
1615 entries_per_ieot_block, ieot_block_count_for_write, record_end_of_index_entries,
1616 write_ieot_payload, IeotEntry, IeotTable, INDEX_EXT_EOIE, INDEX_EXT_IEOT,
1617 };
1618 let has_extended_flags = self.entries.iter().any(|e| e.flags_extended.is_some());
1619 let write_version = if self.version >= 4 {
1620 4
1621 } else if has_extended_flags {
1622 3
1623 } else if self.version >= 3 {
1624 2
1625 } else {
1626 self.version
1627 };
1628 out.reserve(self.estimate_body_capacity(write_version, config));
1629 out.extend_from_slice(b"DIRC");
1631 out.extend_from_slice(&write_version.to_be_bytes());
1632 out.extend_from_slice(&(self.entries.len() as u32).to_be_bytes());
1633
1634 let ieot_blocks = ieot_block_count_for_write(policy, config, self.entries.len());
1635 let ieot_stride = ieot_blocks.map(|b| entries_per_ieot_block(self.entries.len(), b));
1636 let mut ieot_table = IeotTable {
1637 entries: Vec::new(),
1638 };
1639 let mut nr_in_block = 0u32;
1640 let mut block_start_offset = out.len();
1641
1642 if write_version == 4 {
1643 let mut previous_path: Vec<u8> = Vec::new();
1644 for (i, entry) in self.entries.iter().enumerate() {
1645 if let Some(stride) = ieot_stride {
1646 if i > 0 && i % stride == 0 {
1647 ieot_table.entries.push(IeotEntry {
1648 offset: block_start_offset as u32,
1649 nr: nr_in_block,
1650 });
1651 nr_in_block = 0;
1652 if !previous_path.is_empty() {
1655 previous_path[0] = 0;
1656 }
1657 block_start_offset = out.len();
1658 }
1659 }
1660 serialize_entry_v4(entry, &mut previous_path, out);
1661 nr_in_block += 1;
1662 }
1663 } else {
1664 for (i, entry) in self.entries.iter().enumerate() {
1665 if let Some(stride) = ieot_stride {
1666 if i > 0 && i % stride == 0 {
1667 ieot_table.entries.push(IeotEntry {
1668 offset: block_start_offset as u32,
1669 nr: nr_in_block,
1670 });
1671 nr_in_block = 0;
1672 block_start_offset = out.len();
1673 }
1674 }
1675 serialize_entry(entry, write_version, out);
1676 nr_in_block += 1;
1677 }
1678 }
1679 if ieot_blocks.is_some() && nr_in_block > 0 {
1680 ieot_table.entries.push(IeotEntry {
1681 offset: block_start_offset as u32,
1682 nr: nr_in_block,
1683 });
1684 }
1685
1686 let extension_offset = out.len();
1687 let write_eoie = record_end_of_index_entries(config);
1688 let mut eoie_header_bytes: Option<Vec<u8>> = write_eoie.then(Vec::new);
1689
1690 if !ieot_table.entries.is_empty() {
1691 let mut payload = Vec::new();
1692 write_ieot_payload(&ieot_table, &mut payload);
1693 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_IEOT, &payload);
1694 }
1695 if self.sparse_directories {
1696 append_index_extension(
1697 out,
1698 &mut eoie_header_bytes,
1699 INDEX_EXT_SPARSE_DIRECTORIES,
1700 &[],
1701 );
1702 }
1703 if let Some(uc) = &self.untracked_cache {
1704 let payload = untracked_cache::write_untracked_extension(uc);
1705 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_UNTRACKED, &payload);
1706 }
1707 self.append_fsmonitor_extension(out, &mut eoie_header_bytes)?;
1708 if let Some(ru) = &self.resolve_undo {
1709 let payload = write_resolve_undo_payload(ru);
1710 if !payload.is_empty() {
1711 append_index_extension(
1712 out,
1713 &mut eoie_header_bytes,
1714 INDEX_EXT_RESOLVE_UNDO,
1715 &payload,
1716 );
1717 }
1718 }
1719 if let Some(sl) = &self.split_link {
1720 use crate::ewah_bitmap::EwahBitmap;
1721 let del = sl
1722 .delete_bitmap
1723 .as_ref()
1724 .cloned()
1725 .unwrap_or_else(EwahBitmap::new);
1726 let rep = sl
1727 .replace_bitmap
1728 .as_ref()
1729 .cloned()
1730 .unwrap_or_else(EwahBitmap::new);
1731 let payload =
1732 crate::split_index::serialize_link_extension_payload(&sl.base_oid, &del, &rep);
1733 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_LINK, &payload);
1734 }
1735 if let Some(cache_tree) = &self.cache_tree {
1736 let mut payload = Vec::new();
1737 serialize_cache_tree_node(cache_tree, &mut payload);
1738 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_CACHE_TREE, &payload);
1739 }
1740 if write_eoie {
1741 let mut eoie_payload = Vec::new();
1742 eoie_payload.extend_from_slice(&(extension_offset as u32).to_be_bytes());
1743 let headers = eoie_header_bytes.as_deref().unwrap_or(&[]);
1744 eoie_payload.extend_from_slice(&self.hash_algo.checksum_digest(headers));
1745 append_index_extension(out, &mut None, INDEX_EXT_EOIE, &eoie_payload);
1746 }
1747 Ok(())
1748 }
1749
1750 fn append_fsmonitor_extension(
1751 &self,
1752 out: &mut Vec<u8>,
1753 eoie_headers: &mut Option<Vec<u8>>,
1754 ) -> Result<()> {
1755 if let Some(raw) = &self.fsmonitor_raw_extension {
1756 if self.fsmonitor_raw_still_valid() {
1757 append_index_extension(out, eoie_headers, INDEX_EXT_FSMONITOR, raw);
1758 return Ok(());
1759 }
1760 }
1761 let Some(token) = &self.fsmonitor_last_update else {
1762 return Ok(());
1763 };
1764 let dirty = self
1765 .fsmonitor_dirty
1766 .as_ref()
1767 .cloned()
1768 .unwrap_or_else(|| self.build_fsmonitor_dirty_from_entries());
1769 if dirty.bit_size > self.fsmonitor_active_entry_count() {
1770 return Ok(());
1771 }
1772 let mut payload = Vec::new();
1773 payload.extend_from_slice(&FSMONITOR_EXT_VERSION2.to_be_bytes());
1774 payload.extend_from_slice(token.as_bytes());
1775 payload.push(0);
1776 let ewah_size_fixup = payload.len();
1777 payload.extend_from_slice(&0u32.to_be_bytes());
1778 let ewah_start = payload.len();
1779 dirty.serialize(&mut payload);
1780 let ewah_size = (payload.len() - ewah_start) as u32;
1781 payload[ewah_size_fixup..ewah_size_fixup + 4].copy_from_slice(&ewah_size.to_be_bytes());
1782 append_index_extension(out, eoie_headers, INDEX_EXT_FSMONITOR, &payload);
1783 Ok(())
1784 }
1785
1786 fn fsmonitor_active_entry_count(&self) -> usize {
1788 self.entries.len()
1789 }
1790
1791 fn build_fsmonitor_dirty_from_entries(&self) -> crate::ewah_bitmap::EwahBitmap {
1793 use crate::ewah_bitmap::EwahBitmap;
1794 let mut dirty = EwahBitmap::new();
1795 if self.fsmonitor_last_update.is_none() {
1796 return dirty;
1797 }
1798 #[cfg(test)]
1799 tests::fsmonitor_rebuild_counter::note_build();
1800 for (i, entry) in self.entries.iter().enumerate() {
1801 if !entry.fsmonitor_valid() {
1802 dirty.set_bit_extend(i);
1803 }
1804 }
1805 dirty
1806 }
1807
1808 fn fsmonitor_raw_still_valid(&self) -> bool {
1810 use crate::ewah_bitmap::ewah_bitmaps_equal;
1811 let Some(raw) = self.fsmonitor_raw_extension.as_ref() else {
1812 return false;
1813 };
1814 let Some(token) = self.fsmonitor_last_update.as_deref() else {
1815 return false;
1816 };
1817 let Ok((raw_token, raw_dirty)) = parse_fsmonitor_extension(raw) else {
1818 return false;
1819 };
1820 if raw_token != token {
1821 return false;
1822 }
1823 let current = self.build_fsmonitor_dirty_from_entries();
1824 if !ewah_bitmaps_equal(&raw_dirty, ¤t) {
1825 return false;
1826 }
1827 current.bit_size <= self.fsmonitor_active_entry_count()
1828 }
1829
1830 fn rebuild_fsmonitor_dirty_from_entries(&mut self) {
1832 if self.fsmonitor_last_update.is_none() {
1833 self.fsmonitor_dirty = None;
1834 return;
1835 }
1836 self.fsmonitor_dirty = Some(self.build_fsmonitor_dirty_from_entries());
1837 }
1838
1839 fn invalidate_fsmonitor_bitmap(&mut self) {
1841 if self.fsmonitor_last_update.is_none() {
1842 return;
1843 }
1844 self.fsmonitor_raw_extension = None;
1845 self.fsmonitor_dirty = None;
1846 }
1847
1848 pub fn set_fsmonitor_last_update(&mut self, token: Option<String>) {
1850 if self.fsmonitor_last_update.as_deref() != token.as_deref() {
1851 self.fsmonitor_raw_extension = None;
1852 }
1853 self.fsmonitor_last_update = token;
1854 self.rebuild_fsmonitor_dirty_from_entries();
1855 }
1856
1857 pub fn clear_fsmonitor_extension(&mut self) {
1859 self.fsmonitor_last_update = None;
1860 self.fsmonitor_dirty = None;
1861 self.fsmonitor_raw_extension = None;
1862 }
1863
1864 pub fn sync_fsmonitor_dirty_bitmap(&mut self) {
1866 self.rebuild_fsmonitor_dirty_from_entries();
1867 }
1868
1869 pub fn fsmonitor_validity_changed(&mut self) {
1871 self.invalidate_fsmonitor_bitmap();
1872 }
1873
1874 pub(crate) fn invalidate_all_fsmonitor_valid(&mut self) {
1876 for entry in &mut self.entries {
1877 if entry.stage() == 0 {
1878 entry.set_fsmonitor_valid(false);
1879 }
1880 }
1881 self.fsmonitor_validity_changed();
1882 }
1883
1884 pub(crate) fn invalidate_fsmonitor_for_path(&mut self, path: &str, ignorecase: bool) {
1886 if let Some(uc) = self.untracked_cache.as_mut() {
1887 crate::untracked_cache::invalidate_path(uc, path);
1888 }
1889 let mut invalidated = if path.ends_with('/') {
1890 self.invalidate_fsmonitor_directory_cone(path.trim_end_matches('/'), ignorecase)
1891 } else {
1892 self.invalidate_fsmonitor_unqualified_path(path, ignorecase)
1893 };
1894 if !invalidated && ignorecase {
1895 invalidated = self.invalidate_fsmonitor_icase_fallback(path);
1896 }
1897 if invalidated {
1898 self.fsmonitor_validity_changed();
1899 }
1900 }
1901
1902 fn invalidate_fsmonitor_unqualified_path(&mut self, path: &str, ignorecase: bool) -> bool {
1903 if let Some(entry) = self.get_mut(path.as_bytes(), 0) {
1904 entry.set_fsmonitor_valid(false);
1905 return true;
1906 }
1907 if self.has_stage0_descendant_under(path) {
1908 return self.invalidate_fsmonitor_directory_cone(path, ignorecase);
1909 }
1910 false
1911 }
1912
1913 fn invalidate_fsmonitor_directory_cone(&mut self, prefix: &str, ignorecase: bool) -> bool {
1914 if prefix.is_empty() {
1915 self.invalidate_all_fsmonitor_valid();
1916 return true;
1917 }
1918 let prefix_slash = format!("{prefix}/");
1919 let mut any = false;
1920 for entry in &mut self.entries {
1921 if entry.stage() != 0 {
1922 continue;
1923 }
1924 let Some(rel) = std::str::from_utf8(&entry.path).ok() else {
1925 continue;
1926 };
1927 if Self::fsmonitor_path_under_prefix(rel, prefix, &prefix_slash, ignorecase) {
1928 entry.set_fsmonitor_valid(false);
1929 any = true;
1930 }
1931 }
1932 any
1933 }
1934
1935 fn invalidate_fsmonitor_icase_fallback(&mut self, path: &str) -> bool {
1936 let is_dir = path.ends_with('/');
1937 let prefix = path.trim_end_matches('/');
1938 let prefix_slash = format!("{prefix}/");
1939 let mut any = false;
1940 for entry in &mut self.entries {
1941 if entry.stage() != 0 {
1942 continue;
1943 }
1944 let Some(rel) = std::str::from_utf8(&entry.path).ok() else {
1945 continue;
1946 };
1947 let matches = if is_dir {
1948 Self::fsmonitor_path_under_prefix(rel, prefix, &prefix_slash, true)
1949 } else {
1950 rel.eq_ignore_ascii_case(prefix)
1951 };
1952 if matches {
1953 entry.set_fsmonitor_valid(false);
1954 any = true;
1955 }
1956 }
1957 any
1958 }
1959
1960 fn fsmonitor_path_under_prefix(
1961 rel: &str,
1962 prefix: &str,
1963 prefix_slash: &str,
1964 ignorecase: bool,
1965 ) -> bool {
1966 if ignorecase {
1967 rel.eq_ignore_ascii_case(prefix)
1968 || (rel.len() > prefix.len()
1969 && rel.as_bytes().get(prefix.len()) == Some(&b'/')
1970 && rel[..prefix.len()].eq_ignore_ascii_case(prefix))
1971 } else {
1972 rel == prefix || rel.starts_with(prefix_slash)
1973 }
1974 }
1975
1976 fn touch_fsmonitor_after_entry_change(&mut self) {
1978 self.invalidate_fsmonitor_bitmap();
1979 }
1980
1981 #[must_use]
1983 pub fn entries(&self) -> &[IndexEntry] {
1984 &self.entries
1985 }
1986
1987 pub fn entries_mut(&mut self) -> &mut Vec<IndexEntry> {
1989 self.entries_sorted = false;
1990 &mut self.entries
1991 }
1992
1993 pub fn mark_entries_unsorted(&mut self) {
1995 self.entries_sorted = false;
1996 }
1997
1998 pub(crate) fn push_entry_unsorted(&mut self, entry: IndexEntry) {
2000 self.entries.push(entry);
2001 self.entries_sorted = false;
2002 }
2003
2004 #[must_use]
2006 pub fn entries_are_sorted(&self) -> bool {
2007 self.entries
2008 .windows(2)
2009 .all(|pair| (&pair[0].path, pair[0].stage()) <= (&pair[1].path, pair[1].stage()))
2010 }
2011
2012 fn cmp_paths(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
2014 record_path_cmp_for_tests();
2015 a.cmp(b)
2016 }
2017
2018 fn entry_matches(entry: &IndexEntry, path: &[u8], stage: u8) -> bool {
2019 entry.path == path && entry.stage() == stage
2020 }
2021
2022 pub(crate) fn find_entry_pos(&self, path: &[u8], stage: u8) -> Option<usize> {
2024 if self.entries_sorted {
2025 return match self.entries.binary_search_by(|e| {
2026 Self::cmp_paths(e.path.as_slice(), path).then_with(|| e.stage().cmp(&stage))
2027 }) {
2028 Ok(pos) if Self::entry_matches(&self.entries[pos], path, stage) => Some(pos),
2029 _ => None,
2030 };
2031 }
2032 self.entries
2033 .iter()
2034 .position(|e| Self::entry_matches(e, path, stage))
2035 }
2036
2037 fn path_run_range_sorted(&self, path: &[u8]) -> (usize, usize) {
2039 debug_assert!(self.entries_sorted);
2040 let start = self
2041 .entries
2042 .partition_point(|e| Self::cmp_paths(e.path.as_slice(), path).is_lt());
2043 let end = start
2044 + self.entries[start..]
2045 .iter()
2046 .take_while(|e| e.path == path)
2047 .count();
2048 (start, end)
2049 }
2050
2051 #[must_use]
2054 pub fn has_stage0_descendant_under(&self, rel_dir: &str) -> bool {
2055 if rel_dir.is_empty() {
2056 return self.entries.iter().any(|e| e.stage() == 0);
2057 }
2058 if self.entries_sorted {
2059 let (start, end) = self.descendant_run_range_sorted(rel_dir);
2060 return self.entries[start..end].iter().any(|e| e.stage() == 0);
2061 }
2062 let prefix = format!("{rel_dir}/");
2063 self.entries
2064 .iter()
2065 .any(|e| e.stage() == 0 && e.path.starts_with(prefix.as_bytes()))
2066 }
2067
2068 fn descendant_run_range_sorted(&self, path: &str) -> (usize, usize) {
2070 debug_assert!(self.entries_sorted);
2071 let prefix = path.as_bytes();
2072 let plen = prefix.len();
2073 let mut prefix_slash = Vec::with_capacity(plen + 1);
2074 prefix_slash.extend_from_slice(prefix);
2075 prefix_slash.push(b'/');
2076 let start = self.entries.partition_point(|e| {
2077 Self::cmp_paths(e.path.as_slice(), prefix_slash.as_slice()).is_lt()
2078 });
2079 let mut end = start;
2080 while end < self.entries.len() {
2081 let ep = self.entries[end].path.as_slice();
2082 if ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/' {
2083 end += 1;
2084 } else {
2085 break;
2086 }
2087 }
2088 (start, end)
2089 }
2090
2091 fn record_resolve_undo_for_path(&mut self, path: &[u8]) {
2092 for e in &self.entries {
2093 if e.path == path && e.stage() != 0 {
2094 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2095 }
2096 }
2097 }
2098
2099 fn record_resolve_undo_for_run(&mut self, start: usize, end: usize) {
2100 for e in &self.entries[start..end] {
2101 if e.stage() != 0 {
2102 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2103 }
2104 }
2105 }
2106
2107 pub(crate) fn drop_derived_index_extensions(&mut self) {
2109 self.cache_tree = None;
2110 self.cache_tree_root = None;
2111 self.untracked_cache = None;
2112 }
2113
2114 pub(crate) fn invalidate_cache_tree_for_bulk_checkout<'a>(
2116 &mut self,
2117 dir_prefixes: &BTreeSet<String>,
2118 paths: impl IntoIterator<Item = &'a [u8]>,
2119 replacements: &[IndexEntry],
2120 ) {
2121 if self.cache_tree.is_none() {
2122 return;
2123 }
2124 for dir in dir_prefixes {
2125 self.invalidate_cache_tree_for_path(dir.as_bytes());
2126 }
2127 for path in paths {
2128 self.invalidate_cache_tree_for_path(path);
2129 }
2130 for entry in replacements {
2131 self.invalidate_cache_tree_for_path(&entry.path);
2132 }
2133 }
2134
2135 fn invalidate_after_path_removal(&mut self, path: &[u8]) {
2136 if let Ok(p) = std::str::from_utf8(path) {
2137 self.invalidate_untracked_cache_for_path(p);
2138 }
2139 self.invalidate_cache_tree_for_path(path);
2140 }
2141
2142 pub(crate) fn remove_prefixes_paths_and_insert<'a>(
2144 &mut self,
2145 dir_prefixes: &BTreeSet<String>,
2146 paths: impl IntoIterator<Item = &'a [u8]>,
2147 replacements: impl IntoIterator<Item = IndexEntry>,
2148 ) {
2149 let mut replacements: Vec<IndexEntry> = replacements.into_iter().collect();
2150 if !replacements.is_empty() {
2151 replacements
2152 .sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
2153 dedup_sorted_replacement_entries(&mut replacements);
2154 }
2155
2156 let mut paths: Vec<&[u8]> = paths.into_iter().collect();
2157 paths.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2158 paths.dedup_by(|a, b| a == b);
2159
2160 let mut prefix_bytes: Vec<&[u8]> = dir_prefixes.iter().map(|d| d.as_bytes()).collect();
2161 prefix_bytes.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2162 prefix_bytes.dedup_by(|a, b| a == b);
2163
2164 if dir_prefixes.is_empty() && paths.is_empty() {
2165 self.merge_replacements_into_sorted_index(replacements);
2166 return;
2167 }
2168
2169 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2170 let mut kept = Vec::with_capacity(self.entries.len());
2171 let skip_per_path_extension_inval = self.defer_untracked_cache_invalidation;
2172
2173 if self.entries_sorted {
2174 let mut path_idx = 0usize;
2175 for entry in self.entries.drain(..) {
2176 let ep = entry.path.as_slice();
2177 while path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_lt() {
2178 path_idx += 1;
2179 }
2180 let remove = (path_idx < paths.len()
2181 && Self::cmp_paths(paths[path_idx], ep).is_eq())
2182 || Self::entry_under_any_directory_prefix(ep, &prefix_bytes);
2183 if remove {
2184 if entry.stage() != 0 {
2185 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2186 }
2187 if !skip_per_path_extension_inval
2188 && invalidated_paths.last().is_none_or(|p| p.as_slice() != ep)
2189 {
2190 invalidated_paths.push(entry.path.clone());
2191 }
2192 } else {
2193 kept.push(entry);
2194 }
2195 }
2196 let repl_for_inval: Vec<Vec<u8>> = if skip_per_path_extension_inval {
2197 Vec::new()
2198 } else {
2199 replacements
2200 .iter()
2201 .filter(|e| e.stage() == 0)
2202 .map(|e| e.path.clone())
2203 .collect()
2204 };
2205 self.entries = merge_sorted_index_entries(kept, replacements);
2206 self.entries_sorted = true;
2207 if skip_per_path_extension_inval {
2208 self.untracked_cache = None;
2209 self.touch_fsmonitor_after_entry_change();
2210 return;
2211 }
2212 const BULK_INDEX_EXTENSION_INVALIDATE_MIN: usize = 64;
2213 let mut invalidations = dir_prefixes.len();
2214 for path in &invalidated_paths {
2215 if dir_prefixes
2216 .iter()
2217 .all(|d| !Self::entry_under_directory_prefix(path.as_slice(), d.as_bytes()))
2218 {
2219 invalidations += 1;
2220 }
2221 }
2222 invalidations += repl_for_inval.len();
2223 if invalidations >= BULK_INDEX_EXTENSION_INVALIDATE_MIN {
2224 self.drop_derived_index_extensions();
2225 } else {
2226 for dir in dir_prefixes {
2227 self.invalidate_after_path_removal(dir.as_bytes());
2228 }
2229 for path in invalidated_paths {
2230 if dir_prefixes
2231 .iter()
2232 .all(|d| !Self::entry_under_directory_prefix(path.as_slice(), d.as_bytes()))
2233 {
2234 self.invalidate_after_path_removal(&path);
2235 }
2236 }
2237 for path in repl_for_inval {
2238 if let Ok(p) = std::str::from_utf8(&path) {
2239 self.invalidate_untracked_cache_for_path(p);
2240 }
2241 self.invalidate_cache_tree_for_path(&path);
2242 }
2243 }
2244 self.touch_fsmonitor_after_entry_change();
2245 return;
2246 }
2247 if !dir_prefixes.is_empty() {
2248 self.remove_directory_prefixes(dir_prefixes);
2249 }
2250 self.remove_paths_and_insert(paths, replacements);
2251 }
2252
2253 pub(crate) fn remove_directory_prefixes(&mut self, dirs: &BTreeSet<String>) {
2255 if dirs.is_empty() {
2256 return;
2257 }
2258 let mut prefixes: Vec<&[u8]> = dirs.iter().map(|d| d.as_bytes()).collect();
2259 prefixes.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2260 prefixes.dedup_by(|a, b| a == b);
2261
2262 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2263 let mut kept = Vec::with_capacity(self.entries.len());
2264
2265 if self.entries_sorted {
2266 for entry in self.entries.drain(..) {
2267 let ep = entry.path.as_slice();
2268 let remove = Self::entry_under_any_directory_prefix(ep, &prefixes);
2269 if remove {
2270 if entry.stage() != 0 {
2271 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2272 }
2273 if invalidated_paths.last().is_none_or(|p| p.as_slice() != ep) {
2274 invalidated_paths.push(entry.path.clone());
2275 }
2276 } else {
2277 kept.push(entry);
2278 }
2279 }
2280 self.entries = kept;
2281 } else {
2282 for entry in self.entries.drain(..) {
2283 let remove =
2284 Self::entry_under_any_directory_prefix(entry.path.as_slice(), &prefixes);
2285 if remove {
2286 if entry.stage() != 0 {
2287 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2288 }
2289 if invalidated_paths
2290 .last()
2291 .is_none_or(|p| p.as_slice() != entry.path)
2292 {
2293 invalidated_paths.push(entry.path.clone());
2294 }
2295 } else {
2296 kept.push(entry);
2297 }
2298 }
2299 self.entries = kept;
2300 }
2301 if invalidated_paths.is_empty() {
2302 return;
2303 }
2304 for dir in dirs {
2305 self.invalidate_after_path_removal(dir.as_bytes());
2306 }
2307 self.touch_fsmonitor_after_entry_change();
2308 }
2309
2310 fn entry_under_directory_prefix(path: &[u8], prefix: &[u8]) -> bool {
2311 if path.len() < prefix.len() {
2312 return false;
2313 }
2314 if path[..prefix.len()] != *prefix {
2315 return false;
2316 }
2317 path.len() == prefix.len() || path[prefix.len()] == b'/'
2318 }
2319
2320 fn entry_under_any_directory_prefix(path: &[u8], prefixes: &[&[u8]]) -> bool {
2321 prefixes
2322 .iter()
2323 .any(|prefix| Self::entry_under_directory_prefix(path, prefix))
2324 }
2325
2326 pub fn remove_paths<'a>(&mut self, paths: impl IntoIterator<Item = &'a [u8]>) {
2328 use std::collections::BTreeSet;
2329
2330 let mut paths: Vec<&[u8]> = paths.into_iter().collect();
2331 if paths.is_empty() {
2332 return;
2333 }
2334 paths.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2335 paths.dedup_by(|a, b| a == b);
2336
2337 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2338 let mut kept = Vec::with_capacity(self.entries.len());
2339
2340 if self.entries_sorted {
2341 let mut path_idx = 0usize;
2342 for entry in self.entries.drain(..) {
2343 let ep = entry.path.as_slice();
2344 while path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_lt() {
2345 path_idx += 1;
2346 }
2347 let remove = path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_eq();
2348 if remove {
2349 if entry.stage() != 0 {
2350 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2351 }
2352 if invalidated_paths.last().is_none_or(|p| p.as_slice() != ep) {
2353 invalidated_paths.push(entry.path.clone());
2354 }
2355 } else {
2356 kept.push(entry);
2357 }
2358 }
2359 self.entries = kept;
2360 } else {
2361 let remove_set: BTreeSet<&[u8]> = paths.into_iter().collect();
2362 for entry in self.entries.drain(..) {
2363 if remove_set.contains(entry.path.as_slice()) {
2364 if entry.stage() != 0 {
2365 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2366 }
2367 if invalidated_paths
2368 .last()
2369 .is_none_or(|p| p.as_slice() != entry.path.as_slice())
2370 {
2371 invalidated_paths.push(entry.path.clone());
2372 }
2373 } else {
2374 kept.push(entry);
2375 }
2376 }
2377 self.entries = kept;
2378 }
2379
2380 for path in invalidated_paths {
2381 self.invalidate_after_path_removal(&path);
2382 }
2383 self.touch_fsmonitor_after_entry_change();
2384 }
2385
2386 pub(crate) fn replace_stage0_entries_batch(&mut self, replacements: Vec<IndexEntry>) {
2388 self.merge_replacements_into_sorted_index(replacements);
2389 }
2390
2391 pub(crate) fn mark_content_verified(&mut self, path: &[u8], oid: ObjectId) {
2394 self.content_verified.insert(path.to_vec(), oid);
2395 }
2396
2397 pub(crate) fn overwrite_stage0_entries(&mut self, replacements: Vec<IndexEntry>) {
2403 const BULK_INVALIDATE_MIN: usize = 64;
2404 if replacements.is_empty() {
2405 return;
2406 }
2407 let defer_untracked = self.defer_untracked_cache_invalidation;
2408 let drop_caches = !defer_untracked && replacements.len() >= BULK_INVALIDATE_MIN;
2409 let mut unmatched = Vec::new();
2410 for mut entry in replacements {
2411 let pos = if entry.stage() == 0 {
2412 self.find_entry_pos(&entry.path, 0)
2413 } else {
2414 None
2415 };
2416 let Some(pos) = pos else {
2417 unmatched.push(entry);
2418 continue;
2419 };
2420 if !drop_caches {
2421 if !defer_untracked {
2422 if let Ok(p) = std::str::from_utf8(&entry.path) {
2423 self.invalidate_untracked_cache_for_path(p);
2424 }
2425 }
2426 self.invalidate_cache_tree_for_path(&entry.path);
2427 }
2428 entry.base_index_pos = self.entries[pos].base_index_pos;
2429 self.entries[pos] = entry;
2430 }
2431 if drop_caches {
2432 self.cache_tree = None;
2433 self.cache_tree_root = None;
2434 self.untracked_cache = None;
2435 }
2436 self.touch_fsmonitor_after_entry_change();
2437 if !unmatched.is_empty() {
2438 self.merge_replacements_into_sorted_index(unmatched);
2439 }
2440 }
2441
2442 fn merge_sorted_replacements_into_entries(&mut self, mut replacements: Vec<IndexEntry>) {
2444 if replacements.is_empty() {
2445 return;
2446 }
2447 self.entries.reserve(replacements.len());
2448 let mid = self.entries.len();
2449 self.entries.append(&mut replacements);
2450 merge_sorted_index_adjacent_halves(&mut self.entries, mid);
2451 }
2452
2453 fn merge_replacements_into_sorted_index(&mut self, replacements: Vec<IndexEntry>) {
2455 if replacements.is_empty() {
2456 return;
2457 }
2458 if !self.entries_sorted {
2459 for entry in replacements {
2460 self.add_or_replace(entry);
2461 }
2462 return;
2463 }
2464 const BULK_MERGE_MIN: usize = 64;
2465 if self.defer_untracked_cache_invalidation {
2466 for entry in replacements.iter().filter(|e| e.stage() == 0) {
2469 self.invalidate_cache_tree_for_path(&entry.path);
2470 }
2471 self.merge_sorted_replacements_into_entries(replacements);
2472 self.touch_fsmonitor_after_entry_change();
2473 return;
2474 }
2475 let repl_for_inval: Vec<Vec<u8>> = replacements
2476 .iter()
2477 .filter(|e| e.stage() == 0)
2478 .map(|e| e.path.clone())
2479 .collect();
2480 self.merge_sorted_replacements_into_entries(replacements);
2481 if repl_for_inval.len() >= BULK_MERGE_MIN {
2482 self.cache_tree = None;
2483 self.cache_tree_root = None;
2484 self.untracked_cache = None;
2485 self.touch_fsmonitor_after_entry_change();
2486 return;
2487 }
2488 for path in repl_for_inval {
2489 if let Ok(p) = std::str::from_utf8(&path) {
2490 self.invalidate_untracked_cache_for_path(p);
2491 }
2492 self.invalidate_cache_tree_for_path(&path);
2493 }
2494 self.touch_fsmonitor_after_entry_change();
2495 }
2496
2497 pub fn remove_paths_and_insert<'a>(
2502 &mut self,
2503 paths: impl IntoIterator<Item = &'a [u8]>,
2504 replacements: impl IntoIterator<Item = IndexEntry>,
2505 ) {
2506 let mut replacements: Vec<IndexEntry> = replacements.into_iter().collect();
2507 if replacements.is_empty() {
2508 self.remove_paths(paths);
2509 return;
2510 }
2511 replacements.sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
2512 dedup_sorted_replacement_entries(&mut replacements);
2513
2514 let mut paths: Vec<&[u8]> = paths.into_iter().collect();
2515 if paths.is_empty() {
2516 self.merge_replacements_into_sorted_index(replacements);
2517 return;
2518 }
2519 paths.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2520 paths.dedup_by(|a, b| a == b);
2521
2522 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2523 let mut kept = Vec::with_capacity(self.entries.len());
2524
2525 if self.entries_sorted {
2526 let mut path_idx = 0usize;
2527 for entry in self.entries.drain(..) {
2528 let ep = entry.path.as_slice();
2529 while path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_lt() {
2530 path_idx += 1;
2531 }
2532 let remove = path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_eq();
2533 if remove {
2534 if entry.stage() != 0 {
2535 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2536 }
2537 if invalidated_paths.last().is_none_or(|p| p.as_slice() != ep) {
2538 invalidated_paths.push(entry.path.clone());
2539 }
2540 } else {
2541 kept.push(entry);
2542 }
2543 }
2544 let repl_for_inval = replacements
2545 .iter()
2546 .filter(|e| e.stage() == 0)
2547 .map(|e| e.path.clone())
2548 .collect::<Vec<_>>();
2549 self.entries = merge_sorted_index_entries(kept, replacements);
2550 self.entries_sorted = true;
2551 for path in invalidated_paths {
2552 self.invalidate_after_path_removal(&path);
2553 }
2554 for path in repl_for_inval {
2555 if let Ok(p) = std::str::from_utf8(&path) {
2556 self.invalidate_untracked_cache_for_path(p);
2557 }
2558 self.invalidate_cache_tree_for_path(&path);
2559 }
2560 self.touch_fsmonitor_after_entry_change();
2561 return;
2562 }
2563 self.remove_paths(paths);
2564 for entry in replacements {
2565 self.add_or_replace(entry);
2566 }
2567 }
2568
2569 pub fn add_or_replace(&mut self, entry: IndexEntry) {
2571 let path = entry.path.clone();
2572 let stage = entry.stage();
2573 let mut inserted_stage0 = false;
2574 if self.entries_sorted {
2575 let result = self.entries.binary_search_by(|e| {
2576 e.path
2577 .as_slice()
2578 .cmp(path.as_slice())
2579 .then_with(|| e.stage().cmp(&stage))
2580 });
2581 match result {
2582 Ok(pos) => {
2583 let mut e = entry;
2584 e.base_index_pos = self.entries[pos].base_index_pos;
2585 self.entries[pos] = e;
2586 }
2587 Err(pos) => {
2588 self.entries.insert(pos, entry);
2589 inserted_stage0 = stage == 0;
2590 }
2591 }
2592 } else if let Some(pos) = self
2593 .entries
2594 .iter()
2595 .position(|e| e.path == path && e.stage() == stage)
2596 {
2597 let mut e = entry;
2598 e.base_index_pos = self.entries[pos].base_index_pos;
2599 self.entries[pos] = e;
2600 } else {
2601 self.entries.push(entry);
2602 inserted_stage0 = stage == 0;
2603 }
2604 if inserted_stage0 {
2605 if let Ok(p) = std::str::from_utf8(&path) {
2606 self.invalidate_untracked_cache_for_path(p);
2607 }
2608 }
2609 if stage == 0 {
2610 self.invalidate_cache_tree_for_path(&path);
2611 }
2612 self.touch_fsmonitor_after_entry_change();
2613 }
2614
2615 #[must_use]
2617 pub fn has_unmerged_entries(&self) -> bool {
2618 self.entries.iter().any(|e| e.stage() != 0)
2619 }
2620
2621 pub fn stage_file(&mut self, entry: IndexEntry) {
2625 let path = entry.path.clone();
2626 if self.entries_sorted {
2627 let (start, end) = self.path_run_range_sorted(&path);
2628 for i in (start..end).rev() {
2629 if self.entries[i].stage() != 0 {
2630 resolve_undo::record_resolve_undo_for_entry(
2631 &mut self.resolve_undo,
2632 &self.entries[i],
2633 );
2634 self.entries.remove(i);
2635 }
2636 }
2637 } else {
2638 for e in &self.entries {
2639 if e.path == path && e.stage() != 0 {
2640 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2641 }
2642 }
2643 self.entries.retain(|e| e.path != path || e.stage() == 0);
2644 }
2645 self.add_or_replace(entry);
2646 }
2647
2648 pub fn clear_resolve_undo(&mut self) {
2650 self.resolve_undo = None;
2651 }
2652
2653 pub fn take_resolve_undo_record(&mut self, path: &[u8]) -> Option<ResolveUndoRecord> {
2655 let map = self.resolve_undo.as_mut()?;
2656 let ru = map.remove(path)?;
2657 if map.is_empty() {
2658 self.resolve_undo = None;
2659 }
2660 Some(ru)
2661 }
2662
2663 pub fn install_unmerged_from_resolve_undo(&mut self, path: &[u8], record: &ResolveUndoRecord) {
2665 if self.entries_sorted {
2666 let (start, end) = self.path_run_range_sorted(path);
2667 if start < end {
2668 self.entries.drain(start..end);
2669 }
2670 } else {
2671 self.entries.retain(|e| e.path != path);
2672 }
2673 for stage in 1u8..=3u8 {
2674 let i = (stage - 1) as usize;
2675 if record.modes[i] == 0 {
2676 continue;
2677 }
2678 let entry = IndexEntry {
2679 ctime_sec: 0,
2680 ctime_nsec: 0,
2681 mtime_sec: 0,
2682 mtime_nsec: 0,
2683 dev: 0,
2684 ino: 0,
2685 mode: record.modes[i],
2686 uid: 0,
2687 gid: 0,
2688 size: 0,
2689 oid: record.oids[i],
2690 flags: path.len().min(0xFFF) as u16 | ((stage as u16) << 12),
2691 flags_extended: None,
2692 path: path.to_vec(),
2693 base_index_pos: 0,
2694 };
2695 self.add_or_replace(entry);
2696 }
2697 }
2698
2699 pub fn unmerge_path_from_resolve_undo(&mut self, path: &[u8]) -> bool {
2703 let Some(record) = self.take_resolve_undo_record(path) else {
2704 return false;
2705 };
2706 self.install_unmerged_from_resolve_undo(path, &record);
2707 if let Ok(p) = std::str::from_utf8(path) {
2714 self.invalidate_untracked_cache_for_path(p);
2715 }
2716 self.invalidate_cache_tree_for_path(path);
2717 true
2718 }
2719
2720 pub fn remove(&mut self, path: &[u8]) -> bool {
2724 if self.entries_sorted {
2725 let (start, end) = self.path_run_range_sorted(path);
2726 if start < end {
2727 self.record_resolve_undo_for_run(start, end);
2728 self.entries.drain(start..end);
2729 self.invalidate_after_path_removal(path);
2730 self.touch_fsmonitor_after_entry_change();
2731 return true;
2732 }
2733 if !self.entries.iter().any(|e| e.path == path) {
2734 return false;
2735 }
2736 self.entries_sorted = false;
2737 }
2738 self.record_resolve_undo_for_path(path);
2739 let before = self.entries.len();
2740 self.entries.retain(|e| e.path != path);
2741 if self.entries.len() == before {
2742 return false;
2743 }
2744 self.invalidate_after_path_removal(path);
2745 self.touch_fsmonitor_after_entry_change();
2746 true
2747 }
2748
2749 pub fn remove_path_all_stages(&mut self, path: &[u8]) -> bool {
2753 self.remove(path)
2754 }
2755
2756 pub fn invalidate_untracked_cache_for_path(&mut self, path: &str) {
2758 if self.defer_untracked_cache_invalidation {
2759 return;
2760 }
2761 if let Some(uc) = self.untracked_cache.as_mut() {
2762 untracked_cache::invalidate_path(uc, path);
2763 }
2764 }
2765
2766 pub fn remove_descendants_under_path(&mut self, path: &str) {
2772 let prefix = path.as_bytes();
2773 if prefix.is_empty() {
2774 return;
2775 }
2776 let plen = prefix.len();
2777 if self.entries_sorted {
2778 let (start, end) = self.descendant_run_range_sorted(path);
2779 if start < end {
2780 self.record_resolve_undo_for_run(start, end);
2781 self.entries.drain(start..end);
2782 self.invalidate_untracked_cache_for_path(path);
2783 self.invalidate_cache_tree_for_path(path.as_bytes());
2784 return;
2785 }
2786 let any = self.entries.iter().any(|e| {
2787 let ep = e.path.as_slice();
2788 ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/'
2789 });
2790 if !any {
2791 return;
2792 }
2793 self.entries_sorted = false;
2794 }
2795 {
2796 let had_descendant = self.entries.iter().any(|e| {
2797 let ep = e.path.as_slice();
2798 ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/'
2799 });
2800 for e in self.entries.iter() {
2801 let ep = e.path.as_slice();
2802 if ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/' && e.stage() != 0 {
2803 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2804 }
2805 }
2806 self.entries.retain(|e| {
2807 let ep = e.path.as_slice();
2808 if ep.len() <= plen {
2809 return true;
2810 }
2811 if !ep.starts_with(prefix) {
2812 return true;
2813 }
2814 ep[plen] != b'/'
2815 });
2816 if !had_descendant {
2817 return;
2818 }
2819 }
2820 self.invalidate_untracked_cache_for_path(path);
2821 self.invalidate_cache_tree_for_path(path.as_bytes());
2822 }
2823
2824 pub fn set_cache_tree(&mut self, cache_tree: CacheTreeNode) {
2826 self.cache_tree_root = cache_tree.oid.filter(|_| cache_tree.entry_count >= 0);
2827 self.cache_tree = Some(cache_tree);
2828 }
2829
2830 pub fn clear_cache_tree(&mut self) {
2832 self.cache_tree_root = None;
2833 self.cache_tree = None;
2834 }
2835
2836 pub fn invalidate_cache_tree_for_path(&mut self, path: &[u8]) {
2846 let Some(root) = self.cache_tree.as_mut() else {
2847 self.cache_tree_root = None;
2848 return;
2849 };
2850 self.cache_tree_root = None;
2851 Self::do_invalidate_cache_tree_path(root, path);
2852 }
2853
2854 fn do_invalidate_cache_tree_path(node: &mut CacheTreeNode, path: &[u8]) {
2857 node.invalidate();
2858 let slash = path.iter().position(|&b| b == b'/');
2859 match slash {
2860 None => {
2862 if !path.is_empty() {
2863 node.children.retain(|child| child.name != path);
2864 }
2865 }
2866 Some(idx) => {
2868 let (component, rest) = (&path[..idx], &path[idx + 1..]);
2869 if let Some(child) = node
2870 .children
2871 .iter_mut()
2872 .find(|child| child.name == component)
2873 {
2874 Self::do_invalidate_cache_tree_path(child, rest);
2875 }
2876 }
2877 }
2878 }
2879
2880 #[must_use]
2882 pub fn format_cache_tree_dump(&self) -> String {
2883 let Some(root) = self.cache_tree.as_ref() else {
2884 return String::new();
2885 };
2886 let mut out = String::new();
2887 format_cache_tree_node(root, "", &mut out);
2888 out
2889 }
2890
2891 pub fn dump_cache_tree(&self, odb: &Odb) -> Result<String> {
2907 let Some(it_root) = self.cache_tree.as_ref() else {
2908 return Ok(String::new());
2909 };
2910 let ref_root = crate::write_tree::build_cache_tree_from_index(odb, self)?;
2911 let mut out = String::new();
2912 dump_cache_tree_pair(it_root, &ref_root, "", &mut out);
2913 Ok(out)
2914 }
2915
2916 pub fn sort(&mut self) {
2918 self.ensure_canonical_order();
2919 }
2920
2921 pub fn ensure_canonical_order(&mut self) {
2926 if self.entries_sorted && self.entries_are_sorted() {
2927 return;
2928 }
2929 self.sort_entries_canonical();
2930 }
2931
2932 fn sort_entries_canonical(&mut self) {
2933 self.entries
2934 .sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
2935 self.entries_sorted = true;
2936 }
2937
2938 pub fn dedup_paths_keep_last(&mut self) {
2947 let mut seen: std::collections::HashSet<(Vec<u8>, u8)> = std::collections::HashSet::new();
2948 let mut kept: Vec<IndexEntry> = Vec::with_capacity(self.entries.len());
2949 for entry in self.entries.iter().rev() {
2951 if seen.insert((entry.path.clone(), entry.stage())) {
2952 kept.push(entry.clone());
2953 }
2954 }
2955 kept.reverse();
2956 self.entries = kept;
2957 self.entries_sorted = false;
2958 }
2959
2960 #[must_use]
2962 pub fn split_index_base_oid(&self) -> Option<ObjectId> {
2963 self.split_link.as_ref().map(|l| l.base_oid)
2964 }
2965
2966 #[must_use]
2968 pub fn get(&self, path: &[u8], stage: u8) -> Option<&IndexEntry> {
2969 self.find_entry_pos(path, stage)
2970 .map(|pos| &self.entries[pos])
2971 }
2972
2973 pub fn get_mut(&mut self, path: &[u8], stage: u8) -> Option<&mut IndexEntry> {
2975 let pos = self.find_entry_pos(path, stage)?;
2976 Some(&mut self.entries[pos])
2977 }
2978
2979 pub(crate) fn stage0_path_positions<'a>(
2984 &'a self,
2985 ) -> std::collections::HashMap<Cow<'a, str>, usize> {
2986 let stage0_count = self.entries.iter().filter(|e| e.stage() == 0).count();
2987 let mut positions = std::collections::HashMap::with_capacity(stage0_count);
2988 for (pos, entry) in self.entries.iter().enumerate() {
2989 if entry.stage() == 0 {
2990 let key = index_path_outcome_key(&entry.path);
2991 positions.entry(key).or_insert(pos);
2992 }
2993 }
2994 positions
2995 }
2996
2997 pub fn overlay_tree_on_index(
3016 &mut self,
3017 repo: &Repository,
3018 treeish: &str,
3019 prefix: &[u8],
3020 ) -> Result<()> {
3021 let oid = rev_parse::resolve_revision(repo, treeish)?;
3022 let tree_oid = repo.odb().peel_to_tree(oid)?.ok_or_else(|| {
3023 Error::ObjectNotFound(format!("cannot peel {oid} to tree for --with-tree"))
3024 })?;
3025 for e in self.entries.iter_mut() {
3026 if e.stage() != 0 {
3027 e.set_stage(3);
3028 }
3029 }
3030 self.sort();
3031 let has_stage1 = self.entries.iter().any(|e| e.stage() == 1);
3032 let mut appended: Vec<IndexEntry> = Vec::new();
3033 let protect = crate::path_validate::ProtectOptions::from_config(&*repo.config()?);
3034 read_tree_into_overlay(
3035 repo,
3036 &tree_oid,
3037 prefix,
3038 &[],
3039 has_stage1,
3040 protect,
3041 &mut appended,
3042 )?;
3043 for e in appended {
3044 self.add_or_replace(e);
3045 }
3046 if !has_stage1 {
3047 self.sort();
3048 }
3049 let mut last_stage0: Option<&[u8]> = None;
3050 for e in &mut self.entries {
3051 match e.stage() {
3052 0 => {
3053 last_stage0 = Some(e.path.as_slice());
3054 }
3055 1 if last_stage0.is_some_and(|p| p == e.path.as_slice()) => {
3056 e.set_overlay_tree_skip_output(true);
3057 }
3058 _ => {}
3059 }
3060 }
3061 Ok(())
3062 }
3063}
3064
3065#[allow(clippy::too_many_arguments)]
3066fn read_tree_into_overlay(
3067 repo: &Repository,
3068 tree_oid: &ObjectId,
3069 prefix: &[u8],
3070 rel_base: &[u8],
3071 use_replace_path: bool,
3072 protect: crate::path_validate::ProtectOptions,
3073 out: &mut Vec<IndexEntry>,
3074) -> Result<()> {
3075 let obj = repo.odb().read(tree_oid)?;
3076 if obj.kind != ObjectKind::Tree {
3077 return Err(Error::ObjectNotFound(format!(
3078 "object {tree_oid} is not a tree"
3079 )));
3080 }
3081 let entries = parse_tree(&obj.data)?;
3082 for TreeEntry { mode, name, oid } in entries {
3083 let mut path = rel_base.to_vec();
3084 if !path.is_empty() {
3085 path.push(b'/');
3086 }
3087 path.extend_from_slice(&name);
3088 if !prefix_under_or_equal(prefix, &path) {
3089 continue;
3090 }
3091 crate::path_population::validate_tree_walk_entry(rel_base, &name, mode, protect)?;
3092 if mode == MODE_TREE {
3093 read_tree_into_overlay(repo, &oid, prefix, &path, use_replace_path, protect, out)?;
3094 continue;
3095 }
3096 if mode == MODE_GITLINK {
3097 continue;
3098 }
3099 let entry = synthetic_stage1_index_entry(mode, &path, oid);
3100 if use_replace_path {
3101 if let Some(pos) = out.iter().position(|e| e.path == path && e.stage() == 1) {
3102 out[pos] = entry;
3103 } else {
3104 out.push(entry);
3105 }
3106 } else {
3107 out.push(entry);
3108 }
3109 }
3110 Ok(())
3111}
3112
3113fn dedup_sorted_replacement_entries(replacements: &mut Vec<IndexEntry>) {
3115 if replacements.len() <= 1 {
3116 return;
3117 }
3118 let mut out: Vec<IndexEntry> = Vec::with_capacity(replacements.len());
3119 for entry in replacements.drain(..) {
3120 match out.last_mut() {
3121 Some(last) if last.path == entry.path && last.stage() == entry.stage() => {
3122 *last = entry;
3123 }
3124 _ => out.push(entry),
3125 }
3126 }
3127 *replacements = out;
3128}
3129
3130fn index_entry_path_stage_cmp(a: &IndexEntry, b: &IndexEntry) -> std::cmp::Ordering {
3131 a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage()))
3132}
3133
3134fn merge_sorted_index_entries(
3138 mut kept: Vec<IndexEntry>,
3139 mut insert: Vec<IndexEntry>,
3140) -> Vec<IndexEntry> {
3141 if insert.is_empty() {
3142 return kept;
3143 }
3144 if kept.is_empty() {
3145 return insert;
3146 }
3147 kept.reserve(insert.len());
3148 let mid = kept.len();
3149 kept.append(&mut insert);
3150 merge_sorted_index_adjacent_halves(&mut kept, mid);
3151 kept
3152}
3153
3154fn merge_sorted_index_adjacent_halves(v: &mut Vec<IndexEntry>, mid: usize) {
3156 let n = v.len();
3157 if mid == 0 || mid >= n {
3158 return;
3159 }
3160 let right_len = n - mid;
3161 let smaller_is_right = right_len <= mid;
3162 let mut aux = if smaller_is_right {
3163 v.drain(mid..).collect::<Vec<_>>()
3164 } else {
3165 v.drain(..mid).collect::<Vec<_>>()
3166 };
3167 let left_len = if smaller_is_right { v.len() } else { aux.len() };
3168 let pad = aux.len();
3169 v.reserve(pad);
3170 for _ in 0..pad {
3171 v.push(merge_padding_index_entry());
3172 }
3173 let total = v.len();
3174 merge_backward_into_place(v, left_len, total, &mut aux, smaller_is_right);
3175}
3176
3177fn merge_padding_index_entry() -> IndexEntry {
3179 IndexEntry {
3180 ctime_sec: 0,
3181 ctime_nsec: 0,
3182 mtime_sec: 0,
3183 mtime_nsec: 0,
3184 dev: 0,
3185 ino: 0,
3186 mode: 0,
3187 uid: 0,
3188 gid: 0,
3189 size: 0,
3190 oid: ObjectId::zero(),
3191 flags: 0,
3192 flags_extended: None,
3193 path: Vec::new(),
3194 base_index_pos: 0,
3195 }
3196}
3197
3198fn merge_backward_into_place(
3200 v: &mut Vec<IndexEntry>,
3201 left_len: usize,
3202 total: usize,
3203 aux: &mut Vec<IndexEntry>,
3204 smaller_is_right: bool,
3205) {
3206 let pad = aux.len();
3207 let mut li = left_len;
3208 let mut ri = if smaller_is_right {
3209 aux.len()
3210 } else {
3211 total.saturating_sub(pad)
3212 };
3213 let mut k = total;
3214 while li > 0 && ri > 0 {
3215 k -= 1;
3216 let ord = if smaller_is_right {
3217 index_entry_path_stage_cmp(&v[li - 1], &aux[ri - 1])
3218 } else {
3219 index_entry_path_stage_cmp(&aux[li - 1], &v[ri - 1])
3220 };
3221 match ord {
3222 std::cmp::Ordering::Greater => {
3223 if smaller_is_right {
3224 let entry = std::mem::replace(&mut v[li - 1], merge_padding_index_entry());
3225 drop(std::mem::replace(&mut v[k], entry));
3226 li -= 1;
3227 } else {
3228 let entry = std::mem::replace(&mut aux[li - 1], merge_padding_index_entry());
3229 li -= 1;
3230 drop(std::mem::replace(&mut v[k], entry));
3231 }
3232 }
3233 std::cmp::Ordering::Equal => {
3234 let base = if smaller_is_right {
3235 v[li - 1].base_index_pos
3236 } else {
3237 aux[li - 1].base_index_pos
3238 };
3239 let mut entry = if smaller_is_right {
3240 let Some(e) = aux.pop() else {
3241 break;
3242 };
3243 ri -= 1;
3244 e
3245 } else {
3246 ri -= 1;
3247 std::mem::replace(&mut v[ri], merge_padding_index_entry())
3248 };
3249 entry.base_index_pos = base;
3250 if smaller_is_right {
3251 drop(std::mem::replace(
3252 &mut v[li - 1],
3253 merge_padding_index_entry(),
3254 ));
3255 li -= 1;
3256 } else {
3257 drop(std::mem::replace(
3258 &mut aux[li - 1],
3259 merge_padding_index_entry(),
3260 ));
3261 li -= 1;
3262 }
3263 drop(std::mem::replace(&mut v[k], entry));
3264 }
3265 std::cmp::Ordering::Less => {
3266 if smaller_is_right {
3267 let Some(entry) = aux.pop() else {
3268 break;
3269 };
3270 ri -= 1;
3271 drop(std::mem::replace(&mut v[k], entry));
3272 } else {
3273 ri -= 1;
3274 let entry = std::mem::replace(&mut v[ri], merge_padding_index_entry());
3275 drop(std::mem::replace(&mut v[k], entry));
3276 }
3277 }
3278 }
3279 }
3280 while ri > 0 {
3281 k -= 1;
3282 if smaller_is_right {
3283 let Some(entry) = aux.pop() else {
3284 break;
3285 };
3286 ri -= 1;
3287 drop(std::mem::replace(&mut v[k], entry));
3288 } else {
3289 ri -= 1;
3290 let entry = std::mem::replace(&mut v[ri], merge_padding_index_entry());
3291 drop(std::mem::replace(&mut v[k], entry));
3292 }
3293 }
3294 if !smaller_is_right && li > 0 {
3295 for i in 0..li {
3296 let entry = std::mem::replace(&mut aux[i], merge_padding_index_entry());
3297 drop(std::mem::replace(&mut v[i], entry));
3298 }
3299 }
3300 let tail_len = total - k;
3301 if li < k && tail_len > 0 {
3302 for offset in 0..tail_len {
3303 let entry = std::mem::replace(&mut v[k + offset], merge_padding_index_entry());
3304 drop(std::mem::replace(&mut v[li + offset], entry));
3305 }
3306 }
3307 v.truncate(li + tail_len);
3308}
3309
3310fn prefix_under_or_equal(prefix: &[u8], path: &[u8]) -> bool {
3311 if prefix.is_empty() {
3312 return true;
3313 }
3314 if path == prefix {
3315 return true;
3316 }
3317 path.len() > prefix.len() && path.starts_with(prefix) && path[prefix.len()] == b'/'
3318}
3319
3320fn synthetic_stage1_index_entry(mode: u32, path: &[u8], oid: ObjectId) -> IndexEntry {
3321 let path_len = path.len().min(0xFFF) as u16;
3322 let flags = (1u16 << 12) | path_len;
3323 IndexEntry {
3324 ctime_sec: 0,
3325 ctime_nsec: 0,
3326 mtime_sec: 0,
3327 mtime_nsec: 0,
3328 dev: 0,
3329 ino: 0,
3330 mode,
3331 uid: 0,
3332 gid: 0,
3333 size: 0,
3334 oid,
3335 flags,
3336 flags_extended: None,
3337 path: path.to_vec(),
3338 base_index_pos: 0,
3339 }
3340}
3341
3342fn config_truthy(raw: Option<&str>) -> bool {
3343 let Some(val) = raw else {
3344 return false;
3345 };
3346 let lowered = val.trim().to_lowercase();
3347 matches!(lowered.as_str(), "true" | "yes" | "1" | "on")
3348}
3349
3350pub(crate) fn index_skip_hash_for_write(config: Option<&ConfigSet>) -> bool {
3355 let Some(config) = config else {
3356 return false;
3357 };
3358 let many_files = config
3359 .get_bool("feature.manyFiles")
3360 .and_then(|r| r.ok())
3361 .unwrap_or(false);
3362 if many_files {
3363 if let Some(Ok(false)) = config.get_bool("index.skipHash") {
3364 return false;
3365 }
3366 if let Some(Ok(false)) = config.get_bool("index.skiphash") {
3367 return false;
3368 }
3369 return true;
3370 }
3371 for key in ["index.skipHash", "index.skiphash"] {
3372 if let Some(Ok(true)) = config.get_bool(key) {
3373 return true;
3374 }
3375 }
3376 false
3377}
3378
3379fn trim_trailing_slash_bytes(path: &[u8]) -> &[u8] {
3380 path.strip_suffix(b"/").unwrap_or(path)
3381}
3382
3383fn path_under_prefix(path: &[u8], prefix: &[u8]) -> bool {
3384 if path == prefix {
3385 return true;
3386 }
3387 if prefix.is_empty() {
3388 return true;
3389 }
3390 path.len() > prefix.len() && path.starts_with(prefix) && path[prefix.len()] == b'/'
3391}
3392
3393fn directory_in_cone(dir_path: &str, patterns: &[String], cone_mode: bool) -> bool {
3394 let dir = dir_path.trim_end_matches('/');
3403 if dir.is_empty() {
3404 return true;
3405 }
3406 let with_slash = format!("{dir}/");
3407 crate::sparse_checkout::path_matches_sparse_patterns(&with_slash, patterns, cone_mode)
3408}
3409
3410fn collect_directory_prefixes(path: &[u8], out: &mut BTreeSet<Vec<u8>>) {
3411 for (i, &b) in path.iter().enumerate() {
3412 if b == b'/' {
3413 out.insert(path[..i].to_vec());
3414 }
3415 }
3416}
3417
3418fn tree_oid_for_prefix(odb: &Odb, root_tree: &ObjectId, prefix: &[u8]) -> Result<Option<ObjectId>> {
3419 if prefix.is_empty() {
3420 return Ok(Some(*root_tree));
3421 }
3422 let pref_str = String::from_utf8_lossy(prefix);
3423 let components: Vec<&str> = pref_str.split('/').filter(|c| !c.is_empty()).collect();
3424 let mut current = *root_tree;
3425 for comp in components {
3426 let obj = odb.read(¤t)?;
3427 if obj.kind != ObjectKind::Tree {
3428 return Ok(None);
3429 }
3430 let entries = parse_tree(&obj.data)?;
3431 let mut next = None;
3432 for e in entries {
3433 if e.name == comp.as_bytes() {
3434 if e.mode == MODE_TREE {
3435 next = Some(e.oid);
3436 }
3437 break;
3438 }
3439 }
3440 current = match next {
3441 Some(o) => o,
3442 None => return Ok(None),
3443 };
3444 }
3445 Ok(Some(current))
3446}
3447
3448fn collect_sparse_aware_expected_blobs(
3451 odb: &Odb,
3452 tree_oid: &ObjectId,
3453 prefix: &[u8],
3454 patterns: &[String],
3455 cone_mode: bool,
3456 entries: &[IndexEntry],
3457 protect: crate::path_validate::ProtectOptions,
3458) -> Result<Vec<IndexEntry>> {
3459 let mut out = Vec::new();
3460 walk_sparse_aware(
3461 odb, tree_oid, prefix, patterns, cone_mode, entries, protect, &mut out,
3462 )?;
3463 Ok(out)
3464}
3465
3466#[allow(clippy::too_many_arguments)]
3467fn walk_sparse_aware(
3468 odb: &Odb,
3469 tree_oid: &ObjectId,
3470 prefix: &[u8],
3471 patterns: &[String],
3472 cone_mode: bool,
3473 entries: &[IndexEntry],
3474 protect: crate::path_validate::ProtectOptions,
3475 out: &mut Vec<IndexEntry>,
3476) -> Result<()> {
3477 let obj = odb.read(tree_oid)?;
3478 if obj.kind != ObjectKind::Tree {
3479 return Err(Error::IndexError(format!("expected tree at {}", tree_oid)));
3480 }
3481 let tree_entries = parse_tree(&obj.data)?;
3482 for te in tree_entries {
3483 let path = if prefix.is_empty() {
3484 te.name.clone()
3485 } else {
3486 let mut p = prefix.to_vec();
3487 p.push(b'/');
3488 p.extend_from_slice(&te.name);
3489 p
3490 };
3491 crate::path_population::validate_tree_walk_entry(&path, &te.name, te.mode, protect)?;
3492 if te.mode == MODE_TREE {
3493 let path_slash = {
3494 let mut p = path.clone();
3495 p.push(b'/');
3496 p
3497 };
3498 if entries.iter().any(|e| {
3499 e.stage() == 0
3500 && e.is_sparse_directory_placeholder()
3501 && e.path == path_slash
3502 && e.oid == te.oid
3503 }) {
3504 continue;
3505 }
3506 walk_sparse_aware(
3507 odb, &te.oid, &path, patterns, cone_mode, entries, protect, out,
3508 )?;
3509 } else {
3510 let path_len = path.len().min(0xFFF) as u16;
3511 let path_str = String::from_utf8_lossy(&path);
3512 if crate::sparse_checkout::path_matches_sparse_patterns(&path_str, patterns, cone_mode)
3513 {
3514 continue;
3515 }
3516 let mut e = IndexEntry {
3517 ctime_sec: 0,
3518 ctime_nsec: 0,
3519 mtime_sec: 0,
3520 mtime_nsec: 0,
3521 dev: 0,
3522 ino: 0,
3523 mode: te.mode,
3524 uid: 0,
3525 gid: 0,
3526 size: 0,
3527 oid: te.oid,
3528 flags: path_len,
3529 flags_extended: Some(0),
3530 path,
3531 base_index_pos: 0,
3532 };
3533 e.set_skip_worktree(true);
3534 out.push(e);
3535 }
3536 }
3537 Ok(())
3538}
3539
3540fn flatten_tree_blobs(
3541 odb: &Odb,
3542 tree_oid: &ObjectId,
3543 prefix: &[u8],
3544 protect: crate::path_validate::ProtectOptions,
3545) -> Result<Vec<IndexEntry>> {
3546 let obj = odb.read(tree_oid)?;
3547 if obj.kind != ObjectKind::Tree {
3548 return Err(Error::IndexError(format!("expected tree at {}", tree_oid)));
3549 }
3550 let entries = parse_tree(&obj.data)?;
3551 let mut out = Vec::new();
3552 for te in entries {
3553 let path = if prefix.is_empty() {
3554 te.name.clone()
3555 } else {
3556 let mut p = prefix.to_vec();
3557 p.push(b'/');
3558 p.extend_from_slice(&te.name);
3559 p
3560 };
3561 crate::path_population::validate_tree_walk_entry(prefix, &te.name, te.mode, protect)?;
3562 if te.mode == MODE_TREE {
3563 let sub = flatten_tree_blobs(odb, &te.oid, &path, protect)?;
3564 out.extend(sub);
3565 } else {
3566 let path_len = path.len().min(0xFFF) as u16;
3567 let mut e = IndexEntry {
3568 ctime_sec: 0,
3569 ctime_nsec: 0,
3570 mtime_sec: 0,
3571 mtime_nsec: 0,
3572 dev: 0,
3573 ino: 0,
3574 mode: te.mode,
3575 uid: 0,
3576 gid: 0,
3577 size: 0,
3578 oid: te.oid,
3579 flags: path_len,
3580 flags_extended: Some(0),
3581 path,
3582 base_index_pos: 0,
3583 };
3584 e.set_skip_worktree(true);
3585 out.push(e);
3586 }
3587 }
3588 Ok(out)
3589}
3590
3591fn lockfile_pid_enabled(index_path: &Path, config: Option<&ConfigSet>) -> bool {
3592 let git_dir = match index_path.parent() {
3593 Some(dir) => dir,
3594 None => return false,
3595 };
3596
3597 let cfg = config.cloned().unwrap_or_else(|| {
3598 ConfigSet::load(
3599 &crate::environment::Environment::empty(),
3600 Some(git_dir),
3601 true,
3602 )
3603 .unwrap_or_default()
3604 });
3605 cfg.get_bool("core.lockfilepid")
3606 .and_then(|res| res.ok())
3607 .unwrap_or(false)
3608}
3609
3610fn write_lock_pid_file(pid_path: &Path) -> io::Result<()> {
3611 use std::io::Write as _;
3612 let mut file = fs::OpenOptions::new()
3613 .write(true)
3614 .create(true)
3615 .truncate(true)
3616 .open(pid_path)?;
3617 writeln!(file, "pid {}", std::process::id())?;
3618 Ok(())
3619}
3620
3621pub struct IndexLock {
3628 pub(crate) index_path: PathBuf,
3629 pub(crate) lock_path: PathBuf,
3630 pub(crate) pid_path: PathBuf,
3631 pub(crate) file: fs::File,
3632 pub(crate) wrote_pid: bool,
3633 pub(crate) committed: bool,
3634}
3635
3636impl IndexLock {
3637 pub(crate) fn write_index_contents(&mut self, body: &[u8], checksum: &[u8]) -> Result<()> {
3639 self.file.write_all(body).map_err(Error::Io)?;
3640 self.file.write_all(checksum).map_err(Error::Io)?;
3641 drop(std::mem::replace(
3642 &mut self.file,
3643 fs::OpenOptions::new()
3644 .write(true)
3645 .open(&self.lock_path)
3646 .map_err(Error::Io)?,
3647 ));
3648 Ok(())
3649 }
3650
3651 pub(crate) fn commit_replace_index(&mut self) -> Result<()> {
3653 fs::rename(&self.lock_path, &self.index_path).map_err(Error::Io)?;
3654 self.committed = true;
3655 if self.wrote_pid {
3656 let _ = fs::remove_file(&self.pid_path);
3657 }
3658 Ok(())
3659 }
3660
3661 pub fn acquire(index_path: &Path, config: Option<&ConfigSet>) -> Result<Self> {
3669 let lock_path = index_path.with_extension("lock");
3670 let pid_path = pid_path_for_lock(&lock_path);
3671 let lockfile_pid_enabled = lockfile_pid_enabled(index_path, config);
3672
3673 use crate::lockfile::{LockFile, LockWait};
3674 let wait = LockWait::files_ref_lock(config);
3675 let (lock_path, file) =
3676 LockFile::acquire(&lock_path, wait).and_then(LockFile::into_open_file)?;
3677
3678 let mut wrote_pid = false;
3679 if lockfile_pid_enabled {
3680 write_lock_pid_file(&pid_path).map_err(|e| {
3681 let _ = fs::remove_file(&lock_path);
3682 Error::Io(e)
3683 })?;
3684 wrote_pid = true;
3685 }
3686
3687 Ok(Self {
3688 index_path: index_path.to_path_buf(),
3689 lock_path,
3690 pid_path,
3691 file,
3692 wrote_pid,
3693 committed: false,
3694 })
3695 }
3696}
3697
3698impl Drop for IndexLock {
3699 fn drop(&mut self) {
3700 if self.committed {
3701 return;
3702 }
3703 let _ = fs::remove_file(&self.lock_path);
3704 if self.wrote_pid {
3705 let _ = fs::remove_file(&self.pid_path);
3706 }
3707 }
3708}
3709
3710fn hash_index_body(algo: HashAlgo, body: &[u8]) -> Vec<u8> {
3712 algo.checksum_digest(body)
3713}
3714
3715fn write_index_body_and_trailer(
3717 file: &mut fs::File,
3718 algo: HashAlgo,
3719 skip_hash: bool,
3720 body: &[u8],
3721) -> io::Result<()> {
3722 use std::io::BufWriter;
3723 let mut out = BufWriter::with_capacity(256 * 1024, file);
3724 if skip_hash {
3725 out.write_all(body)?;
3726 out.write_all(&vec![0u8; algo.len()])?;
3727 return out.flush();
3728 }
3729 let mut hasher = algo.checksum_hasher();
3730 for chunk in body.chunks(256 * 1024) {
3731 hasher.update(chunk);
3732 out.write_all(chunk)?;
3733 }
3734 out.write_all(&hasher.finalize())?;
3735 out.flush()
3736}
3737
3738impl Index {
3739 fn estimate_body_capacity(&self, write_version: u32, config: Option<&ConfigSet>) -> usize {
3741 use crate::index_eoie_ieot::record_end_of_index_entries;
3742 let hash_len = self.hash_algo.len();
3743 let mut entries_bytes = 0usize;
3744 for entry in &self.entries {
3745 let mut n = 62usize
3746 .saturating_add(hash_len)
3747 .saturating_add(entry.path.len());
3748 if write_version >= 3
3749 && entry
3750 .flags_extended
3751 .map(IndexEntry::disk_flags_extended)
3752 .filter(|fe| *fe != 0)
3753 .is_some()
3754 {
3755 n += 2;
3756 }
3757 if write_version == 4 {
3758 n += 12;
3759 } else {
3760 n = (n + 7) & !7;
3761 }
3762 entries_bytes = entries_bytes.saturating_add(n);
3763 }
3764 let mut extra = 256usize;
3765 if self.cache_tree.is_some() {
3766 extra += 4096;
3767 }
3768 if self.untracked_cache.is_some() {
3769 extra += 4096;
3770 }
3771 if self.fsmonitor_last_update.is_some() {
3772 extra += 512;
3773 }
3774 if record_end_of_index_entries(config) {
3775 extra += 64;
3776 }
3777 12 + entries_bytes + extra
3778 }
3779}
3780
3781fn append_index_extension(
3782 out: &mut Vec<u8>,
3783 eoie_headers: &mut Option<Vec<u8>>,
3784 signature: u32,
3785 payload: &[u8],
3786) {
3787 let size = payload.len() as u32;
3788 out.extend_from_slice(&signature.to_be_bytes());
3789 out.extend_from_slice(&size.to_be_bytes());
3790 if let Some(headers) = eoie_headers {
3791 headers.extend_from_slice(&signature.to_be_bytes());
3792 headers.extend_from_slice(&size.to_be_bytes());
3793 }
3794 out.extend_from_slice(payload);
3795}
3796
3797fn detect_index_hash_algo(data: &[u8]) -> HashAlgo {
3802 let sha256_len = HashAlgo::Sha256.len();
3805 if data.len() > sha256_len + 12 {
3806 let (body, checksum) = data.split_at(data.len() - sha256_len);
3807 if checksum.iter().any(|&b| b != 0) && hash_index_body(HashAlgo::Sha256, body) == checksum {
3808 return HashAlgo::Sha256;
3809 }
3810 }
3811 HashAlgo::Sha1
3812}
3813
3814pub(crate) fn parse_entry(
3818 data: &[u8],
3819 version: u32,
3820 prev_path: &[u8],
3821 hash_len: usize,
3822) -> Result<(IndexEntry, usize)> {
3823 if data.len() < 40 + hash_len + 2 {
3825 return Err(Error::IndexError("entry too short".to_owned()));
3826 }
3827
3828 let mut pos = 0;
3829
3830 macro_rules! read_u32 {
3831 () => {{
3832 let v = u32::from_be_bytes(
3833 data[pos..pos + 4]
3834 .try_into()
3835 .map_err(|_| Error::IndexError("truncated u32".to_owned()))?,
3836 );
3837 pos += 4;
3838 v
3839 }};
3840 }
3841
3842 let ctime_sec = read_u32!();
3843 let ctime_nsec = read_u32!();
3844 let mtime_sec = read_u32!();
3845 let mtime_nsec = read_u32!();
3846 let dev = read_u32!();
3847 let ino = read_u32!();
3848 let mode = read_u32!();
3849 let uid = read_u32!();
3850 let gid = read_u32!();
3851 let size = read_u32!();
3852
3853 let oid = ObjectId::from_bytes(&data[pos..pos + hash_len])?;
3854 pos += hash_len;
3855
3856 let flags = u16::from_be_bytes(
3857 data[pos..pos + 2]
3858 .try_into()
3859 .map_err(|_| Error::IndexError("truncated flags".to_owned()))?,
3860 );
3861 pos += 2;
3862
3863 let flags_extended = if version >= 3 && flags & 0x4000 != 0 {
3864 let fe = u16::from_be_bytes(
3865 data[pos..pos + 2]
3866 .try_into()
3867 .map_err(|_| Error::IndexError("truncated extended flags".to_owned()))?,
3868 );
3869 pos += 2;
3870 Some(fe)
3871 } else {
3872 None
3873 };
3874
3875 let path;
3876 if version == 4 {
3877 let (strip_len, varint_bytes) = read_varint(&data[pos..]);
3879 pos += varint_bytes;
3880 let nul = data[pos..]
3881 .iter()
3882 .position(|&b| b == 0)
3883 .ok_or_else(|| Error::IndexError("v4 entry path missing NUL".to_owned()))?;
3884 let suffix = &data[pos..pos + nul];
3885 pos += nul + 1;
3886 let keep = prev_path.len().saturating_sub(strip_len);
3887 let mut full_path = Vec::with_capacity(keep + suffix.len());
3888 full_path.extend_from_slice(&prev_path[..keep]);
3889 full_path.extend_from_slice(suffix);
3890 path = full_path;
3891 } else {
3892 let nul = data[pos..]
3894 .iter()
3895 .position(|&b| b == 0)
3896 .ok_or_else(|| Error::IndexError("entry path missing NUL terminator".to_owned()))?;
3897 path = data[pos..pos + nul].to_vec();
3898 pos += nul + 1;
3899 let entry_start = 0usize;
3900 let entry_len = pos - entry_start;
3901 let padded = (entry_len + 7) & !7;
3902 let padding = padded.saturating_sub(entry_len);
3903 pos += padding;
3904 }
3905
3906 Ok((
3907 IndexEntry {
3908 ctime_sec,
3909 ctime_nsec,
3910 mtime_sec,
3911 mtime_nsec,
3912 dev,
3913 ino,
3914 mode,
3915 uid,
3916 gid,
3917 size,
3918 oid,
3919 flags,
3920 flags_extended,
3921 path,
3922 base_index_pos: 0,
3923 },
3924 pos,
3925 ))
3926}
3927
3928fn write_varint(out: &mut Vec<u8>, mut value: usize) {
3932 loop {
3933 let mut b = (value & 0x7F) as u8;
3934 value >>= 7;
3935 if value != 0 {
3936 b |= 0x80;
3937 }
3938 out.push(b);
3939 if value == 0 {
3940 break;
3941 }
3942 }
3943}
3944
3945fn read_varint(data: &[u8]) -> (usize, usize) {
3946 let mut value: usize = 0;
3947 let mut shift = 0usize;
3948 let mut pos = 0;
3949 loop {
3950 if pos >= data.len() {
3951 break;
3952 }
3953 let byte = data[pos] as usize;
3954 pos += 1;
3955 value |= (byte & 0x7F) << shift;
3956 if byte & 0x80 == 0 {
3957 break;
3958 }
3959 shift += 7;
3960 if shift > 28 {
3962 break;
3963 }
3964 }
3965 (value, pos)
3966}
3967
3968fn serialize_entry_v4(entry: &IndexEntry, previous_path: &mut Vec<u8>, out: &mut Vec<u8>) {
3969 let write_u32 = |out: &mut Vec<u8>, v: u32| out.extend_from_slice(&v.to_be_bytes());
3970
3971 write_u32(out, entry.ctime_sec);
3972 write_u32(out, entry.ctime_nsec);
3973 write_u32(out, entry.mtime_sec);
3974 write_u32(out, entry.mtime_nsec);
3975 write_u32(out, entry.dev);
3976 write_u32(out, entry.ino);
3977 write_u32(out, entry.mode);
3978 write_u32(out, entry.uid);
3979 write_u32(out, entry.gid);
3980 write_u32(out, entry.size);
3981 out.extend_from_slice(entry.oid.as_bytes());
3982
3983 let mut flags = entry.flags;
3984 let disk_ext = entry
3985 .flags_extended
3986 .map(IndexEntry::disk_flags_extended)
3987 .filter(|fe| *fe != 0);
3988 if disk_ext.is_some() {
3989 flags |= 0x4000;
3990 } else {
3991 flags &= !0x4000;
3992 }
3993 let path_len = entry.path.len().min(0xFFF) as u16;
3994 flags = (flags & 0xF000) | path_len;
3995 out.extend_from_slice(&flags.to_be_bytes());
3996
3997 if let Some(fe) = disk_ext {
3998 out.extend_from_slice(&fe.to_be_bytes());
3999 }
4000
4001 let common = previous_path
4002 .iter()
4003 .zip(entry.path.iter())
4004 .take_while(|(a, b)| a == b)
4005 .count();
4006 let to_remove = previous_path.len().saturating_sub(common);
4007 write_varint(out, to_remove);
4008 out.extend_from_slice(&entry.path[common..]);
4009 out.push(0);
4010
4011 previous_path.clear();
4012 previous_path.extend_from_slice(&entry.path);
4013}
4014
4015fn serialize_entry(entry: &IndexEntry, version: u32, out: &mut Vec<u8>) {
4016 let start = out.len();
4017
4018 let write_u32 = |out: &mut Vec<u8>, v: u32| out.extend_from_slice(&v.to_be_bytes());
4019
4020 write_u32(out, entry.ctime_sec);
4021 write_u32(out, entry.ctime_nsec);
4022 write_u32(out, entry.mtime_sec);
4023 write_u32(out, entry.mtime_nsec);
4024 write_u32(out, entry.dev);
4025 write_u32(out, entry.ino);
4026 write_u32(out, entry.mode);
4027 write_u32(out, entry.uid);
4028 write_u32(out, entry.gid);
4029 write_u32(out, entry.size);
4030 out.extend_from_slice(entry.oid.as_bytes());
4031
4032 let mut flags = entry.flags;
4034 let disk_ext = entry
4035 .flags_extended
4036 .map(IndexEntry::disk_flags_extended)
4037 .filter(|fe| *fe != 0);
4038 if version >= 3 && disk_ext.is_some() {
4039 flags |= 0x4000;
4040 } else {
4041 flags &= !0x4000;
4042 }
4043 let path_len = entry.path.len().min(0xFFF) as u16;
4045 flags = (flags & 0xF000) | path_len;
4046 out.extend_from_slice(&flags.to_be_bytes());
4047
4048 if version >= 3 {
4049 if let Some(fe) = disk_ext {
4050 out.extend_from_slice(&fe.to_be_bytes());
4051 }
4052 }
4053
4054 out.extend_from_slice(&entry.path);
4055 out.push(0);
4056
4057 let entry_len = out.len() - start;
4059 let padded = (entry_len + 7) & !7;
4060 let padding = padded - entry_len;
4061 for _ in 0..padding {
4062 out.push(0);
4063 }
4064}
4065
4066pub fn entry_from_stat(
4079 path: &Path,
4080 rel_path: &[u8],
4081 oid: ObjectId,
4082 mode: u32,
4083) -> Result<IndexEntry> {
4084 let meta = fs::symlink_metadata(path)?;
4085 Ok(entry_from_metadata(&meta, rel_path, oid, mode))
4086}
4087
4088#[must_use]
4092pub fn index_file_mtime(path: &Path) -> Option<(u32, u32)> {
4093 let meta = fs::metadata(path).ok()?;
4094 Some(index_mtime_from_metadata(&meta))
4095}
4096
4097fn index_mtime_from_metadata(meta: &fs::Metadata) -> (u32, u32) {
4098 let ft = filetime::FileTime::from_last_modification_time(meta);
4099 let secs = ft.unix_seconds();
4100 if secs.is_negative() {
4101 return (0, 0);
4102 }
4103 (secs as u32, ft.nanoseconds())
4104}
4105
4106const INDEX_MMAP_MIN_BYTES: u64 = 64 * 1024;
4108
4109enum IndexFileBytes {
4111 Owned(Vec<u8>),
4112 Mapped(memmap2::Mmap),
4113}
4114
4115impl std::ops::Deref for IndexFileBytes {
4116 type Target = [u8];
4117
4118 fn deref(&self) -> &[u8] {
4119 match self {
4120 Self::Owned(bytes) => bytes,
4121 Self::Mapped(map) => map,
4122 }
4123 }
4124}
4125
4126fn read_index_file(path: &Path) -> io::Result<(IndexFileBytes, (u32, u32))> {
4131 use std::io::Read;
4132
4133 let mut file = fs::File::open(path)?;
4134 let meta = file.metadata()?;
4135 let mtime = index_mtime_from_metadata(&meta);
4136 let len = meta.len();
4137 if meta.is_file() && len >= INDEX_MMAP_MIN_BYTES {
4138 if let Ok(len) = usize::try_from(len) {
4139 if let Ok(map) = unsafe { memmap2::MmapOptions::new().len(len).map(&file) } {
4142 return Ok((IndexFileBytes::Mapped(map), mtime));
4143 }
4144 }
4145 }
4146 let mut bytes = Vec::with_capacity(usize::try_from(len).unwrap_or(0));
4147 file.read_to_end(&mut bytes)?;
4148 Ok((IndexFileBytes::Owned(bytes), mtime))
4149}
4150
4151fn max_stage0_entry_mtime(entries: &[IndexEntry]) -> (u32, u32) {
4152 let mut max_sec = 0u32;
4153 let mut max_nsec = 0u32;
4154 for ie in entries {
4155 if ie.stage() != 0 {
4156 continue;
4157 }
4158 if ie.mtime_sec > max_sec || (ie.mtime_sec == max_sec && ie.mtime_nsec > max_nsec) {
4159 max_sec = ie.mtime_sec;
4160 max_nsec = ie.mtime_nsec;
4161 }
4162 }
4163 (max_sec, max_nsec)
4164}
4165
4166#[must_use]
4168pub(crate) fn max_stage0_mtime_pair(a: (u32, u32), b: (u32, u32)) -> (u32, u32) {
4169 if a.0 > b.0 || (a.0 == b.0 && a.1 > b.1) {
4170 a
4171 } else {
4172 b
4173 }
4174}
4175
4176pub(crate) fn stage0_mtime_max(entries: &[IndexEntry]) -> (u32, u32) {
4178 max_stage0_entry_mtime(entries)
4179}
4180
4181pub(crate) fn advance_index_file_mtime_past_entries(
4198 index: &Index,
4199 index_path: &Path,
4200 touched_only_max: Option<(u32, u32)>,
4201) -> Result<()> {
4202 use filetime::FileTime;
4203 let (max_sec, max_nsec) = match touched_only_max {
4204 Some(pair) => pair,
4205 None => max_stage0_entry_mtime(&index.entries),
4206 };
4207 if max_sec == 0 && max_nsec == 0 {
4208 return Ok(());
4209 }
4210 let needs_bump = index_file_mtime(index_path)
4211 .is_none_or(|(sec, nsec)| sec < max_sec || (sec == max_sec && nsec <= max_nsec));
4212 if !needs_bump {
4213 return Ok(());
4214 }
4215 let ft = if max_nsec < 999_999_999 {
4216 FileTime::from_unix_time(i64::from(max_sec), max_nsec + 1)
4217 } else {
4218 FileTime::from_unix_time(i64::from(max_sec) + 1, 0)
4219 };
4220 filetime::set_file_mtime(index_path, ft).map_err(Error::Io)
4221}
4222
4223#[must_use]
4225pub(crate) fn index_path_outcome_key(path: &[u8]) -> Cow<'_, str> {
4226 match std::str::from_utf8(path) {
4227 Ok(s) => Cow::Borrowed(s),
4228 Err(_) => Cow::Owned(String::from_utf8_lossy(path).into_owned()),
4229 }
4230}
4231
4232#[must_use]
4236pub fn worktree_path_from_index_rel(work_tree: &Path, rel: &[u8]) -> PathBuf {
4237 #[cfg(unix)]
4238 {
4239 use std::ffi::OsStr;
4240 use std::os::unix::ffi::OsStrExt;
4241 work_tree.join(OsStr::from_bytes(rel))
4242 }
4243 #[cfg(not(unix))]
4244 {
4245 let rel = std::str::from_utf8(rel)
4246 .map(Cow::Borrowed)
4247 .unwrap_or_else(|_| Cow::Owned(String::from_utf8_lossy(rel).into_owned()));
4248 work_tree.join(rel.as_ref())
4249 }
4250}
4251
4252#[derive(Debug, Clone, Copy)]
4254pub struct IndexStatFields {
4255 ctime_sec: u32,
4256 ctime_nsec: u32,
4257 mtime_sec: u32,
4258 mtime_nsec: u32,
4259 dev: u32,
4260 ino: u32,
4261 uid: u32,
4262 gid: u32,
4263 size: u32,
4264}
4265
4266impl IndexStatFields {
4267 #[cfg(all(target_os = "linux", target_pointer_width = "64"))]
4269 pub(crate) fn from_linux_stat(stat: &libc::stat) -> Self {
4270 Self {
4271 ctime_sec: stat.st_ctime as u32,
4272 ctime_nsec: stat.st_ctime_nsec as u32,
4273 mtime_sec: stat.st_mtime as u32,
4274 mtime_nsec: stat.st_mtime_nsec as u32,
4275 dev: stat.st_dev as u32,
4276 ino: stat.st_ino as u32,
4277 uid: stat.st_uid,
4278 gid: stat.st_gid,
4279 size: munge_index_size(stat.st_size as u64),
4280 }
4281 }
4282
4283 #[must_use]
4285 pub(crate) fn index_size(&self) -> u32 {
4286 self.size
4287 }
4288
4289 #[must_use]
4291 pub fn from_metadata(meta: &fs::Metadata) -> Self {
4292 #[cfg(unix)]
4293 {
4294 use std::os::unix::fs::MetadataExt;
4295 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4296 Self {
4297 ctime_sec,
4298 ctime_nsec,
4299 mtime_sec,
4300 mtime_nsec,
4301 dev: meta.dev() as u32,
4302 ino: meta.ino() as u32,
4303 uid: meta.uid(),
4304 gid: meta.gid(),
4305 size: munge_index_size(meta.size()),
4306 }
4307 }
4308 #[cfg(not(unix))]
4309 {
4310 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4311 Self {
4312 ctime_sec,
4313 ctime_nsec,
4314 mtime_sec,
4315 mtime_nsec,
4316 dev: 0,
4317 ino: 0,
4318 uid: 0,
4319 gid: 0,
4320 size: munge_index_size(meta.len()),
4321 }
4322 }
4323 }
4324
4325 #[must_use]
4327 pub(crate) fn of_entry(ie: &IndexEntry) -> Self {
4328 Self {
4329 ctime_sec: ie.ctime_sec,
4330 ctime_nsec: ie.ctime_nsec,
4331 mtime_sec: ie.mtime_sec,
4332 mtime_nsec: ie.mtime_nsec,
4333 dev: ie.dev,
4334 ino: ie.ino,
4335 uid: ie.uid,
4336 gid: ie.gid,
4337 size: ie.size,
4338 }
4339 }
4340
4341 #[must_use]
4343 pub(crate) fn equivalent_to(&self, other: &Self, policy: StatMatchPolicy) -> bool {
4344 if self.size != other.size || self.mtime_sec != other.mtime_sec {
4345 return false;
4346 }
4347 if policy.use_nanosec && policy.check_stat && self.mtime_nsec != other.mtime_nsec {
4348 return false;
4349 }
4350 if policy.trust_ctime && policy.check_stat {
4351 if self.ctime_sec != other.ctime_sec {
4352 return false;
4353 }
4354 if policy.use_nanosec && self.ctime_nsec != other.ctime_nsec {
4355 return false;
4356 }
4357 }
4358 !policy.check_stat
4359 || (self.dev == other.dev
4360 && self.ino == other.ino
4361 && self.uid == other.uid
4362 && self.gid == other.gid)
4363 }
4364
4365 pub(crate) fn store_in(&self, ie: &mut IndexEntry) {
4367 ie.ctime_sec = self.ctime_sec;
4368 ie.ctime_nsec = self.ctime_nsec;
4369 ie.mtime_sec = self.mtime_sec;
4370 ie.mtime_nsec = self.mtime_nsec;
4371 ie.dev = self.dev;
4372 ie.ino = self.ino;
4373 ie.uid = self.uid;
4374 ie.gid = self.gid;
4375 ie.size = self.size;
4376 }
4377
4378 #[must_use]
4380 pub fn index_entry(self, rel_path: &[u8], oid: ObjectId, mode: u32) -> IndexEntry {
4381 IndexEntry {
4382 ctime_sec: self.ctime_sec,
4383 ctime_nsec: self.ctime_nsec,
4384 mtime_sec: self.mtime_sec,
4385 mtime_nsec: self.mtime_nsec,
4386 dev: self.dev,
4387 ino: self.ino,
4388 mode,
4389 uid: self.uid,
4390 gid: self.gid,
4391 size: self.size,
4392 oid,
4393 flags: rel_path.len().min(0xFFF) as u16,
4394 flags_extended: None,
4395 path: rel_path.to_vec(),
4396 base_index_pos: 0,
4397 }
4398 }
4399}
4400
4401impl From<&fs::Metadata> for IndexStatFields {
4402 fn from(meta: &fs::Metadata) -> Self {
4403 Self::from_metadata(meta)
4404 }
4405}
4406
4407impl IndexStatFields {
4408 #[must_use]
4410 #[cfg_attr(not(windows), allow(dead_code))]
4411 pub(crate) fn mtime_and_size_match_index(&self, ie: &IndexEntry) -> bool {
4412 self.size == ie.size && self.mtime_sec == ie.mtime_sec
4413 }
4414
4415 #[must_use]
4417 pub(crate) fn matches_index_entry(&self, ie: &IndexEntry, policy: StatMatchPolicy) -> bool {
4418 if self.size != ie.size {
4419 return false;
4420 }
4421 if self.mtime_sec != ie.mtime_sec {
4422 return false;
4423 }
4424 if policy.use_nanosec && policy.check_stat && self.mtime_nsec != ie.mtime_nsec {
4425 return false;
4426 }
4427 if policy.trust_ctime && policy.check_stat {
4428 if self.ctime_sec != ie.ctime_sec {
4429 return false;
4430 }
4431 if policy.use_nanosec && self.ctime_nsec != ie.ctime_nsec {
4432 return false;
4433 }
4434 }
4435 if policy.check_stat {
4436 #[cfg(windows)]
4440 {
4441 if self.ino != ie.ino && self.ino != 0 {
4442 return false;
4443 }
4444 if self.dev != ie.dev && self.dev != 0 {
4445 return false;
4446 }
4447 if self.uid != ie.uid && self.uid != 0 {
4448 return false;
4449 }
4450 if self.gid != ie.gid && self.gid != 0 {
4451 return false;
4452 }
4453 }
4454 #[cfg(not(windows))]
4455 if self.ino != ie.ino {
4456 return false;
4457 }
4458 }
4459 true
4460 }
4461}
4462
4463#[must_use]
4468pub fn entry_from_metadata(
4469 meta: &fs::Metadata,
4470 rel_path: &[u8],
4471 oid: ObjectId,
4472 mode: u32,
4473) -> IndexEntry {
4474 #[cfg(unix)]
4475 {
4476 use std::os::unix::fs::MetadataExt;
4477 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4478 IndexEntry {
4479 ctime_sec,
4480 ctime_nsec,
4481 mtime_sec,
4482 mtime_nsec,
4483 dev: meta.dev() as u32,
4484 ino: meta.ino() as u32,
4485 mode,
4486 uid: meta.uid(),
4487 gid: meta.gid(),
4488 size: munge_index_size(meta.size()),
4489 oid,
4490 flags: rel_path.len().min(0xFFF) as u16,
4491 flags_extended: None,
4492 path: rel_path.to_vec(),
4493 base_index_pos: 0,
4494 }
4495 }
4496 #[cfg(not(unix))]
4497 {
4498 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4499 IndexEntry {
4500 ctime_sec,
4501 ctime_nsec,
4502 mtime_sec,
4503 mtime_nsec,
4504 dev: 0,
4505 ino: 0,
4506 mode,
4507 uid: 0,
4508 gid: 0,
4509 size: munge_index_size(meta.len()),
4510 oid,
4511 flags: rel_path.len().min(0xFFF) as u16,
4512 flags_extended: None,
4513 path: rel_path.to_vec(),
4514 base_index_pos: 0,
4515 }
4516 }
4517}
4518
4519#[must_use]
4529pub fn normalize_mode(raw_mode: u32) -> u32 {
4530 const S_IFMT: u32 = 0o170000;
4531 const S_IFLNK: u32 = 0o120000;
4532 const S_IFREG: u32 = 0o100000;
4533
4534 let fmt = raw_mode & S_IFMT;
4535 if fmt == S_IFLNK {
4536 return MODE_SYMLINK;
4537 }
4538 if fmt == S_IFREG {
4539 if raw_mode & 0o111 != 0 {
4541 return MODE_EXECUTABLE;
4542 }
4543 return MODE_REGULAR;
4544 }
4545 MODE_REGULAR
4547}
4548
4549#[cfg(test)]
4550pub(crate) mod index_path_cmp {
4551 use std::cell::Cell;
4552
4553 thread_local! { static PATH_CMP_COUNT: Cell<usize> = const { Cell::new(0) };
4555 }
4556
4557 pub fn reset() {
4558 PATH_CMP_COUNT.with(|c| c.set(0));
4559 }
4560
4561 pub fn take() -> usize {
4562 PATH_CMP_COUNT.with(|c| {
4563 let n = c.get();
4564 c.set(0);
4565 n
4566 })
4567 }
4568
4569 pub fn bump() {
4570 PATH_CMP_COUNT.with(|c| c.set(c.get() + 1));
4571 }
4572}
4573
4574fn record_path_cmp_for_tests() {
4575 #[cfg(test)]
4576 index_path_cmp::bump();
4577}
4578
4579#[cfg(test)]
4580mod tests {
4581 #![allow(clippy::expect_used, clippy::unwrap_used)]
4582
4583 use super::*;
4584 use tempfile::TempDir;
4585
4586 pub(super) mod fsmonitor_rebuild_counter {
4589 use std::cell::Cell;
4590
4591 thread_local! { static COUNT: Cell<usize> = const { Cell::new(0) };
4593 }
4594
4595 pub fn reset() {
4596 COUNT.with(|c| c.set(0));
4597 }
4598
4599 pub fn count() -> usize {
4600 COUNT.with(Cell::get)
4601 }
4602
4603 pub fn note_build() {
4604 COUNT.with(|c| c.set(c.get() + 1));
4605 }
4606 }
4607
4608 fn dummy_oid() -> ObjectId {
4609 ObjectId::from_bytes(&[0u8; 20]).unwrap()
4610 }
4611
4612 fn make_entry(path: &str) -> IndexEntry {
4613 IndexEntry {
4614 ctime_sec: 0,
4615 ctime_nsec: 0,
4616 mtime_sec: 0,
4617 mtime_nsec: 0,
4618 dev: 0,
4619 ino: 0,
4620 mode: MODE_REGULAR,
4621 uid: 0,
4622 gid: 0,
4623 size: 0,
4624 oid: dummy_oid(),
4625 flags: path.len().min(0xFFF) as u16,
4626 flags_extended: None,
4627 path: path.as_bytes().to_vec(),
4628 base_index_pos: 0,
4629 }
4630 }
4631
4632 #[test]
4633 fn round_trip_empty_index() {
4634 let dir = TempDir::new().unwrap();
4635 let path = dir.path().join("index");
4636
4637 let mut idx = Index::new();
4638 idx.write(&path).unwrap();
4639
4640 let loaded = Index::load(&path).unwrap();
4641 assert_eq!(loaded.entries.len(), 0);
4642 }
4643
4644 #[test]
4645 fn read_index_file_maps_only_large_files() {
4646 let dir = TempDir::new().unwrap();
4647 let threshold = usize::try_from(INDEX_MMAP_MIN_BYTES).unwrap();
4648 for (len, mapped) in [(0, false), (threshold - 1, false), (threshold, true)] {
4649 let path = dir.path().join(format!("index-{len}"));
4650 let contents: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
4651 fs::write(&path, &contents).unwrap();
4652 let (bytes, mtime) = read_index_file(&path).unwrap();
4653 assert_eq!(matches!(bytes, IndexFileBytes::Mapped(_)), mapped, "{len}");
4654 assert_eq!(&*bytes, contents.as_slice());
4655 assert_eq!(Some(mtime), index_file_mtime(&path));
4656 }
4657 let missing = read_index_file(&dir.path().join("missing")).err().unwrap();
4658 assert_eq!(missing.kind(), io::ErrorKind::NotFound);
4659 }
4660
4661 #[test]
4662 fn round_trip_with_entries() {
4663 let dir = TempDir::new().unwrap();
4664 let path = dir.path().join("index");
4665
4666 let mut idx = Index::new();
4667 idx.add_or_replace(make_entry("foo.txt"));
4668 idx.add_or_replace(make_entry("bar/baz.txt"));
4669 idx.write(&path).unwrap(); let loaded = Index::load(&path).unwrap();
4672 assert_eq!(loaded.entries.len(), 2);
4673 assert_eq!(loaded.entries[0].path, b"bar/baz.txt");
4674 assert_eq!(loaded.entries[1].path, b"foo.txt");
4675 }
4676
4677 fn overwrite_fixture() -> Index {
4678 let mut idx = Index::new();
4679 for i in 0..200 {
4680 let mut e = make_entry(&format!("d{}/f{i:03}.txt", i % 4));
4681 e.base_index_pos = i + 1;
4682 idx.add_or_replace(e);
4683 }
4684 for stage in 1..=3u16 {
4685 let mut e = make_entry("d0/conflict.txt");
4686 e.flags |= stage << 12;
4687 idx.add_or_replace(e);
4688 }
4689 let leaf =
4690 |name: &str| CacheTreeNode::valid(name.as_bytes().to_vec(), 50, dummy_oid(), vec![]);
4691 idx.cache_tree_root = Some(dummy_oid());
4692 idx.cache_tree = Some(CacheTreeNode::valid(
4693 Vec::new(),
4694 203,
4695 dummy_oid(),
4696 vec![leaf("d0"), leaf("d1"), leaf("d2"), leaf("d3")],
4697 ));
4698 assert!(idx.entries_sorted);
4699 idx
4700 }
4701
4702 fn overwrite_replacements(count: usize) -> Vec<IndexEntry> {
4703 let changed = ObjectId::from_bytes(&[7u8; 20]).unwrap();
4704 let mut out: Vec<IndexEntry> = (0..count)
4705 .map(|i| {
4706 let mut e = make_entry(&format!("d{}/f{:03}.txt", (i * 3) % 4, i * 3));
4707 e.oid = changed;
4708 e.size = 9;
4709 e
4710 })
4711 .collect();
4712 out.sort_by(|a, b| a.path.cmp(&b.path));
4713 out
4714 }
4715
4716 #[test]
4717 fn overwrite_stage0_entries_matches_sorted_merge() {
4718 for count in [3, 64] {
4719 let mut merged = overwrite_fixture();
4720 merged.replace_stage0_entries_batch(overwrite_replacements(count));
4721 let mut overwritten = overwrite_fixture();
4722 overwritten.overwrite_stage0_entries(overwrite_replacements(count));
4723 assert_eq!(overwritten.entries, merged.entries, "count {count}");
4724 assert_eq!(overwritten.cache_tree, merged.cache_tree, "count {count}");
4725 assert_eq!(overwritten.cache_tree_root, merged.cache_tree_root);
4726 assert_eq!(overwritten.entries.len(), 203);
4727 }
4728 }
4729
4730 #[test]
4731 fn overwrite_stage0_entries_keeps_split_positions_and_invalidates_touched_dirs() {
4732 let mut idx = overwrite_fixture();
4733 let target = b"d1/f001.txt".to_vec();
4734 let pos = idx.find_entry_pos(&target, 0).unwrap();
4735 let before = idx.entries[pos].base_index_pos;
4736 let mut e = make_entry("d1/f001.txt");
4737 e.size = 42;
4738 idx.overwrite_stage0_entries(vec![e]);
4739 assert_eq!(idx.entries[pos].size, 42);
4740 assert_eq!(idx.entries[pos].base_index_pos, before);
4741 let tree = idx.cache_tree.as_ref().unwrap();
4742 assert!(!tree.is_valid());
4743 let child = |name: &[u8]| tree.children.iter().find(|c| c.name == name).unwrap();
4744 assert!(!child(b"d1").is_valid());
4745 assert!(child(b"d0").is_valid());
4746 }
4747
4748 #[test]
4749 fn overwrite_stage0_entries_merges_paths_without_a_stage0_row() {
4750 let mut idx = overwrite_fixture();
4751 let mut new_path = make_entry("d2/new.txt");
4752 new_path.size = 1;
4753 idx.overwrite_stage0_entries(vec![make_entry("d0/f000.txt"), new_path]);
4754 assert_eq!(idx.entries.len(), 204);
4755 assert!(idx.get(b"d2/new.txt", 0).is_some());
4756 assert_eq!(
4757 idx.entries
4758 .iter()
4759 .filter(|e| e.path == b"d0/conflict.txt")
4760 .count(),
4761 3
4762 );
4763 assert!(idx.entries.windows(2).all(|w| {
4764 Index::cmp_paths(&w[0].path, &w[1].path)
4765 .then_with(|| w[0].stage().cmp(&w[1].stage()))
4766 .is_lt()
4767 }));
4768 }
4769
4770 #[test]
4771 fn remove_descendants_under_path_drops_nested_only() {
4772 let mut idx = Index::new();
4773 idx.add_or_replace(make_entry("d/e"));
4774 idx.add_or_replace(make_entry("d-other"));
4775 idx.add_or_replace(make_entry("prefix/d"));
4776 idx.remove_descendants_under_path("d");
4777 let paths: Vec<_> = idx.entries.iter().map(|e| e.path.as_slice()).collect();
4778 assert_eq!(paths, vec![b"d-other".as_slice(), b"prefix/d".as_slice()]);
4779 }
4780
4781 #[test]
4782 fn fsmonitor_extension_round_trip_matches_git_layout() {
4783 let fsmn_payload: [u8; 41] = [
4785 0, 0, 0, 2, b'b', b'u', b'i', b'l', b't', b'i', b'n', b':', b'f', b'a', b'k', b'e', 0,
4786 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4787 ];
4788 let (token, dirty) = parse_fsmonitor_extension(&fsmn_payload).unwrap();
4789 assert_eq!(token, "builtin:fake");
4790 assert_eq!(dirty.bit_size, 0);
4791
4792 let dir = TempDir::new().unwrap();
4793 let path = dir.path().join("index");
4794 let mut idx = Index::new();
4795 idx.add_or_replace(make_entry("a"));
4796 if let Some(e) = idx.get_mut(b"a", 0) {
4797 e.set_fsmonitor_valid(true);
4798 }
4799 idx.fsmonitor_last_update = Some(token);
4800 idx.fsmonitor_dirty = Some(dirty);
4801 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
4802 idx.write(&path).unwrap(); let loaded = Index::load(&path).unwrap();
4805 assert_eq!(
4806 loaded.fsmonitor_last_update.as_deref(),
4807 Some("builtin:fake")
4808 );
4809 assert_eq!(loaded.fsmonitor_dirty.as_ref().map(|b| b.bit_size), Some(0));
4810 assert_eq!(
4811 loaded.fsmonitor_raw_extension.as_deref(),
4812 Some(fsmn_payload.as_slice())
4813 );
4814
4815 let mut body = Vec::new();
4816 loaded.serialize_into(&mut body).unwrap();
4817 assert!(
4818 body.windows(4).any(|w| w == b"FSMN"),
4819 "serialized index must include FSMN"
4820 );
4821 let off = body.windows(4).position(|w| w == b"FSMN").unwrap();
4822 let ext_sz = u32::from_be_bytes(body[off + 4..off + 8].try_into().unwrap()) as usize;
4823 let written = &body[off + 8..off + 8 + ext_sz];
4824 assert_eq!(written, fsmn_payload);
4825 }
4826
4827 #[test]
4828 fn fsmonitor_extension_written_from_token_only_state() {
4829 let dir = TempDir::new().unwrap();
4830 let path = dir.path().join("index");
4831 let mut idx = Index::new();
4832 idx.add_or_replace(make_entry("a"));
4833 idx.set_fsmonitor_last_update(Some("builtin:fake".to_owned()));
4834 idx.write(&path).unwrap(); let data = fs::read(&path).unwrap();
4837 assert!(
4838 data.windows(4).any(|w| w == b"FSMN"),
4839 "token-only enable must emit a full FSMN extension"
4840 );
4841 let loaded = Index::load(&path).unwrap();
4842 assert_eq!(
4843 loaded.fsmonitor_last_update.as_deref(),
4844 Some("builtin:fake")
4845 );
4846 }
4847
4848 #[test]
4849 fn fsmonitor_raw_not_reused_after_token_change() {
4850 let fsmn_payload: [u8; 41] = [
4851 0, 0, 0, 2, b'b', b'u', b'i', b'l', b't', b'i', b'n', b':', b'f', b'a', b'k', b'e', 0,
4852 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4853 ];
4854 let mut idx = Index::new();
4855 idx.add_or_replace(make_entry("a"));
4856 idx.fsmonitor_last_update = Some("builtin:fake".to_owned());
4857 idx.fsmonitor_dirty = Some(parse_fsmonitor_extension(&fsmn_payload).unwrap().1);
4858 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
4859
4860 idx.set_fsmonitor_last_update(Some("last_update_token".to_owned()));
4861 let mut body = Vec::new();
4862 idx.serialize_into(&mut body).unwrap();
4863 let off = body.windows(4).position(|w| w == b"FSMN").unwrap();
4864 let ext_sz = u32::from_be_bytes(body[off + 4..off + 8].try_into().unwrap()) as usize;
4865 let written = &body[off + 8..off + 8 + ext_sz];
4866 let (token, _) = parse_fsmonitor_extension(written).unwrap();
4867 assert_eq!(token, "last_update_token");
4868 assert_ne!(written, fsmn_payload);
4869 }
4870
4871 #[test]
4872 fn fsmonitor_raw_not_reused_after_validity_change() {
4873 let fsmn_payload: [u8; 41] = [
4874 0, 0, 0, 2, b'b', b'u', b'i', b'l', b't', b'i', b'n', b':', b'f', b'a', b'k', b'e', 0,
4875 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4876 ];
4877 let mut idx = Index::new();
4878 idx.add_or_replace(make_entry("a"));
4879 idx.fsmonitor_last_update = Some("builtin:fake".to_owned());
4880 idx.fsmonitor_dirty = Some(parse_fsmonitor_extension(&fsmn_payload).unwrap().1);
4881 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
4882 if let Some(e) = idx.get_mut(b"a", 0) {
4883 e.set_fsmonitor_valid(false);
4884 }
4885 idx.fsmonitor_validity_changed();
4886
4887 let mut body = Vec::new();
4888 idx.serialize_into(&mut body).unwrap();
4889 let off = body.windows(4).position(|w| w == b"FSMN").unwrap();
4890 let ext_sz = u32::from_be_bytes(body[off + 4..off + 8].try_into().unwrap()) as usize;
4891 let written = &body[off + 8..off + 8 + ext_sz];
4892 assert_ne!(written, fsmn_payload);
4893 let (_, dirty) = parse_fsmonitor_extension(written).unwrap();
4894 assert!(dirty.bit_size >= 1);
4895 }
4896
4897 fn make_staged_entry(path: &str, stage: u8) -> IndexEntry {
4898 let mut e = make_entry(path);
4899 e.set_stage(stage);
4900 e
4901 }
4902
4903 struct LinearIndex {
4905 entries: Vec<IndexEntry>,
4906 resolve_undo: Option<BTreeMap<Vec<u8>, ResolveUndoRecord>>,
4907 }
4908
4909 impl LinearIndex {
4910 fn from_index(idx: &Index) -> Self {
4911 Self {
4912 entries: idx.entries.clone(),
4913 resolve_undo: idx.resolve_undo.clone(),
4914 }
4915 }
4916
4917 fn assert_matches(&self, idx: &Index) {
4918 let mut actual = idx.entries.clone();
4919 actual.sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
4920 let mut expect = self.entries.clone();
4921 expect.sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
4922 assert_eq!(actual, expect);
4923 assert_eq!(idx.resolve_undo, self.resolve_undo);
4924 }
4925
4926 fn add_or_replace(&mut self, entry: IndexEntry) {
4927 if let Some(pos) = self
4928 .entries
4929 .iter()
4930 .position(|e| e.path == entry.path && e.stage() == entry.stage())
4931 {
4932 self.entries[pos] = entry;
4933 } else {
4934 self.entries.push(entry);
4935 }
4936 self.entries
4937 .sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
4938 }
4939
4940 fn remove(&mut self, path: &[u8]) -> bool {
4941 let mut removed_any = false;
4942 for e in &self.entries {
4943 if e.path == path {
4944 if e.stage() != 0 {
4945 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
4946 }
4947 removed_any = true;
4948 }
4949 }
4950 if !removed_any {
4951 return false;
4952 }
4953 self.entries.retain(|e| e.path != path);
4954 removed_any
4955 }
4956
4957 fn stage_file(&mut self, entry: IndexEntry) {
4958 let path = entry.path.clone();
4959 for e in &self.entries {
4960 if e.path == path && e.stage() != 0 {
4961 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
4962 }
4963 }
4964 self.entries.retain(|e| e.path != path || e.stage() == 0);
4965 self.add_or_replace(entry);
4966 }
4967
4968 fn remove_descendants_under_path(&mut self, path: &str) {
4969 let prefix = path.as_bytes();
4970 if prefix.is_empty() {
4971 return;
4972 }
4973 let plen = prefix.len();
4974 for e in self.entries.iter() {
4975 let ep = e.path.as_slice();
4976 if ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/' && e.stage() != 0 {
4977 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
4978 }
4979 }
4980 self.entries.retain(|e| {
4981 let ep = e.path.as_slice();
4982 if ep.len() <= plen {
4983 return true;
4984 }
4985 if !ep.starts_with(prefix) {
4986 return true;
4987 }
4988 ep[plen] != b'/'
4989 });
4990 }
4991 }
4992
4993 #[test]
4994 fn sorted_ops_match_linear_oracle() {
4995 let paths: Vec<Vec<u8>> = [
4996 "a", "a-b", "a/b", "a/b/c", "aa", "b", "dir/x", "dir/x/y", "dir-xy",
4997 ]
4998 .into_iter()
4999 .map(|p| p.as_bytes().to_vec())
5000 .collect();
5001
5002 let mut idx = Index::new();
5003 let mut oracle = LinearIndex::from_index(&idx);
5004 let mut rng_state: u64 = 376;
5005
5006 fn next_u64(state: &mut u64) -> u64 {
5007 *state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
5008 *state
5009 }
5010
5011 for step in 0..400 {
5012 let op = next_u64(&mut rng_state) % 4;
5013 match op {
5014 0 => {
5015 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
5016 let stage = (next_u64(&mut rng_state) % 4) as u8;
5017 let mut e = make_staged_entry(std::str::from_utf8(p).unwrap(), stage);
5018 e.oid = ObjectId::from_bytes(&[(step % 256) as u8; 20]).unwrap();
5019 idx.add_or_replace(e.clone());
5020 oracle.add_or_replace(e);
5021 }
5022 1 => {
5023 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
5024 idx.remove(p);
5025 oracle.remove(p);
5026 }
5027 2 => {
5028 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
5029 let e = make_entry(std::str::from_utf8(p).unwrap());
5030 idx.stage_file(e.clone());
5031 oracle.stage_file(e);
5032 }
5033 _ => {
5034 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
5035 let ps = std::str::from_utf8(p).unwrap();
5036 idx.remove_descendants_under_path(ps);
5037 oracle.remove_descendants_under_path(ps);
5038 }
5039 }
5040 oracle.assert_matches(&idx);
5041 }
5042 }
5043
5044 #[test]
5045 fn stale_sorted_flag_miss_until_resorted() {
5046 let mut idx = Index::new();
5047 idx.entries.push(make_entry("z"));
5048 idx.entries.push(make_entry("a"));
5049 idx.entries.push(make_entry("b"));
5050 idx.entries_sorted = true;
5051 assert!(!idx.entries_are_sorted());
5052 assert!(idx.get(b"z", 0).is_none());
5053 idx.sort();
5054 assert!(idx.get(b"z", 0).is_some());
5055 assert!(idx.remove(b"z"));
5056 assert!(idx.get(b"z", 0).is_none());
5057 }
5058
5059 #[test]
5060 fn write_restores_canonical_order_when_sorted_flag_is_stale() {
5061 let mut idx = Index::new();
5062 idx.entries.push(make_entry("zzz.txt"));
5063 idx.entries.push(make_entry("aaa.txt"));
5064 idx.entries_sorted = true;
5065 assert!(!idx.entries_are_sorted());
5066 let dir = tempfile::tempdir().expect("tempdir");
5067 let path = dir.path().join("index");
5068 idx.write(&path).expect("write index");
5069 let parsed = Index::load(&path).expect("load index");
5070 assert!(parsed.entries_are_sorted());
5071 assert_eq!(parsed.entries[0].path, b"aaa.txt".as_slice());
5072 }
5073
5074 #[test]
5075 fn find_entry_pos_sorted_miss_is_constant() {
5076 const N: usize = 100_000;
5077 const M: usize = 1_000;
5078 let mut idx = Index::new();
5079 for i in 0..N {
5080 idx.add_or_replace(make_entry(&format!("file/{i:06}.txt")));
5081 }
5082 assert!(idx.entries_sorted);
5083 let misses: Vec<Vec<u8>> = (0..M)
5084 .map(|i| format!("missing/{i:06}.txt").into_bytes())
5085 .collect();
5086 index_path_cmp::reset();
5087 for path in &misses {
5088 assert!(idx.get(path, 0).is_none());
5089 }
5090 let cmps = index_path_cmp::take();
5091 let log_n = (N as f64).log2().ceil() as usize;
5092 let bound = 100 * M * log_n;
5093 assert!(
5094 cmps < bound,
5095 "sorted miss path comparisons {cmps} should stay under {bound} (N={N}, M={M})"
5096 );
5097 }
5098
5099 #[test]
5100 fn unsorted_remove_drops_every_matching_stage_globally() {
5101 let mut idx = Index::new();
5102 idx.mark_entries_unsorted();
5103 idx.entries.push(make_staged_entry("a", 1));
5104 idx.entries.push(make_entry("z"));
5105 idx.entries.push(make_staged_entry("a", 2));
5106 assert!(idx.remove(b"a"));
5107 assert!(idx.get(b"a", 1).is_none());
5108 assert!(idx.get(b"a", 2).is_none());
5109 assert!(idx.get(b"z", 0).is_some());
5110 }
5111
5112 #[test]
5113 fn unsorted_remove_descendants_drops_noncontiguous_matches() {
5114 let mut idx = Index::new();
5115 idx.mark_entries_unsorted();
5116 idx.entries.push(make_entry("a/x"));
5117 idx.entries.push(make_entry("z"));
5118 idx.entries.push(make_entry("a/y"));
5119 idx.remove_descendants_under_path("a");
5120 let paths: Vec<_> = idx.entries.iter().map(|e| e.path.as_slice()).collect();
5121 assert_eq!(paths, vec![b"z".as_slice()]);
5122 }
5123
5124 #[test]
5125 fn sorted_ops_match_linear_oracle_unsorted_noncontiguous() {
5126 let mut idx = Index::new();
5127 idx.mark_entries_unsorted();
5128 idx.entries.push(make_entry("a/x"));
5129 idx.entries.push(make_entry("z"));
5130 idx.entries.push(make_staged_entry("a", 1));
5131 idx.remove(b"a");
5132 idx.entries.push(make_entry("a/y"));
5133 idx.sort();
5134 let mut oracle = LinearIndex::from_index(&idx);
5135 oracle.entries = idx.entries.clone();
5136 oracle.assert_matches(&idx);
5137 idx.remove_descendants_under_path("a");
5138 oracle.remove_descendants_under_path("a");
5139 oracle.assert_matches(&idx);
5140 }
5141
5142 #[test]
5143 fn merge_replacements_preserves_base_index_pos() {
5144 let mut idx = Index::new();
5145 let mut kept = make_entry("path/a.txt");
5146 kept.base_index_pos = 42;
5147 idx.add_or_replace(kept);
5148 let mut replacement = make_entry("path/a.txt");
5149 replacement.oid = dummy_oid();
5150 idx.remove_paths_and_insert([], [replacement]);
5151 assert_eq!(idx.entries[0].base_index_pos, 42);
5152 }
5153
5154 #[test]
5155 fn remove_paths_and_insert_replaces_equal_path_without_duplicates() {
5156 let mut idx = Index::new();
5157 idx.add_or_replace(make_entry("path/a.txt"));
5158 idx.add_or_replace(make_entry("path/b.txt"));
5159 let mut replacement = make_entry("path/a.txt");
5160 replacement.oid = dummy_oid();
5161 idx.remove_paths_and_insert(["ghost.txt".as_bytes()], [replacement.clone()]);
5162 assert_eq!(idx.entries.len(), 2);
5163 assert_eq!(idx.entries[0].path, b"path/a.txt");
5164 assert_eq!(idx.entries[0].oid, replacement.oid);
5165 assert_eq!(idx.entries[1].path, b"path/b.txt");
5166 }
5167
5168 #[test]
5169 fn remove_directory_prefixes_drops_nested_paths() {
5170 let mut idx = Index::new();
5171 idx.add_or_replace(make_entry("d005/f0.txt"));
5172 idx.add_or_replace(make_entry("d005/f1.txt"));
5173 idx.add_or_replace(make_entry("d006/f0.txt"));
5174 idx.add_or_replace(make_entry("keep.txt"));
5175 idx.remove_directory_prefixes(&BTreeSet::from(["d005".to_owned()]));
5176 assert!(idx.get(b"d005/f0.txt", 0).is_none());
5177 assert!(idx.get(b"d005/f1.txt", 0).is_none());
5178 assert!(idx.get(b"d006/f0.txt", 0).is_some());
5179 assert!(idx.get(b"keep.txt", 0).is_some());
5180 }
5181
5182 #[test]
5183 fn remove_prefixes_paths_and_insert_combines_prefix_exact_and_replacements() {
5184 let mut idx = Index::new();
5185 idx.add_or_replace(make_entry("d005/f0.txt"));
5186 idx.add_or_replace(make_entry("d005/f1.txt"));
5187 idx.add_or_replace(make_entry("d006/f0.txt"));
5188 idx.add_or_replace(make_entry("solo.txt"));
5189 let mut replacement = make_entry("solo.txt");
5190 replacement.oid = dummy_oid();
5191 idx.remove_prefixes_paths_and_insert(
5192 &BTreeSet::from(["d005".to_owned()]),
5193 [b"d006/f0.txt".as_slice()],
5194 [replacement.clone()],
5195 );
5196 assert!(idx.get(b"d005/f0.txt", 0).is_none());
5197 assert!(idx.get(b"d005/f1.txt", 0).is_none());
5198 assert!(idx.get(b"d006/f0.txt", 0).is_none());
5199 assert_eq!(idx.get(b"solo.txt", 0).unwrap().oid, replacement.oid);
5200 }
5201
5202 #[test]
5203 fn remove_directory_prefixes_does_not_skip_lexically_later_prefix_children() {
5204 let mut idx = Index::new();
5205 idx.add_or_replace(make_entry("d005/f0.txt"));
5206 idx.add_or_replace(make_entry("d005/f1.txt"));
5207 idx.remove_directory_prefixes(&BTreeSet::from(["d005".to_owned(), "d0050".to_owned()]));
5208 assert!(idx.get(b"d005/f0.txt", 0).is_none());
5209 assert!(idx.get(b"d005/f1.txt", 0).is_none());
5210 }
5211
5212 #[test]
5213 fn remove_is_sublinear() {
5214 const N: usize = 100_000;
5215 const M: usize = 1_000;
5216 let mut idx = Index::new();
5217 for i in 0..N {
5218 idx.add_or_replace(make_entry(&format!("file/{i:06}.txt")));
5219 }
5220 let paths: Vec<Vec<u8>> = (0..M)
5221 .map(|i| format!("file/{i:06}.txt").into_bytes())
5222 .collect();
5223 index_path_cmp::reset();
5224 idx.remove_paths(paths.iter().map(|p| p.as_slice()));
5225 let cmps = index_path_cmp::take();
5226 let log_n = (N as f64).log2().ceil() as usize;
5227 let bound = 100 * M * log_n;
5228 assert!(
5229 cmps < bound,
5230 "path comparisons {cmps} should stay under {bound} (N={N}, M={M})"
5231 );
5232 assert_eq!(idx.entries.len(), N - M);
5233 }
5234
5235 #[test]
5236 fn fsmonitor_rebuild_once_per_batch() {
5237 let fsmn_payload: [u8; 41] = [
5238 0, 0, 0, 2, b'b', b'u', b'i', b'l', b't', b'i', b'n', b':', b'f', b'a', b'k', b'e', 0,
5239 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5240 ];
5241 let (token, dirty) = parse_fsmonitor_extension(&fsmn_payload).unwrap();
5242 let dir = TempDir::new().unwrap();
5243 let path = dir.path().join("index");
5244
5245 let mut idx = Index::new();
5246 for i in 0..1000 {
5247 idx.add_or_replace(make_entry(&format!("path-{i:04}.txt")));
5248 }
5249 idx.sort();
5250 for e in &mut idx.entries {
5251 e.set_fsmonitor_valid(true);
5252 }
5253 idx.fsmonitor_last_update = Some(token);
5254 idx.fsmonitor_dirty = Some(dirty);
5255 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
5256
5257 fsmonitor_rebuild_counter::reset();
5258 for i in 0..1000 {
5259 idx.add_or_replace(make_entry(&format!("mut-{i:04}.txt")));
5260 }
5261 assert_eq!(
5262 fsmonitor_rebuild_counter::count(),
5263 0,
5264 "entry mutations must not scan entries for the EWAH"
5265 );
5266 idx.write(&path).unwrap(); assert_eq!(
5268 fsmonitor_rebuild_counter::count(),
5269 1,
5270 "write must build the EWAH once for the batch"
5271 );
5272 }
5273
5274 #[test]
5275 fn requested_v4_writes_v4_on_disk() {
5276 let dir = TempDir::new().unwrap();
5277 let path = dir.path().join("index");
5278
5279 let mut idx = Index {
5280 version: 4,
5281 ..Index::default()
5282 };
5283 idx.add_or_replace(make_entry("one"));
5284 idx.add_or_replace(make_entry("two/one"));
5285 idx.write(&path).unwrap(); let data = fs::read(&path).unwrap();
5288 assert_eq!(&data[4..8], &4u32.to_be_bytes());
5289
5290 let loaded = Index::load(&path).unwrap();
5291 assert_eq!(loaded.version, 4);
5292 assert_eq!(loaded.entries[0].path, b"one");
5293 assert_eq!(loaded.entries[1].path, b"two/one");
5294 }
5295
5296 #[test]
5297 fn stat_match_policy_from_repo_config() {
5298 let dir = TempDir::new().unwrap();
5299 let git_dir = dir.path().join(".git");
5300 fs::create_dir_all(&git_dir).unwrap();
5301 fs::write(
5302 git_dir.join("config"),
5303 "[core]\n\ttrustctime = false\n\tcheckstat = minimal\n\tusenanosec = true\n",
5304 )
5305 .unwrap();
5306 let cfg = crate::config::ConfigSet::load(
5307 &crate::environment::Environment::empty(),
5308 Some(&git_dir),
5309 true,
5310 )
5311 .unwrap();
5312 let policy = StatMatchPolicy::from_config(Some(&cfg));
5313 assert!(!policy.trust_ctime);
5314 assert!(!policy.check_stat);
5315 assert!(policy.use_nanosec);
5316 }
5317
5318 #[test]
5319 fn index_entry_extended_flags_and_placeholders() {
5320 let mut e = make_entry("p");
5321 e.set_intent_to_add(true);
5322 assert!(e.intent_to_add());
5323 e.set_assume_unchanged(true);
5324 assert!(e.assume_unchanged());
5325 e.set_skip_worktree(true);
5326 assert!(e.skip_worktree());
5327 e.mode = MODE_TREE;
5328 e.set_skip_worktree(true);
5329 assert!(e.is_sparse_directory_placeholder());
5330 e.set_overlay_tree_skip_output(true);
5331 assert!(e.overlay_tree_skip_output());
5332 }
5333
5334 #[test]
5335 fn cache_tree_formatting_and_invalidation_helpers() {
5336 let mut idx = Index::new();
5337 idx.add_or_replace(make_entry("a/b"));
5338 let child = CacheTreeNode::valid(b"a".to_vec(), 1, dummy_oid(), vec![]);
5339 let root = CacheTreeNode::valid(b"".to_vec(), 1, dummy_oid(), vec![child]);
5340 idx.set_cache_tree(root);
5341 let dump = idx.format_cache_tree_dump();
5342 assert!(dump.contains("a/") || dump.contains("invalid"));
5343 idx.invalidate_cache_tree_for_path(b"a/b");
5344 idx.clear_cache_tree();
5345 assert!(idx.cache_tree.is_none());
5346 }
5347
5348 #[test]
5349 fn dedup_paths_and_stage0_descendant_queries() {
5350 let mut idx = Index::new();
5351 idx.add_or_replace(make_entry("dir/x"));
5352 idx.add_or_replace(make_entry("dir/x"));
5353 idx.dedup_paths_keep_last();
5354 assert_eq!(idx.entries.len(), 1);
5355 assert!(idx.has_stage0_descendant_under("dir"));
5356 assert!(!idx.has_stage0_descendant_under("missing"));
5357 }
5358
5359 #[test]
5360 fn load_missing_index_is_empty() {
5361 let dir = TempDir::new().unwrap();
5362 let path = dir.path().join("no-index");
5363 let idx = Index::load(&path).unwrap();
5364 assert!(idx.entries.is_empty());
5365 }
5366
5367 #[test]
5368 fn munge_and_stat_equivalence_helpers() {
5369 assert_eq!(munge_index_size(0x1_0000_0000), 0x8000_0000);
5370 let a = make_entry("x");
5371 let b = make_entry("x");
5372 assert!(index_entries_stat_equivalent(
5373 &a,
5374 &b,
5375 StatMatchPolicy::default()
5376 ));
5377 }
5378
5379 #[test]
5380 fn new_from_config_invalid_index_version_falls_back() {
5381 let dir = TempDir::new().unwrap();
5382 let git_dir = dir.path().join(".git");
5383 fs::create_dir_all(&git_dir).unwrap();
5384 fs::write(
5385 git_dir.join("config"),
5386 "[index]\n\tversion = not-a-number\n",
5387 )
5388 .unwrap();
5389 let cfg = crate::config::ConfigSet::load(
5390 &crate::environment::Environment::empty(),
5391 Some(&git_dir),
5392 true,
5393 )
5394 .unwrap();
5395 let idx = Index::new_from_config(&cfg);
5396 assert_eq!(idx.version, INDEX_CONFIG_INVALID_FALLBACK);
5397 }
5398
5399 #[test]
5400 fn fsmonitor_invalidate_directory_prefix_without_trailing_slash() {
5401 let mut idx = Index::new();
5402 idx.add_or_replace(make_entry("dir/a.txt"));
5403 idx.add_or_replace(make_entry("dir/b.txt"));
5404 idx.set_fsmonitor_last_update(Some("tok".into()));
5405 for entry in idx.entries_mut() {
5406 entry.set_fsmonitor_valid(true);
5407 }
5408 idx.invalidate_fsmonitor_for_path("dir", false);
5409 assert!(!idx.get(b"dir/a.txt", 0).unwrap().fsmonitor_valid());
5410 assert!(!idx.get(b"dir/b.txt", 0).unwrap().fsmonitor_valid());
5411 }
5412
5413 #[test]
5414 fn parse_fsmonitor_extension_rejects_truncated_ewah() {
5415 let mut payload = vec![0, 0, 0, 2, b't', 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0];
5416 let err = parse_fsmonitor_extension(&payload).unwrap_err();
5417 assert!(matches!(
5418 err,
5419 Error::IndexFsmonitorExtensionTruncated | Error::IndexFsmonitorExtensionEwahInvalid
5420 ));
5421 payload[12..16].copy_from_slice(&2u32.to_be_bytes());
5422 let err2 = parse_fsmonitor_extension(&payload).unwrap_err();
5423 assert!(matches!(
5424 err2,
5425 Error::IndexFsmonitorExtensionTruncated | Error::IndexFsmonitorExtensionEwahInvalid
5426 ));
5427 }
5428
5429 #[test]
5430 fn unmerge_from_resolve_undo_installs_conflict_stages() {
5431 use crate::resolve_undo::ResolveUndoRecord;
5432 use std::collections::BTreeMap;
5433
5434 let mut idx = Index::new();
5435 idx.add_or_replace(make_entry("f.txt"));
5436 let oid1 = dummy_oid();
5437 let oid2 = ObjectId::from_bytes(&[1u8; 20]).unwrap();
5438 idx.resolve_undo = Some(BTreeMap::from([(
5439 b"f.txt".to_vec(),
5440 ResolveUndoRecord {
5441 modes: [MODE_REGULAR, MODE_REGULAR, 0],
5442 oids: [oid1, oid2, ObjectId::zero()],
5443 },
5444 )]));
5445 assert!(idx.unmerge_path_from_resolve_undo(b"f.txt"));
5446 assert!(idx.has_unmerged_entries());
5447 }
5448
5449 #[test]
5450 fn parse_rejects_truncated_headers() {
5451 for n in 0..12 {
5452 assert!(Index::parse(&vec![0u8; n]).is_err());
5453 }
5454 }
5455
5456 #[test]
5457 fn entry_from_stat_populates_index_metadata() {
5458 let dir = TempDir::new().unwrap();
5459 fs::write(dir.path().join("f.txt"), b"payload").unwrap();
5460 let oid = dummy_oid();
5461 let entry = entry_from_stat(dir.path(), b"f.txt", oid, MODE_REGULAR).unwrap();
5462 assert_eq!(entry.path, b"f.txt");
5463 assert!(entry.size > 0);
5464 }
5465
5466 #[test]
5467 fn write_to_path_honors_skip_hash_flag() {
5468 let dir = TempDir::new().unwrap();
5469 let path = dir.path().join("index");
5470 let mut idx = Index::new();
5471 idx.add_or_replace(make_entry("x"));
5472 idx.write_to_path(&path, true).unwrap();
5473 let bytes = fs::read(&path).unwrap();
5474 assert!(bytes.ends_with(&[0u8; 20]));
5475 }
5476
5477 #[test]
5478 fn index_lock_acquire_blocks_second_holder() {
5479 let dir = TempDir::new().unwrap();
5480 let path = dir.path().join("index");
5481 let mut idx = Index::new();
5482 idx.add_or_replace(make_entry("tracked.txt"));
5483 let lock = IndexLock::acquire(&path, None).unwrap();
5484 assert!(matches!(
5485 IndexLock::acquire(&path, None),
5486 Err(Error::LockHeld { .. })
5487 ));
5488 drop(lock);
5489 idx.write(&path).unwrap(); assert_eq!(Index::load(&path).unwrap().entries.len(), 1);
5491 }
5492
5493 #[test]
5494 fn held_index_lock_is_a_typed_error_naming_the_holder() {
5495 let dir = TempDir::new().unwrap();
5496 let path = dir.path().join("index");
5497 let lock_path = dir.path().join("index.lock");
5498 fs::write(&lock_path, b"foreign").unwrap();
5499 fs::write(
5500 dir.path().join("index~pid.lock"),
5501 format!("pid {}\n", std::process::id()),
5502 )
5503 .unwrap();
5504
5505 let Err(acquire_err) = IndexLock::acquire(&path, None) else {
5506 panic!("lock must be refused");
5507 };
5508 let write_err = Index::new().write(&path).unwrap_err();
5509 for err in [acquire_err, write_err] {
5510 match &err {
5511 Error::LockHeld { path: p, holder } => {
5512 assert_eq!(p, &lock_path);
5513 assert_eq!(
5514 *holder,
5515 Some(crate::lockfile::LockHolder {
5516 pid: u64::from(std::process::id()),
5517 running: true,
5518 })
5519 );
5520 }
5521 other => panic!("expected LockHeld, got {other:?}"),
5522 }
5523 assert!(
5524 err.to_string().contains(&lock_path.display().to_string()),
5525 "{err}"
5526 );
5527 }
5528 assert_eq!(fs::read(&lock_path).unwrap(), b"foreign");
5529 assert!(!path.exists());
5530 }
5531
5532 #[test]
5533 fn write_with_config_and_parallel_policy() {
5534 let dir = TempDir::new().unwrap();
5535 let path = dir.path().join("index");
5536 let mut idx = Index::new();
5537 idx.add_or_replace(make_entry("p"));
5538 let cfg = crate::config::ConfigSet::new();
5539 idx.write_with_config_and_policy(&path, &cfg, crate::parallel::ThreadPolicy::default())
5540 .unwrap();
5541 assert!(path.is_file());
5542 }
5543
5544 #[test]
5545 fn fsmonitor_sync_and_clear_helpers() {
5546 let mut idx = Index::new();
5547 idx.add_or_replace(make_entry("a"));
5548 idx.set_fsmonitor_last_update(Some("token".to_owned()));
5549 idx.sync_fsmonitor_dirty_bitmap();
5550 if let Some(e) = idx.get_mut(b"a", 0) {
5551 e.set_fsmonitor_valid(false);
5552 }
5553 idx.fsmonitor_validity_changed();
5554 idx.clear_fsmonitor_extension();
5555 assert!(idx.fsmonitor_last_update.is_none());
5556 }
5557
5558 #[test]
5559 fn intent_to_add_entry_forces_empty_blob_on_write_tree_path() {
5560 let dir = TempDir::new().unwrap();
5561 let odb = crate::odb::Odb::new(dir.path());
5562 let mut idx = Index::new();
5563 let mut e = make_entry("ita.txt");
5564 e.set_intent_to_add(true);
5565 idx.add_or_replace(e);
5566 crate::write_tree::write_tree_from_index(&odb, &idx, "").unwrap();
5567 }
5568
5569 #[test]
5570 fn optional_load_is_empty_only_when_the_index_is_missing() {
5571 let dir = TempDir::new().unwrap();
5572 let odb = crate::odb::Odb::new(dir.path());
5573 let path = dir.path().join("index");
5574 let missing = Index::load_expand_sparse_optional(&path, &odb).unwrap();
5575 assert!(missing.entries.is_empty());
5576
5577 for len in [0, 12, 31] {
5578 std::fs::write(&path, b"DIRC\0\0\0\x02\0\0\0\0".repeat(3)[..len].to_vec()).unwrap();
5579 assert!(
5580 matches!(
5581 Index::load_expand_sparse_optional(&path, &odb),
5582 Err(Error::IndexError(_))
5583 ),
5584 "len={len}"
5585 );
5586 }
5587 }
5588
5589 fn naive_merge_sorted(kept: &[IndexEntry], insert: &[IndexEntry]) -> Vec<IndexEntry> {
5590 let mut out: Vec<IndexEntry> = kept.iter().chain(insert.iter()).cloned().collect();
5591 out.sort_by(index_entry_path_stage_cmp);
5592 let mut deduped = Vec::with_capacity(out.len());
5593 for entry in out {
5594 if deduped
5595 .last()
5596 .is_some_and(|last| index_entry_path_stage_cmp(last, &entry).is_eq())
5597 {
5598 *deduped.last_mut().unwrap() = entry;
5599 } else {
5600 deduped.push(entry);
5601 }
5602 }
5603 deduped
5604 }
5605
5606 #[test]
5607 fn merge_sorted_index_halves_matches_naive_merge() {
5608 let paths = [
5609 ".gitattributes",
5610 "a.txt",
5611 "b.txt",
5612 "f.txt",
5613 "z.txt",
5614 "dir/nested.txt",
5615 ];
5616 let mut kept = Vec::new();
5617 let mut insert = Vec::new();
5618 for (i, path) in paths.iter().enumerate() {
5619 let mut e = make_entry(path);
5620 e.oid = ObjectId::from_bytes(&[i as u8; 20]).unwrap();
5621 kept.push(e);
5622 }
5623 for (i, path) in paths.iter().enumerate() {
5624 if i % 2 == 0 {
5625 continue;
5626 }
5627 let mut e = make_entry(path);
5628 e.oid = ObjectId::from_bytes(&[(i + 100) as u8; 20]).unwrap();
5629 insert.push(e);
5630 }
5631 kept.sort_by(index_entry_path_stage_cmp);
5632 insert.sort_by(index_entry_path_stage_cmp);
5633
5634 let expected = naive_merge_sorted(&kept, &insert);
5635 let merged = merge_sorted_index_entries(kept, insert);
5636 assert_eq!(merged, expected, "fixed scenario merge mismatch");
5637
5638 for seed in 0u64..200 {
5639 let mut kept = Vec::new();
5640 let mut insert = Vec::new();
5641 for n in 0..((seed % 7) + 1) {
5642 let path = format!("p/{seed}/{n}.txt");
5643 let mut e = make_entry(&path);
5644 e.oid = ObjectId::from_bytes(&[(seed as u8).wrapping_add(n as u8); 20]).unwrap();
5645 kept.push(e);
5646 }
5647 for n in 0..((seed % 5) + 1) {
5648 let path = format!("p/{seed}/{n}.txt");
5649 let mut e = make_entry(&path);
5650 e.oid = ObjectId::from_bytes(
5651 &[(seed as u8).wrapping_add(n as u8).wrapping_add(50); 20],
5652 )
5653 .unwrap();
5654 insert.push(e);
5655 }
5656 kept.sort_by(index_entry_path_stage_cmp);
5657 insert.sort_by(index_entry_path_stage_cmp);
5658 let expected = naive_merge_sorted(&kept, &insert);
5659 let merged = merge_sorted_index_entries(kept, insert);
5660 assert_eq!(merged, expected, "seed {seed} merge mismatch");
5661 }
5662 }
5663}