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 algo = odb.hash_algo();
1057 let mut idx = match read_index_file(path) {
1058 Ok((data, mtime)) => Self {
1059 source_mtime: Some(mtime),
1060 ..Self::parse_with_algo_and_config(&data, algo, config, parse_context)?
1061 },
1062 Err(e) if e.kind() == io::ErrorKind::NotFound => Self {
1063 hash_algo: algo,
1064 ..Self::new()
1065 },
1066 Err(e) => return Err(Error::Io(e)),
1067 };
1068 let protect = config
1069 .map(crate::path_validate::ProtectOptions::from_config)
1070 .unwrap_or_default();
1071 idx.expand_sparse_directory_placeholders(odb, protect)?;
1072 Ok(idx)
1073 }
1074
1075 #[must_use]
1077 pub fn has_sparse_directory_placeholders(&self) -> bool {
1078 self.entries
1079 .iter()
1080 .any(IndexEntry::is_sparse_directory_placeholder)
1081 }
1082
1083 pub fn expand_sparse_directory_placeholders(
1088 &mut self,
1089 odb: &Odb,
1090 protect: crate::path_validate::ProtectOptions,
1091 ) -> Result<()> {
1092 if !self.has_sparse_directory_placeholders() {
1093 return Ok(());
1094 }
1095 let mut out: Vec<IndexEntry> = Vec::with_capacity(self.entries.len());
1096 for entry in self.entries.drain(..) {
1097 if entry.is_sparse_directory_placeholder() {
1098 let prefix = trim_trailing_slash_bytes(&entry.path);
1099 let blobs = flatten_tree_blobs(odb, &entry.oid, prefix, protect)?;
1100 out.extend(blobs);
1101 } else {
1102 out.push(entry);
1103 }
1104 }
1105 self.entries = out;
1106 self.sparse_directories = false;
1107 self.sort();
1108 Ok(())
1109 }
1110
1111 pub fn try_collapse_sparse_directories(
1117 &mut self,
1118 odb: &Odb,
1119 head_tree: &ObjectId,
1120 patterns: &[String],
1121 cone_mode: bool,
1122 enable_sparse_index: bool,
1123 protect: crate::path_validate::ProtectOptions,
1124 ) -> Result<()> {
1125 if !enable_sparse_index || !cone_mode {
1126 self.sparse_directories = false;
1127 return Ok(());
1128 }
1129
1130 let mut prefixes = BTreeSet::<Vec<u8>>::new();
1131 for e in &self.entries {
1132 if e.stage() != 0 || e.mode == MODE_TREE || !e.skip_worktree() {
1133 continue;
1134 }
1135 collect_directory_prefixes(&e.path, &mut prefixes);
1136 }
1137
1138 let mut collapsed_any = false;
1139 let mut ordered: Vec<Vec<u8>> = prefixes.into_iter().collect();
1141 ordered.sort_by_key(|p| std::cmp::Reverse(p.len()));
1142
1143 for pref in ordered {
1144 let pref_str = String::from_utf8_lossy(&pref);
1145 if directory_in_cone(&pref_str, patterns, cone_mode) {
1146 continue;
1147 }
1148 let Some(subtree_oid) = tree_oid_for_prefix(odb, head_tree, &pref)? else {
1149 continue;
1150 };
1151 let expected = collect_sparse_aware_expected_blobs(
1152 odb,
1153 &subtree_oid,
1154 &pref,
1155 patterns,
1156 cone_mode,
1157 &self.entries,
1158 protect,
1159 )?;
1160 if expected.is_empty() {
1161 continue;
1162 }
1163 let mut matched = Vec::new();
1164 for e in &self.entries {
1165 if e.stage() != 0 {
1166 continue;
1167 }
1168 if path_under_prefix(&e.path, &pref) && e.mode != MODE_TREE {
1169 matched.push(e.clone());
1170 }
1171 }
1172 if matched.len() != expected.len() {
1173 continue;
1174 }
1175 matched.sort_by(|a, b| a.path.cmp(&b.path));
1176 let mut exp_sorted = expected;
1177 exp_sorted.sort_by(|a, b| a.path.cmp(&b.path));
1178 if !matched
1179 .iter()
1180 .zip(exp_sorted.iter())
1181 .all(|(a, b)| a.path == b.path && a.oid == b.oid && a.mode == b.mode)
1182 {
1183 continue;
1184 }
1185 if !matched.iter().all(|e| e.skip_worktree()) {
1186 continue;
1187 }
1188 if matched.iter().any(|e| e.mode == MODE_GITLINK)
1192 || exp_sorted.iter().any(|e| e.mode == MODE_GITLINK)
1193 {
1194 continue;
1195 }
1196
1197 let mut path_with_slash = pref.clone();
1198 if !path_with_slash.ends_with(b"/") {
1199 path_with_slash.push(b'/');
1200 }
1201 self.entries
1202 .retain(|e| e.stage() != 0 || !path_under_prefix(&e.path, &pref));
1203 let mut placeholder = IndexEntry {
1204 ctime_sec: 0,
1205 ctime_nsec: 0,
1206 mtime_sec: 0,
1207 mtime_nsec: 0,
1208 dev: 0,
1209 ino: 0,
1210 mode: MODE_TREE,
1211 uid: 0,
1212 gid: 0,
1213 size: 0,
1214 oid: subtree_oid,
1215 flags: path_with_slash.len().min(0xFFF) as u16,
1216 flags_extended: Some(0),
1217 path: path_with_slash,
1218 base_index_pos: 0,
1219 };
1220 placeholder.set_skip_worktree(true);
1221 self.add_or_replace(placeholder);
1222 collapsed_any = true;
1223 }
1224
1225 if collapsed_any {
1226 self.sort();
1227 self.sparse_directories = true;
1228 } else {
1229 self.sparse_directories = false;
1230 }
1231 Ok(())
1232 }
1233
1234 pub fn parse(data: &[u8]) -> Result<Self> {
1240 Self::parse_with_algo(data, detect_index_hash_algo(data))
1244 }
1245
1246 pub fn parse_with_algo(data: &[u8], hash_algo: HashAlgo) -> Result<Self> {
1250 Self::parse_with_algo_and_config(data, hash_algo, None, IndexParseContext::default())
1251 }
1252
1253 pub fn parse_with_algo_and_config(
1255 data: &[u8],
1256 hash_algo: HashAlgo,
1257 config: Option<&ConfigSet>,
1258 parse_context: IndexParseContext,
1259 ) -> Result<Self> {
1260 const INDEX_HEADER_LEN: usize = 12;
1261
1262 let hash_len = hash_algo.len();
1263 if data.len() < INDEX_HEADER_LEN + hash_len {
1264 return Err(Error::IndexError(
1265 "file too short for index header and checksum".to_owned(),
1266 ));
1267 }
1268 let (body, checksum) = data.split_at(data.len() - hash_len);
1269 if body.len() < INDEX_HEADER_LEN {
1270 return Err(Error::IndexError(
1271 "file too short for index header and checksum".to_owned(),
1272 ));
1273 }
1274 let trailer_error = |e: crate::hash::TrailerMismatch| {
1275 Error::IndexError(format!(
1276 "{} checksum mismatch: {e}",
1277 hash_algo.name().to_uppercase()
1278 ))
1279 };
1280 if checksum.iter().all(|&b| b == 0) {
1281 return Self::parse_body(body, hash_algo, config, parse_context);
1282 }
1283 let verify_in_parallel = data.len() >= PARALLEL_TRAILER_VERIFY_MIN_BYTES
1284 && parse_context.thread_policy.threads_override != Some(1);
1285 if !verify_in_parallel {
1286 verify_trailer(hash_algo, data).map_err(trailer_error)?;
1287 return Self::parse_body(body, hash_algo, config, parse_context);
1288 }
1289 std::thread::scope(|scope| {
1292 let verifier = scope.spawn(|| verify_trailer(hash_algo, data));
1293 let parsed = Self::parse_body(body, hash_algo, config, parse_context);
1294 verifier
1295 .join()
1296 .map_err(|_| Error::Message("index checksum worker thread panicked".to_owned()))?
1297 .map_err(trailer_error)?;
1298 parsed
1299 })
1300 }
1301
1302 fn parse_body(
1304 body: &[u8],
1305 hash_algo: HashAlgo,
1306 config: Option<&ConfigSet>,
1307 parse_context: IndexParseContext,
1308 ) -> Result<Self> {
1309 use crate::index_eoie_ieot::{
1310 index_load_worker_count, parallel_index_load_allowed, parse_entries_parallel,
1311 parse_entries_serial, parse_index_extensions, read_eoie_extension, read_ieot_extension,
1312 ParsedIndexExtensions,
1313 };
1314
1315 let hash_len = hash_algo.len();
1316 let magic = &body[..4];
1317 if magic != b"DIRC" {
1318 return Err(Error::IndexError("bad magic: expected DIRC".to_owned()));
1319 }
1320 let version = u32::from_be_bytes(
1321 body[4..8]
1322 .try_into()
1323 .map_err(|_| Error::IndexError("cannot read version".to_owned()))?,
1324 );
1325 if version != 2 && version != 3 && version != 4 {
1326 return Err(Error::IndexError(format!(
1327 "unsupported index version {version}"
1328 )));
1329 }
1330 let count = u32::from_be_bytes(
1331 body[8..12]
1332 .try_into()
1333 .map_err(|_| Error::IndexError("cannot read entry count".to_owned()))?,
1334 );
1335
1336 let workers = index_load_worker_count(parse_context.thread_policy, config, count as usize);
1337 let extension_offset = if workers > 1 {
1338 read_eoie_extension(body, hash_algo)
1339 } else {
1340 None
1341 };
1342
1343 let eoie_payload = 4 + hash_len;
1344 let eoie_with_header = 8 + eoie_payload;
1345 let extensions_end = body
1346 .len()
1347 .checked_sub(eoie_with_header)
1348 .filter(|_| extension_offset.is_some())
1349 .unwrap_or(body.len());
1350
1351 let try_parallel = extension_offset.is_some()
1352 && workers > 1
1353 && parallel_index_load_allowed(parse_context.thread_policy, config, count as usize);
1354
1355 let (entries, ext_fields): (Vec<IndexEntry>, ParsedIndexExtensions) = if try_parallel {
1356 let Some(ext_off) = extension_offset else {
1357 return Err(Error::IndexError(
1358 "parallel index load missing EOIE offset".to_owned(),
1359 ));
1360 };
1361 let ieot = read_ieot_extension(body, ext_off, extensions_end);
1362 let mut loaded_entries = if let Some(ref ieot) = ieot {
1363 parse_entries_parallel(body, version, count, hash_len, ieot, workers)?
1364 } else {
1365 parse_entries_serial(body, version, count, hash_len, 12)?.0
1366 };
1367 let ext_fields =
1368 parse_index_extensions(body, ext_off, extensions_end, hash_algo, None)?;
1369 if let Some(dirty) = ext_fields.fsmonitor_dirty.as_ref() {
1370 apply_fsmonitor_dirty_to_entries(&mut loaded_entries, dirty);
1371 }
1372 (loaded_entries, ext_fields)
1373 } else {
1374 let (mut entries, pos) = parse_entries_serial(body, version, count, hash_len, 12)?;
1375 let ext_fields =
1376 parse_index_extensions(body, pos, body.len(), hash_algo, Some(&mut entries))?;
1377 (entries, ext_fields)
1378 };
1379
1380 Ok(Self {
1381 version,
1382 entries,
1383 entries_sorted: true,
1384 sparse_directories: ext_fields.sparse_directories,
1385 untracked_cache: ext_fields.untracked_cache,
1386 fsmonitor_last_update: ext_fields.fsmonitor_last_update,
1387 fsmonitor_dirty: ext_fields.fsmonitor_dirty,
1388 fsmonitor_raw_extension: ext_fields.fsmonitor_raw_extension,
1389 resolve_undo: ext_fields.resolve_undo,
1390 split_link: ext_fields.split_link,
1391 cache_tree_root: ext_fields.cache_tree_root,
1392 cache_tree: ext_fields.cache_tree,
1393 hash_algo,
1394 source_mtime: None,
1395 defer_untracked_cache_invalidation: false,
1396 skip_racy_smudge_on_write: false,
1397 content_verified: HashMap::new(),
1398 fsmonitor_refresh_ran: false,
1399 suppress_pre_write_index_housekeeping: false,
1400 })
1401 }
1402
1403 pub fn write(&mut self, path: &Path) -> Result<()> {
1409 let git_dir = path.parent();
1410 let config = git_dir.and_then(|d| {
1411 ConfigSet::load(&crate::environment::Environment::empty(), Some(d), true).ok()
1412 });
1413 let skip_hash = index_skip_hash_for_write(config.as_ref());
1414 self.write_to_path(path, skip_hash)
1415 }
1416
1417 pub fn write_with_config(&mut self, path: &Path, config: &ConfigSet) -> Result<()> {
1423 self.write_with_config_and_policy(path, config, crate::parallel::ThreadPolicy::default())
1424 }
1425
1426 pub fn write_with_config_and_policy(
1428 &mut self,
1429 path: &Path,
1430 config: &ConfigSet,
1431 policy: crate::parallel::ThreadPolicy,
1432 ) -> Result<()> {
1433 let skip_hash = index_skip_hash_for_write(Some(config));
1434 self.write_to_path_with_config_and_policy(path, skip_hash, Some(config), policy)
1435 }
1436
1437 pub fn write_to_path(&mut self, path: &Path, skip_hash: bool) -> Result<()> {
1441 self.write_to_path_with_config(path, skip_hash, None)
1442 }
1443
1444 pub(crate) fn write_to_path_with_config(
1446 &mut self,
1447 path: &Path,
1448 skip_hash: bool,
1449 config: Option<&ConfigSet>,
1450 ) -> Result<()> {
1451 self.write_to_path_with_config_and_policy(
1452 path,
1453 skip_hash,
1454 config,
1455 crate::parallel::ThreadPolicy::default(),
1456 )
1457 }
1458
1459 pub(crate) fn write_to_path_with_config_and_policy(
1460 &mut self,
1461 path: &Path,
1462 skip_hash: bool,
1463 config: Option<&ConfigSet>,
1464 policy: crate::parallel::ThreadPolicy,
1465 ) -> Result<()> {
1466 self.write_to_path_with_config_and_lock_policy(path, skip_hash, config, None, policy)
1467 }
1468
1469 pub(crate) fn write_to_path_with_config_and_lock_policy(
1470 &mut self,
1471 path: &Path,
1472 skip_hash: bool,
1473 config: Option<&ConfigSet>,
1474 existing_lock: Option<&mut IndexLock>,
1475 policy: crate::parallel::ThreadPolicy,
1476 ) -> Result<()> {
1477 self.write_to_path_with_config_and_lock_mode_policy(
1478 path,
1479 skip_hash,
1480 config,
1481 existing_lock,
1482 true,
1483 policy,
1484 )
1485 }
1486
1487 pub(crate) fn write_to_path_with_config_and_lock_mode_policy(
1488 &mut self,
1489 path: &Path,
1490 skip_hash: bool,
1491 config: Option<&ConfigSet>,
1492 existing_lock: Option<&mut IndexLock>,
1493 commit: bool,
1494 policy: crate::parallel::ThreadPolicy,
1495 ) -> Result<()> {
1496 let body = self.serialize_index_body(config, policy)?;
1497
1498 if let Some(lock) = existing_lock {
1499 if lock.index_path != path {
1500 return Err(Error::Message(
1501 "index lock path does not match write target".into(),
1502 ));
1503 }
1504 let checksum: Vec<u8> = if skip_hash {
1505 vec![0u8; self.hash_algo.len()]
1506 } else {
1507 hash_index_body(self.hash_algo, &body)
1508 };
1509 lock.write_index_contents(&body, &checksum)?;
1510 if commit {
1511 lock.commit_replace_index()?;
1512 }
1513 return Ok(());
1514 }
1515
1516 let tmp_path = path.with_extension("lock");
1517 let pid_path = pid_path_for_lock(&tmp_path);
1518 let lockfile_pid_enabled = lockfile_pid_enabled(path, config);
1519
1520 use crate::lockfile::{LockFile, LockWait};
1521 let wait = LockWait::files_ref_lock(config);
1522 let (tmp_path, mut lock_file) =
1523 LockFile::acquire(&tmp_path, wait).and_then(LockFile::into_open_file)?;
1524
1525 let mut wrote_pid_file = false;
1526 if lockfile_pid_enabled {
1527 if let Err(e) = write_lock_pid_file(&pid_path) {
1528 let _ = fs::remove_file(&tmp_path);
1529 return Err(Error::Io(e));
1530 }
1531 wrote_pid_file = true;
1532 }
1533
1534 if let Err(e) =
1535 write_index_body_and_trailer(&mut lock_file, self.hash_algo, skip_hash, &body)
1536 {
1537 let _ = fs::remove_file(&tmp_path);
1538 if wrote_pid_file {
1539 let _ = fs::remove_file(&pid_path);
1540 }
1541 return Err(Error::Io(e));
1542 }
1543 drop(lock_file);
1544
1545 if let Err(e) = fs::rename(&tmp_path, path) {
1546 let _ = fs::remove_file(&tmp_path);
1547 if wrote_pid_file {
1548 let _ = fs::remove_file(&pid_path);
1549 }
1550 return Err(Error::Io(e));
1551 }
1552 if wrote_pid_file {
1553 let _ = fs::remove_file(&pid_path);
1554 }
1555 Ok(())
1556 }
1557
1558 fn serialize_index_body(
1559 &mut self,
1560 config: Option<&ConfigSet>,
1561 policy: crate::parallel::ThreadPolicy,
1562 ) -> Result<Vec<u8>> {
1563 self.ensure_canonical_order();
1564 let mut body = Vec::new();
1565 self.serialize_into_with_options(&mut body, config, policy)?;
1566 Ok(body)
1567 }
1568
1569 #[cfg(test)]
1570 pub(crate) fn serialize_into(&self, out: &mut Vec<u8>) -> Result<()> {
1574 self.serialize_into_with_options(out, None, crate::parallel::ThreadPolicy::default())
1575 }
1576
1577 pub(crate) fn serialize_into_with_options(
1578 &self,
1579 out: &mut Vec<u8>,
1580 config: Option<&ConfigSet>,
1581 policy: crate::parallel::ThreadPolicy,
1582 ) -> Result<()> {
1583 use crate::index_eoie_ieot::{
1584 entries_per_ieot_block, ieot_block_count_for_write, record_end_of_index_entries,
1585 write_ieot_payload, IeotEntry, IeotTable, INDEX_EXT_EOIE, INDEX_EXT_IEOT,
1586 };
1587 let has_extended_flags = self.entries.iter().any(|e| e.flags_extended.is_some());
1588 let write_version = if self.version >= 4 {
1589 4
1590 } else if has_extended_flags {
1591 3
1592 } else if self.version >= 3 {
1593 2
1594 } else {
1595 self.version
1596 };
1597 out.reserve(self.estimate_body_capacity(write_version, config));
1598 out.extend_from_slice(b"DIRC");
1600 out.extend_from_slice(&write_version.to_be_bytes());
1601 out.extend_from_slice(&(self.entries.len() as u32).to_be_bytes());
1602
1603 let ieot_blocks = ieot_block_count_for_write(policy, config, self.entries.len());
1604 let ieot_stride = ieot_blocks.map(|b| entries_per_ieot_block(self.entries.len(), b));
1605 let mut ieot_table = IeotTable {
1606 entries: Vec::new(),
1607 };
1608 let mut nr_in_block = 0u32;
1609 let mut block_start_offset = out.len();
1610
1611 if write_version == 4 {
1612 let mut previous_path: Vec<u8> = Vec::new();
1613 for (i, entry) in self.entries.iter().enumerate() {
1614 if let Some(stride) = ieot_stride {
1615 if i > 0 && i % stride == 0 {
1616 ieot_table.entries.push(IeotEntry {
1617 offset: block_start_offset as u32,
1618 nr: nr_in_block,
1619 });
1620 nr_in_block = 0;
1621 if !previous_path.is_empty() {
1624 previous_path[0] = 0;
1625 }
1626 block_start_offset = out.len();
1627 }
1628 }
1629 serialize_entry_v4(entry, &mut previous_path, out);
1630 nr_in_block += 1;
1631 }
1632 } else {
1633 for (i, entry) in self.entries.iter().enumerate() {
1634 if let Some(stride) = ieot_stride {
1635 if i > 0 && i % stride == 0 {
1636 ieot_table.entries.push(IeotEntry {
1637 offset: block_start_offset as u32,
1638 nr: nr_in_block,
1639 });
1640 nr_in_block = 0;
1641 block_start_offset = out.len();
1642 }
1643 }
1644 serialize_entry(entry, write_version, out);
1645 nr_in_block += 1;
1646 }
1647 }
1648 if ieot_blocks.is_some() && nr_in_block > 0 {
1649 ieot_table.entries.push(IeotEntry {
1650 offset: block_start_offset as u32,
1651 nr: nr_in_block,
1652 });
1653 }
1654
1655 let extension_offset = out.len();
1656 let write_eoie = record_end_of_index_entries(config);
1657 let mut eoie_header_bytes: Option<Vec<u8>> = write_eoie.then(Vec::new);
1658
1659 if !ieot_table.entries.is_empty() {
1660 let mut payload = Vec::new();
1661 write_ieot_payload(&ieot_table, &mut payload);
1662 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_IEOT, &payload);
1663 }
1664 if self.sparse_directories {
1665 append_index_extension(
1666 out,
1667 &mut eoie_header_bytes,
1668 INDEX_EXT_SPARSE_DIRECTORIES,
1669 &[],
1670 );
1671 }
1672 if let Some(uc) = &self.untracked_cache {
1673 let payload = untracked_cache::write_untracked_extension(uc);
1674 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_UNTRACKED, &payload);
1675 }
1676 self.append_fsmonitor_extension(out, &mut eoie_header_bytes)?;
1677 if let Some(ru) = &self.resolve_undo {
1678 let payload = write_resolve_undo_payload(ru);
1679 if !payload.is_empty() {
1680 append_index_extension(
1681 out,
1682 &mut eoie_header_bytes,
1683 INDEX_EXT_RESOLVE_UNDO,
1684 &payload,
1685 );
1686 }
1687 }
1688 if let Some(sl) = &self.split_link {
1689 use crate::ewah_bitmap::EwahBitmap;
1690 let del = sl
1691 .delete_bitmap
1692 .as_ref()
1693 .cloned()
1694 .unwrap_or_else(EwahBitmap::new);
1695 let rep = sl
1696 .replace_bitmap
1697 .as_ref()
1698 .cloned()
1699 .unwrap_or_else(EwahBitmap::new);
1700 let payload =
1701 crate::split_index::serialize_link_extension_payload(&sl.base_oid, &del, &rep);
1702 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_LINK, &payload);
1703 }
1704 if let Some(cache_tree) = &self.cache_tree {
1705 let mut payload = Vec::new();
1706 serialize_cache_tree_node(cache_tree, &mut payload);
1707 append_index_extension(out, &mut eoie_header_bytes, INDEX_EXT_CACHE_TREE, &payload);
1708 }
1709 if write_eoie {
1710 let mut eoie_payload = Vec::new();
1711 eoie_payload.extend_from_slice(&(extension_offset as u32).to_be_bytes());
1712 let headers = eoie_header_bytes.as_deref().unwrap_or(&[]);
1713 eoie_payload.extend_from_slice(&self.hash_algo.checksum_digest(headers));
1714 append_index_extension(out, &mut None, INDEX_EXT_EOIE, &eoie_payload);
1715 }
1716 Ok(())
1717 }
1718
1719 fn append_fsmonitor_extension(
1720 &self,
1721 out: &mut Vec<u8>,
1722 eoie_headers: &mut Option<Vec<u8>>,
1723 ) -> Result<()> {
1724 if let Some(raw) = &self.fsmonitor_raw_extension {
1725 if self.fsmonitor_raw_still_valid() {
1726 append_index_extension(out, eoie_headers, INDEX_EXT_FSMONITOR, raw);
1727 return Ok(());
1728 }
1729 }
1730 let Some(token) = &self.fsmonitor_last_update else {
1731 return Ok(());
1732 };
1733 let dirty = self
1734 .fsmonitor_dirty
1735 .as_ref()
1736 .cloned()
1737 .unwrap_or_else(|| self.build_fsmonitor_dirty_from_entries());
1738 if dirty.bit_size > self.fsmonitor_active_entry_count() {
1739 return Ok(());
1740 }
1741 let mut payload = Vec::new();
1742 payload.extend_from_slice(&FSMONITOR_EXT_VERSION2.to_be_bytes());
1743 payload.extend_from_slice(token.as_bytes());
1744 payload.push(0);
1745 let ewah_size_fixup = payload.len();
1746 payload.extend_from_slice(&0u32.to_be_bytes());
1747 let ewah_start = payload.len();
1748 dirty.serialize(&mut payload);
1749 let ewah_size = (payload.len() - ewah_start) as u32;
1750 payload[ewah_size_fixup..ewah_size_fixup + 4].copy_from_slice(&ewah_size.to_be_bytes());
1751 append_index_extension(out, eoie_headers, INDEX_EXT_FSMONITOR, &payload);
1752 Ok(())
1753 }
1754
1755 fn fsmonitor_active_entry_count(&self) -> usize {
1757 self.entries.len()
1758 }
1759
1760 fn build_fsmonitor_dirty_from_entries(&self) -> crate::ewah_bitmap::EwahBitmap {
1762 use crate::ewah_bitmap::EwahBitmap;
1763 let mut dirty = EwahBitmap::new();
1764 if self.fsmonitor_last_update.is_none() {
1765 return dirty;
1766 }
1767 #[cfg(test)]
1768 tests::fsmonitor_rebuild_counter::note_build();
1769 for (i, entry) in self.entries.iter().enumerate() {
1770 if !entry.fsmonitor_valid() {
1771 dirty.set_bit_extend(i);
1772 }
1773 }
1774 dirty
1775 }
1776
1777 fn fsmonitor_raw_still_valid(&self) -> bool {
1779 use crate::ewah_bitmap::ewah_bitmaps_equal;
1780 let Some(raw) = self.fsmonitor_raw_extension.as_ref() else {
1781 return false;
1782 };
1783 let Some(token) = self.fsmonitor_last_update.as_deref() else {
1784 return false;
1785 };
1786 let Ok((raw_token, raw_dirty)) = parse_fsmonitor_extension(raw) else {
1787 return false;
1788 };
1789 if raw_token != token {
1790 return false;
1791 }
1792 let current = self.build_fsmonitor_dirty_from_entries();
1793 if !ewah_bitmaps_equal(&raw_dirty, ¤t) {
1794 return false;
1795 }
1796 current.bit_size <= self.fsmonitor_active_entry_count()
1797 }
1798
1799 fn rebuild_fsmonitor_dirty_from_entries(&mut self) {
1801 if self.fsmonitor_last_update.is_none() {
1802 self.fsmonitor_dirty = None;
1803 return;
1804 }
1805 self.fsmonitor_dirty = Some(self.build_fsmonitor_dirty_from_entries());
1806 }
1807
1808 fn invalidate_fsmonitor_bitmap(&mut self) {
1810 if self.fsmonitor_last_update.is_none() {
1811 return;
1812 }
1813 self.fsmonitor_raw_extension = None;
1814 self.fsmonitor_dirty = None;
1815 }
1816
1817 pub fn set_fsmonitor_last_update(&mut self, token: Option<String>) {
1819 if self.fsmonitor_last_update.as_deref() != token.as_deref() {
1820 self.fsmonitor_raw_extension = None;
1821 }
1822 self.fsmonitor_last_update = token;
1823 self.rebuild_fsmonitor_dirty_from_entries();
1824 }
1825
1826 pub fn clear_fsmonitor_extension(&mut self) {
1828 self.fsmonitor_last_update = None;
1829 self.fsmonitor_dirty = None;
1830 self.fsmonitor_raw_extension = None;
1831 }
1832
1833 pub fn sync_fsmonitor_dirty_bitmap(&mut self) {
1835 self.rebuild_fsmonitor_dirty_from_entries();
1836 }
1837
1838 pub fn fsmonitor_validity_changed(&mut self) {
1840 self.invalidate_fsmonitor_bitmap();
1841 }
1842
1843 pub(crate) fn invalidate_all_fsmonitor_valid(&mut self) {
1845 for entry in &mut self.entries {
1846 if entry.stage() == 0 {
1847 entry.set_fsmonitor_valid(false);
1848 }
1849 }
1850 self.fsmonitor_validity_changed();
1851 }
1852
1853 pub(crate) fn invalidate_fsmonitor_for_path(&mut self, path: &str, ignorecase: bool) {
1855 if let Some(uc) = self.untracked_cache.as_mut() {
1856 crate::untracked_cache::invalidate_path(uc, path);
1857 }
1858 let mut invalidated = if path.ends_with('/') {
1859 self.invalidate_fsmonitor_directory_cone(path.trim_end_matches('/'), ignorecase)
1860 } else {
1861 self.invalidate_fsmonitor_unqualified_path(path, ignorecase)
1862 };
1863 if !invalidated && ignorecase {
1864 invalidated = self.invalidate_fsmonitor_icase_fallback(path);
1865 }
1866 if invalidated {
1867 self.fsmonitor_validity_changed();
1868 }
1869 }
1870
1871 fn invalidate_fsmonitor_unqualified_path(&mut self, path: &str, ignorecase: bool) -> bool {
1872 if let Some(entry) = self.get_mut(path.as_bytes(), 0) {
1873 entry.set_fsmonitor_valid(false);
1874 return true;
1875 }
1876 if self.has_stage0_descendant_under(path) {
1877 return self.invalidate_fsmonitor_directory_cone(path, ignorecase);
1878 }
1879 false
1880 }
1881
1882 fn invalidate_fsmonitor_directory_cone(&mut self, prefix: &str, ignorecase: bool) -> bool {
1883 if prefix.is_empty() {
1884 self.invalidate_all_fsmonitor_valid();
1885 return true;
1886 }
1887 let prefix_slash = format!("{prefix}/");
1888 let mut any = false;
1889 for entry in &mut self.entries {
1890 if entry.stage() != 0 {
1891 continue;
1892 }
1893 let Some(rel) = std::str::from_utf8(&entry.path).ok() else {
1894 continue;
1895 };
1896 if Self::fsmonitor_path_under_prefix(rel, prefix, &prefix_slash, ignorecase) {
1897 entry.set_fsmonitor_valid(false);
1898 any = true;
1899 }
1900 }
1901 any
1902 }
1903
1904 fn invalidate_fsmonitor_icase_fallback(&mut self, path: &str) -> bool {
1905 let is_dir = path.ends_with('/');
1906 let prefix = path.trim_end_matches('/');
1907 let prefix_slash = format!("{prefix}/");
1908 let mut any = false;
1909 for entry in &mut self.entries {
1910 if entry.stage() != 0 {
1911 continue;
1912 }
1913 let Some(rel) = std::str::from_utf8(&entry.path).ok() else {
1914 continue;
1915 };
1916 let matches = if is_dir {
1917 Self::fsmonitor_path_under_prefix(rel, prefix, &prefix_slash, true)
1918 } else {
1919 rel.eq_ignore_ascii_case(prefix)
1920 };
1921 if matches {
1922 entry.set_fsmonitor_valid(false);
1923 any = true;
1924 }
1925 }
1926 any
1927 }
1928
1929 fn fsmonitor_path_under_prefix(
1930 rel: &str,
1931 prefix: &str,
1932 prefix_slash: &str,
1933 ignorecase: bool,
1934 ) -> bool {
1935 if ignorecase {
1936 rel.eq_ignore_ascii_case(prefix)
1937 || (rel.len() > prefix.len()
1938 && rel.as_bytes().get(prefix.len()) == Some(&b'/')
1939 && rel[..prefix.len()].eq_ignore_ascii_case(prefix))
1940 } else {
1941 rel == prefix || rel.starts_with(prefix_slash)
1942 }
1943 }
1944
1945 fn touch_fsmonitor_after_entry_change(&mut self) {
1947 self.invalidate_fsmonitor_bitmap();
1948 }
1949
1950 #[must_use]
1952 pub fn entries(&self) -> &[IndexEntry] {
1953 &self.entries
1954 }
1955
1956 pub fn entries_mut(&mut self) -> &mut Vec<IndexEntry> {
1958 self.entries_sorted = false;
1959 &mut self.entries
1960 }
1961
1962 pub fn mark_entries_unsorted(&mut self) {
1964 self.entries_sorted = false;
1965 }
1966
1967 pub(crate) fn push_entry_unsorted(&mut self, entry: IndexEntry) {
1969 self.entries.push(entry);
1970 self.entries_sorted = false;
1971 }
1972
1973 #[must_use]
1975 pub fn entries_are_sorted(&self) -> bool {
1976 self.entries
1977 .windows(2)
1978 .all(|pair| (&pair[0].path, pair[0].stage()) <= (&pair[1].path, pair[1].stage()))
1979 }
1980
1981 fn cmp_paths(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
1983 record_path_cmp_for_tests();
1984 a.cmp(b)
1985 }
1986
1987 fn entry_matches(entry: &IndexEntry, path: &[u8], stage: u8) -> bool {
1988 entry.path == path && entry.stage() == stage
1989 }
1990
1991 pub(crate) fn find_entry_pos(&self, path: &[u8], stage: u8) -> Option<usize> {
1993 if self.entries_sorted {
1994 return match self.entries.binary_search_by(|e| {
1995 Self::cmp_paths(e.path.as_slice(), path).then_with(|| e.stage().cmp(&stage))
1996 }) {
1997 Ok(pos) if Self::entry_matches(&self.entries[pos], path, stage) => Some(pos),
1998 _ => None,
1999 };
2000 }
2001 self.entries
2002 .iter()
2003 .position(|e| Self::entry_matches(e, path, stage))
2004 }
2005
2006 fn path_run_range_sorted(&self, path: &[u8]) -> (usize, usize) {
2008 debug_assert!(self.entries_sorted);
2009 let start = self
2010 .entries
2011 .partition_point(|e| Self::cmp_paths(e.path.as_slice(), path).is_lt());
2012 let end = start
2013 + self.entries[start..]
2014 .iter()
2015 .take_while(|e| e.path == path)
2016 .count();
2017 (start, end)
2018 }
2019
2020 #[must_use]
2023 pub fn has_stage0_descendant_under(&self, rel_dir: &str) -> bool {
2024 if rel_dir.is_empty() {
2025 return self.entries.iter().any(|e| e.stage() == 0);
2026 }
2027 if self.entries_sorted {
2028 let (start, end) = self.descendant_run_range_sorted(rel_dir);
2029 return self.entries[start..end].iter().any(|e| e.stage() == 0);
2030 }
2031 let prefix = format!("{rel_dir}/");
2032 self.entries
2033 .iter()
2034 .any(|e| e.stage() == 0 && e.path.starts_with(prefix.as_bytes()))
2035 }
2036
2037 fn descendant_run_range_sorted(&self, path: &str) -> (usize, usize) {
2039 debug_assert!(self.entries_sorted);
2040 let prefix = path.as_bytes();
2041 let plen = prefix.len();
2042 let mut prefix_slash = Vec::with_capacity(plen + 1);
2043 prefix_slash.extend_from_slice(prefix);
2044 prefix_slash.push(b'/');
2045 let start = self.entries.partition_point(|e| {
2046 Self::cmp_paths(e.path.as_slice(), prefix_slash.as_slice()).is_lt()
2047 });
2048 let mut end = start;
2049 while end < self.entries.len() {
2050 let ep = self.entries[end].path.as_slice();
2051 if ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/' {
2052 end += 1;
2053 } else {
2054 break;
2055 }
2056 }
2057 (start, end)
2058 }
2059
2060 fn record_resolve_undo_for_path(&mut self, path: &[u8]) {
2061 for e in &self.entries {
2062 if e.path == path && e.stage() != 0 {
2063 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2064 }
2065 }
2066 }
2067
2068 fn record_resolve_undo_for_run(&mut self, start: usize, end: usize) {
2069 for e in &self.entries[start..end] {
2070 if e.stage() != 0 {
2071 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2072 }
2073 }
2074 }
2075
2076 pub(crate) fn drop_derived_index_extensions(&mut self) {
2078 self.cache_tree = None;
2079 self.cache_tree_root = None;
2080 self.untracked_cache = None;
2081 }
2082
2083 pub(crate) fn invalidate_cache_tree_for_bulk_checkout<'a>(
2085 &mut self,
2086 dir_prefixes: &BTreeSet<String>,
2087 paths: impl IntoIterator<Item = &'a [u8]>,
2088 replacements: &[IndexEntry],
2089 ) {
2090 if self.cache_tree.is_none() {
2091 return;
2092 }
2093 for dir in dir_prefixes {
2094 self.invalidate_cache_tree_for_path(dir.as_bytes());
2095 }
2096 for path in paths {
2097 self.invalidate_cache_tree_for_path(path);
2098 }
2099 for entry in replacements {
2100 self.invalidate_cache_tree_for_path(&entry.path);
2101 }
2102 }
2103
2104 fn invalidate_after_path_removal(&mut self, path: &[u8]) {
2105 if let Ok(p) = std::str::from_utf8(path) {
2106 self.invalidate_untracked_cache_for_path(p);
2107 }
2108 self.invalidate_cache_tree_for_path(path);
2109 }
2110
2111 pub(crate) fn remove_prefixes_paths_and_insert<'a>(
2113 &mut self,
2114 dir_prefixes: &BTreeSet<String>,
2115 paths: impl IntoIterator<Item = &'a [u8]>,
2116 replacements: impl IntoIterator<Item = IndexEntry>,
2117 ) {
2118 let mut replacements: Vec<IndexEntry> = replacements.into_iter().collect();
2119 if !replacements.is_empty() {
2120 replacements
2121 .sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
2122 dedup_sorted_replacement_entries(&mut replacements);
2123 }
2124
2125 let mut paths: Vec<&[u8]> = paths.into_iter().collect();
2126 paths.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2127 paths.dedup_by(|a, b| a == b);
2128
2129 let mut prefix_bytes: Vec<&[u8]> = dir_prefixes.iter().map(|d| d.as_bytes()).collect();
2130 prefix_bytes.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2131 prefix_bytes.dedup_by(|a, b| a == b);
2132
2133 if dir_prefixes.is_empty() && paths.is_empty() {
2134 self.merge_replacements_into_sorted_index(replacements);
2135 return;
2136 }
2137
2138 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2139 let mut kept = Vec::with_capacity(self.entries.len());
2140 let skip_per_path_extension_inval = self.defer_untracked_cache_invalidation;
2141
2142 if self.entries_sorted {
2143 let mut path_idx = 0usize;
2144 for entry in self.entries.drain(..) {
2145 let ep = entry.path.as_slice();
2146 while path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_lt() {
2147 path_idx += 1;
2148 }
2149 let remove = (path_idx < paths.len()
2150 && Self::cmp_paths(paths[path_idx], ep).is_eq())
2151 || Self::entry_under_any_directory_prefix(ep, &prefix_bytes);
2152 if remove {
2153 if entry.stage() != 0 {
2154 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2155 }
2156 if !skip_per_path_extension_inval
2157 && invalidated_paths.last().is_none_or(|p| p.as_slice() != ep)
2158 {
2159 invalidated_paths.push(entry.path.clone());
2160 }
2161 } else {
2162 kept.push(entry);
2163 }
2164 }
2165 let repl_for_inval: Vec<Vec<u8>> = if skip_per_path_extension_inval {
2166 Vec::new()
2167 } else {
2168 replacements
2169 .iter()
2170 .filter(|e| e.stage() == 0)
2171 .map(|e| e.path.clone())
2172 .collect()
2173 };
2174 self.entries = merge_sorted_index_entries(kept, replacements);
2175 self.entries_sorted = true;
2176 if skip_per_path_extension_inval {
2177 self.untracked_cache = None;
2178 self.touch_fsmonitor_after_entry_change();
2179 return;
2180 }
2181 const BULK_INDEX_EXTENSION_INVALIDATE_MIN: usize = 64;
2182 let mut invalidations = dir_prefixes.len();
2183 for path in &invalidated_paths {
2184 if dir_prefixes
2185 .iter()
2186 .all(|d| !Self::entry_under_directory_prefix(path.as_slice(), d.as_bytes()))
2187 {
2188 invalidations += 1;
2189 }
2190 }
2191 invalidations += repl_for_inval.len();
2192 if invalidations >= BULK_INDEX_EXTENSION_INVALIDATE_MIN {
2193 self.drop_derived_index_extensions();
2194 } else {
2195 for dir in dir_prefixes {
2196 self.invalidate_after_path_removal(dir.as_bytes());
2197 }
2198 for path in invalidated_paths {
2199 if dir_prefixes
2200 .iter()
2201 .all(|d| !Self::entry_under_directory_prefix(path.as_slice(), d.as_bytes()))
2202 {
2203 self.invalidate_after_path_removal(&path);
2204 }
2205 }
2206 for path in repl_for_inval {
2207 if let Ok(p) = std::str::from_utf8(&path) {
2208 self.invalidate_untracked_cache_for_path(p);
2209 }
2210 self.invalidate_cache_tree_for_path(&path);
2211 }
2212 }
2213 self.touch_fsmonitor_after_entry_change();
2214 return;
2215 }
2216 if !dir_prefixes.is_empty() {
2217 self.remove_directory_prefixes(dir_prefixes);
2218 }
2219 self.remove_paths_and_insert(paths, replacements);
2220 }
2221
2222 pub(crate) fn remove_directory_prefixes(&mut self, dirs: &BTreeSet<String>) {
2224 if dirs.is_empty() {
2225 return;
2226 }
2227 let mut prefixes: Vec<&[u8]> = dirs.iter().map(|d| d.as_bytes()).collect();
2228 prefixes.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2229 prefixes.dedup_by(|a, b| a == b);
2230
2231 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2232 let mut kept = Vec::with_capacity(self.entries.len());
2233
2234 if self.entries_sorted {
2235 for entry in self.entries.drain(..) {
2236 let ep = entry.path.as_slice();
2237 let remove = Self::entry_under_any_directory_prefix(ep, &prefixes);
2238 if remove {
2239 if entry.stage() != 0 {
2240 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2241 }
2242 if invalidated_paths.last().is_none_or(|p| p.as_slice() != ep) {
2243 invalidated_paths.push(entry.path.clone());
2244 }
2245 } else {
2246 kept.push(entry);
2247 }
2248 }
2249 self.entries = kept;
2250 } else {
2251 for entry in self.entries.drain(..) {
2252 let remove =
2253 Self::entry_under_any_directory_prefix(entry.path.as_slice(), &prefixes);
2254 if remove {
2255 if entry.stage() != 0 {
2256 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2257 }
2258 if invalidated_paths
2259 .last()
2260 .is_none_or(|p| p.as_slice() != entry.path)
2261 {
2262 invalidated_paths.push(entry.path.clone());
2263 }
2264 } else {
2265 kept.push(entry);
2266 }
2267 }
2268 self.entries = kept;
2269 }
2270 if invalidated_paths.is_empty() {
2271 return;
2272 }
2273 for dir in dirs {
2274 self.invalidate_after_path_removal(dir.as_bytes());
2275 }
2276 self.touch_fsmonitor_after_entry_change();
2277 }
2278
2279 fn entry_under_directory_prefix(path: &[u8], prefix: &[u8]) -> bool {
2280 if path.len() < prefix.len() {
2281 return false;
2282 }
2283 if path[..prefix.len()] != *prefix {
2284 return false;
2285 }
2286 path.len() == prefix.len() || path[prefix.len()] == b'/'
2287 }
2288
2289 fn entry_under_any_directory_prefix(path: &[u8], prefixes: &[&[u8]]) -> bool {
2290 prefixes
2291 .iter()
2292 .any(|prefix| Self::entry_under_directory_prefix(path, prefix))
2293 }
2294
2295 pub fn remove_paths<'a>(&mut self, paths: impl IntoIterator<Item = &'a [u8]>) {
2297 use std::collections::BTreeSet;
2298
2299 let mut paths: Vec<&[u8]> = paths.into_iter().collect();
2300 if paths.is_empty() {
2301 return;
2302 }
2303 paths.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2304 paths.dedup_by(|a, b| a == b);
2305
2306 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2307 let mut kept = Vec::with_capacity(self.entries.len());
2308
2309 if self.entries_sorted {
2310 let mut path_idx = 0usize;
2311 for entry in self.entries.drain(..) {
2312 let ep = entry.path.as_slice();
2313 while path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_lt() {
2314 path_idx += 1;
2315 }
2316 let remove = path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_eq();
2317 if remove {
2318 if entry.stage() != 0 {
2319 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2320 }
2321 if invalidated_paths.last().is_none_or(|p| p.as_slice() != ep) {
2322 invalidated_paths.push(entry.path.clone());
2323 }
2324 } else {
2325 kept.push(entry);
2326 }
2327 }
2328 self.entries = kept;
2329 } else {
2330 let remove_set: BTreeSet<&[u8]> = paths.into_iter().collect();
2331 for entry in self.entries.drain(..) {
2332 if remove_set.contains(entry.path.as_slice()) {
2333 if entry.stage() != 0 {
2334 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2335 }
2336 if invalidated_paths
2337 .last()
2338 .is_none_or(|p| p.as_slice() != entry.path.as_slice())
2339 {
2340 invalidated_paths.push(entry.path.clone());
2341 }
2342 } else {
2343 kept.push(entry);
2344 }
2345 }
2346 self.entries = kept;
2347 }
2348
2349 for path in invalidated_paths {
2350 self.invalidate_after_path_removal(&path);
2351 }
2352 self.touch_fsmonitor_after_entry_change();
2353 }
2354
2355 pub(crate) fn replace_stage0_entries_batch(&mut self, replacements: Vec<IndexEntry>) {
2357 self.merge_replacements_into_sorted_index(replacements);
2358 }
2359
2360 pub(crate) fn mark_content_verified(&mut self, path: &[u8], oid: ObjectId) {
2363 self.content_verified.insert(path.to_vec(), oid);
2364 }
2365
2366 pub(crate) fn overwrite_stage0_entries(&mut self, replacements: Vec<IndexEntry>) {
2372 const BULK_INVALIDATE_MIN: usize = 64;
2373 if replacements.is_empty() {
2374 return;
2375 }
2376 let defer_untracked = self.defer_untracked_cache_invalidation;
2377 let drop_caches = !defer_untracked && replacements.len() >= BULK_INVALIDATE_MIN;
2378 let mut unmatched = Vec::new();
2379 for mut entry in replacements {
2380 let pos = if entry.stage() == 0 {
2381 self.find_entry_pos(&entry.path, 0)
2382 } else {
2383 None
2384 };
2385 let Some(pos) = pos else {
2386 unmatched.push(entry);
2387 continue;
2388 };
2389 if !drop_caches {
2390 if !defer_untracked {
2391 if let Ok(p) = std::str::from_utf8(&entry.path) {
2392 self.invalidate_untracked_cache_for_path(p);
2393 }
2394 }
2395 self.invalidate_cache_tree_for_path(&entry.path);
2396 }
2397 entry.base_index_pos = self.entries[pos].base_index_pos;
2398 self.entries[pos] = entry;
2399 }
2400 if drop_caches {
2401 self.cache_tree = None;
2402 self.cache_tree_root = None;
2403 self.untracked_cache = None;
2404 }
2405 self.touch_fsmonitor_after_entry_change();
2406 if !unmatched.is_empty() {
2407 self.merge_replacements_into_sorted_index(unmatched);
2408 }
2409 }
2410
2411 fn merge_sorted_replacements_into_entries(&mut self, mut replacements: Vec<IndexEntry>) {
2413 if replacements.is_empty() {
2414 return;
2415 }
2416 self.entries.reserve(replacements.len());
2417 let mid = self.entries.len();
2418 self.entries.append(&mut replacements);
2419 merge_sorted_index_adjacent_halves(&mut self.entries, mid);
2420 }
2421
2422 fn merge_replacements_into_sorted_index(&mut self, replacements: Vec<IndexEntry>) {
2424 if replacements.is_empty() {
2425 return;
2426 }
2427 if !self.entries_sorted {
2428 for entry in replacements {
2429 self.add_or_replace(entry);
2430 }
2431 return;
2432 }
2433 const BULK_MERGE_MIN: usize = 64;
2434 if self.defer_untracked_cache_invalidation {
2435 for entry in replacements.iter().filter(|e| e.stage() == 0) {
2438 self.invalidate_cache_tree_for_path(&entry.path);
2439 }
2440 self.merge_sorted_replacements_into_entries(replacements);
2441 self.touch_fsmonitor_after_entry_change();
2442 return;
2443 }
2444 let repl_for_inval: Vec<Vec<u8>> = replacements
2445 .iter()
2446 .filter(|e| e.stage() == 0)
2447 .map(|e| e.path.clone())
2448 .collect();
2449 self.merge_sorted_replacements_into_entries(replacements);
2450 if repl_for_inval.len() >= BULK_MERGE_MIN {
2451 self.cache_tree = None;
2452 self.cache_tree_root = None;
2453 self.untracked_cache = None;
2454 self.touch_fsmonitor_after_entry_change();
2455 return;
2456 }
2457 for path in repl_for_inval {
2458 if let Ok(p) = std::str::from_utf8(&path) {
2459 self.invalidate_untracked_cache_for_path(p);
2460 }
2461 self.invalidate_cache_tree_for_path(&path);
2462 }
2463 self.touch_fsmonitor_after_entry_change();
2464 }
2465
2466 pub fn remove_paths_and_insert<'a>(
2471 &mut self,
2472 paths: impl IntoIterator<Item = &'a [u8]>,
2473 replacements: impl IntoIterator<Item = IndexEntry>,
2474 ) {
2475 let mut replacements: Vec<IndexEntry> = replacements.into_iter().collect();
2476 if replacements.is_empty() {
2477 self.remove_paths(paths);
2478 return;
2479 }
2480 replacements.sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
2481 dedup_sorted_replacement_entries(&mut replacements);
2482
2483 let mut paths: Vec<&[u8]> = paths.into_iter().collect();
2484 if paths.is_empty() {
2485 self.merge_replacements_into_sorted_index(replacements);
2486 return;
2487 }
2488 paths.sort_unstable_by(|a, b| Self::cmp_paths(a, b));
2489 paths.dedup_by(|a, b| a == b);
2490
2491 let mut invalidated_paths: Vec<Vec<u8>> = Vec::new();
2492 let mut kept = Vec::with_capacity(self.entries.len());
2493
2494 if self.entries_sorted {
2495 let mut path_idx = 0usize;
2496 for entry in self.entries.drain(..) {
2497 let ep = entry.path.as_slice();
2498 while path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_lt() {
2499 path_idx += 1;
2500 }
2501 let remove = path_idx < paths.len() && Self::cmp_paths(paths[path_idx], ep).is_eq();
2502 if remove {
2503 if entry.stage() != 0 {
2504 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, &entry);
2505 }
2506 if invalidated_paths.last().is_none_or(|p| p.as_slice() != ep) {
2507 invalidated_paths.push(entry.path.clone());
2508 }
2509 } else {
2510 kept.push(entry);
2511 }
2512 }
2513 let repl_for_inval = replacements
2514 .iter()
2515 .filter(|e| e.stage() == 0)
2516 .map(|e| e.path.clone())
2517 .collect::<Vec<_>>();
2518 self.entries = merge_sorted_index_entries(kept, replacements);
2519 self.entries_sorted = true;
2520 for path in invalidated_paths {
2521 self.invalidate_after_path_removal(&path);
2522 }
2523 for path in repl_for_inval {
2524 if let Ok(p) = std::str::from_utf8(&path) {
2525 self.invalidate_untracked_cache_for_path(p);
2526 }
2527 self.invalidate_cache_tree_for_path(&path);
2528 }
2529 self.touch_fsmonitor_after_entry_change();
2530 return;
2531 }
2532 self.remove_paths(paths);
2533 for entry in replacements {
2534 self.add_or_replace(entry);
2535 }
2536 }
2537
2538 pub fn add_or_replace(&mut self, entry: IndexEntry) {
2540 let path = entry.path.clone();
2541 let stage = entry.stage();
2542 let mut inserted_stage0 = false;
2543 if self.entries_sorted {
2544 let result = self.entries.binary_search_by(|e| {
2545 e.path
2546 .as_slice()
2547 .cmp(path.as_slice())
2548 .then_with(|| e.stage().cmp(&stage))
2549 });
2550 match result {
2551 Ok(pos) => {
2552 let mut e = entry;
2553 e.base_index_pos = self.entries[pos].base_index_pos;
2554 self.entries[pos] = e;
2555 }
2556 Err(pos) => {
2557 self.entries.insert(pos, entry);
2558 inserted_stage0 = stage == 0;
2559 }
2560 }
2561 } else if let Some(pos) = self
2562 .entries
2563 .iter()
2564 .position(|e| e.path == path && e.stage() == stage)
2565 {
2566 let mut e = entry;
2567 e.base_index_pos = self.entries[pos].base_index_pos;
2568 self.entries[pos] = e;
2569 } else {
2570 self.entries.push(entry);
2571 inserted_stage0 = stage == 0;
2572 }
2573 if inserted_stage0 {
2574 if let Ok(p) = std::str::from_utf8(&path) {
2575 self.invalidate_untracked_cache_for_path(p);
2576 }
2577 }
2578 if stage == 0 {
2579 self.invalidate_cache_tree_for_path(&path);
2580 }
2581 self.touch_fsmonitor_after_entry_change();
2582 }
2583
2584 #[must_use]
2586 pub fn has_unmerged_entries(&self) -> bool {
2587 self.entries.iter().any(|e| e.stage() != 0)
2588 }
2589
2590 pub fn stage_file(&mut self, entry: IndexEntry) {
2594 let path = entry.path.clone();
2595 if self.entries_sorted {
2596 let (start, end) = self.path_run_range_sorted(&path);
2597 for i in (start..end).rev() {
2598 if self.entries[i].stage() != 0 {
2599 resolve_undo::record_resolve_undo_for_entry(
2600 &mut self.resolve_undo,
2601 &self.entries[i],
2602 );
2603 self.entries.remove(i);
2604 }
2605 }
2606 } else {
2607 for e in &self.entries {
2608 if e.path == path && e.stage() != 0 {
2609 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2610 }
2611 }
2612 self.entries.retain(|e| e.path != path || e.stage() == 0);
2613 }
2614 self.add_or_replace(entry);
2615 }
2616
2617 pub fn clear_resolve_undo(&mut self) {
2619 self.resolve_undo = None;
2620 }
2621
2622 pub fn take_resolve_undo_record(&mut self, path: &[u8]) -> Option<ResolveUndoRecord> {
2624 let map = self.resolve_undo.as_mut()?;
2625 let ru = map.remove(path)?;
2626 if map.is_empty() {
2627 self.resolve_undo = None;
2628 }
2629 Some(ru)
2630 }
2631
2632 pub fn install_unmerged_from_resolve_undo(&mut self, path: &[u8], record: &ResolveUndoRecord) {
2634 if self.entries_sorted {
2635 let (start, end) = self.path_run_range_sorted(path);
2636 if start < end {
2637 self.entries.drain(start..end);
2638 }
2639 } else {
2640 self.entries.retain(|e| e.path != path);
2641 }
2642 for stage in 1u8..=3u8 {
2643 let i = (stage - 1) as usize;
2644 if record.modes[i] == 0 {
2645 continue;
2646 }
2647 let entry = IndexEntry {
2648 ctime_sec: 0,
2649 ctime_nsec: 0,
2650 mtime_sec: 0,
2651 mtime_nsec: 0,
2652 dev: 0,
2653 ino: 0,
2654 mode: record.modes[i],
2655 uid: 0,
2656 gid: 0,
2657 size: 0,
2658 oid: record.oids[i],
2659 flags: path.len().min(0xFFF) as u16 | ((stage as u16) << 12),
2660 flags_extended: None,
2661 path: path.to_vec(),
2662 base_index_pos: 0,
2663 };
2664 self.add_or_replace(entry);
2665 }
2666 }
2667
2668 pub fn unmerge_path_from_resolve_undo(&mut self, path: &[u8]) -> bool {
2672 let Some(record) = self.take_resolve_undo_record(path) else {
2673 return false;
2674 };
2675 self.install_unmerged_from_resolve_undo(path, &record);
2676 if let Ok(p) = std::str::from_utf8(path) {
2683 self.invalidate_untracked_cache_for_path(p);
2684 }
2685 self.invalidate_cache_tree_for_path(path);
2686 true
2687 }
2688
2689 pub fn remove(&mut self, path: &[u8]) -> bool {
2693 if self.entries_sorted {
2694 let (start, end) = self.path_run_range_sorted(path);
2695 if start < end {
2696 self.record_resolve_undo_for_run(start, end);
2697 self.entries.drain(start..end);
2698 self.invalidate_after_path_removal(path);
2699 self.touch_fsmonitor_after_entry_change();
2700 return true;
2701 }
2702 if !self.entries.iter().any(|e| e.path == path) {
2703 return false;
2704 }
2705 self.entries_sorted = false;
2706 }
2707 self.record_resolve_undo_for_path(path);
2708 let before = self.entries.len();
2709 self.entries.retain(|e| e.path != path);
2710 if self.entries.len() == before {
2711 return false;
2712 }
2713 self.invalidate_after_path_removal(path);
2714 self.touch_fsmonitor_after_entry_change();
2715 true
2716 }
2717
2718 pub fn remove_path_all_stages(&mut self, path: &[u8]) -> bool {
2722 self.remove(path)
2723 }
2724
2725 pub fn invalidate_untracked_cache_for_path(&mut self, path: &str) {
2727 if self.defer_untracked_cache_invalidation {
2728 return;
2729 }
2730 if let Some(uc) = self.untracked_cache.as_mut() {
2731 untracked_cache::invalidate_path(uc, path);
2732 }
2733 }
2734
2735 pub fn remove_descendants_under_path(&mut self, path: &str) {
2741 let prefix = path.as_bytes();
2742 if prefix.is_empty() {
2743 return;
2744 }
2745 let plen = prefix.len();
2746 if self.entries_sorted {
2747 let (start, end) = self.descendant_run_range_sorted(path);
2748 if start < end {
2749 self.record_resolve_undo_for_run(start, end);
2750 self.entries.drain(start..end);
2751 self.invalidate_untracked_cache_for_path(path);
2752 self.invalidate_cache_tree_for_path(path.as_bytes());
2753 return;
2754 }
2755 let any = self.entries.iter().any(|e| {
2756 let ep = e.path.as_slice();
2757 ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/'
2758 });
2759 if !any {
2760 return;
2761 }
2762 self.entries_sorted = false;
2763 }
2764 {
2765 let had_descendant = self.entries.iter().any(|e| {
2766 let ep = e.path.as_slice();
2767 ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/'
2768 });
2769 for e in self.entries.iter() {
2770 let ep = e.path.as_slice();
2771 if ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/' && e.stage() != 0 {
2772 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
2773 }
2774 }
2775 self.entries.retain(|e| {
2776 let ep = e.path.as_slice();
2777 if ep.len() <= plen {
2778 return true;
2779 }
2780 if !ep.starts_with(prefix) {
2781 return true;
2782 }
2783 ep[plen] != b'/'
2784 });
2785 if !had_descendant {
2786 return;
2787 }
2788 }
2789 self.invalidate_untracked_cache_for_path(path);
2790 self.invalidate_cache_tree_for_path(path.as_bytes());
2791 }
2792
2793 pub fn set_cache_tree(&mut self, cache_tree: CacheTreeNode) {
2795 self.cache_tree_root = cache_tree.oid.filter(|_| cache_tree.entry_count >= 0);
2796 self.cache_tree = Some(cache_tree);
2797 }
2798
2799 pub fn clear_cache_tree(&mut self) {
2801 self.cache_tree_root = None;
2802 self.cache_tree = None;
2803 }
2804
2805 pub fn invalidate_cache_tree_for_path(&mut self, path: &[u8]) {
2815 let Some(root) = self.cache_tree.as_mut() else {
2816 self.cache_tree_root = None;
2817 return;
2818 };
2819 self.cache_tree_root = None;
2820 Self::do_invalidate_cache_tree_path(root, path);
2821 }
2822
2823 fn do_invalidate_cache_tree_path(node: &mut CacheTreeNode, path: &[u8]) {
2826 node.invalidate();
2827 let slash = path.iter().position(|&b| b == b'/');
2828 match slash {
2829 None => {
2831 if !path.is_empty() {
2832 node.children.retain(|child| child.name != path);
2833 }
2834 }
2835 Some(idx) => {
2837 let (component, rest) = (&path[..idx], &path[idx + 1..]);
2838 if let Some(child) = node
2839 .children
2840 .iter_mut()
2841 .find(|child| child.name == component)
2842 {
2843 Self::do_invalidate_cache_tree_path(child, rest);
2844 }
2845 }
2846 }
2847 }
2848
2849 #[must_use]
2851 pub fn format_cache_tree_dump(&self) -> String {
2852 let Some(root) = self.cache_tree.as_ref() else {
2853 return String::new();
2854 };
2855 let mut out = String::new();
2856 format_cache_tree_node(root, "", &mut out);
2857 out
2858 }
2859
2860 pub fn dump_cache_tree(&self, odb: &Odb) -> Result<String> {
2876 let Some(it_root) = self.cache_tree.as_ref() else {
2877 return Ok(String::new());
2878 };
2879 let ref_root = crate::write_tree::build_cache_tree_from_index(odb, self)?;
2880 let mut out = String::new();
2881 dump_cache_tree_pair(it_root, &ref_root, "", &mut out);
2882 Ok(out)
2883 }
2884
2885 pub fn sort(&mut self) {
2887 self.ensure_canonical_order();
2888 }
2889
2890 pub fn ensure_canonical_order(&mut self) {
2895 if self.entries_sorted && self.entries_are_sorted() {
2896 return;
2897 }
2898 self.sort_entries_canonical();
2899 }
2900
2901 fn sort_entries_canonical(&mut self) {
2902 self.entries
2903 .sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
2904 self.entries_sorted = true;
2905 }
2906
2907 pub fn dedup_paths_keep_last(&mut self) {
2916 let mut seen: std::collections::HashSet<(Vec<u8>, u8)> = std::collections::HashSet::new();
2917 let mut kept: Vec<IndexEntry> = Vec::with_capacity(self.entries.len());
2918 for entry in self.entries.iter().rev() {
2920 if seen.insert((entry.path.clone(), entry.stage())) {
2921 kept.push(entry.clone());
2922 }
2923 }
2924 kept.reverse();
2925 self.entries = kept;
2926 self.entries_sorted = false;
2927 }
2928
2929 #[must_use]
2931 pub fn split_index_base_oid(&self) -> Option<ObjectId> {
2932 self.split_link.as_ref().map(|l| l.base_oid)
2933 }
2934
2935 #[must_use]
2937 pub fn get(&self, path: &[u8], stage: u8) -> Option<&IndexEntry> {
2938 self.find_entry_pos(path, stage)
2939 .map(|pos| &self.entries[pos])
2940 }
2941
2942 pub fn get_mut(&mut self, path: &[u8], stage: u8) -> Option<&mut IndexEntry> {
2944 let pos = self.find_entry_pos(path, stage)?;
2945 Some(&mut self.entries[pos])
2946 }
2947
2948 pub(crate) fn stage0_path_positions<'a>(
2953 &'a self,
2954 ) -> std::collections::HashMap<Cow<'a, str>, usize> {
2955 let stage0_count = self.entries.iter().filter(|e| e.stage() == 0).count();
2956 let mut positions = std::collections::HashMap::with_capacity(stage0_count);
2957 for (pos, entry) in self.entries.iter().enumerate() {
2958 if entry.stage() == 0 {
2959 let key = index_path_outcome_key(&entry.path);
2960 positions.entry(key).or_insert(pos);
2961 }
2962 }
2963 positions
2964 }
2965
2966 pub fn overlay_tree_on_index(
2985 &mut self,
2986 repo: &Repository,
2987 treeish: &str,
2988 prefix: &[u8],
2989 ) -> Result<()> {
2990 let oid = rev_parse::resolve_revision(repo, treeish)?;
2991 let tree_oid = repo.odb().peel_to_tree(oid)?.ok_or_else(|| {
2992 Error::ObjectNotFound(format!("cannot peel {oid} to tree for --with-tree"))
2993 })?;
2994 for e in self.entries.iter_mut() {
2995 if e.stage() != 0 {
2996 e.set_stage(3);
2997 }
2998 }
2999 self.sort();
3000 let has_stage1 = self.entries.iter().any(|e| e.stage() == 1);
3001 let mut appended: Vec<IndexEntry> = Vec::new();
3002 let protect = crate::path_validate::ProtectOptions::from_config(&*repo.config()?);
3003 read_tree_into_overlay(
3004 repo,
3005 &tree_oid,
3006 prefix,
3007 &[],
3008 has_stage1,
3009 protect,
3010 &mut appended,
3011 )?;
3012 for e in appended {
3013 self.add_or_replace(e);
3014 }
3015 if !has_stage1 {
3016 self.sort();
3017 }
3018 let mut last_stage0: Option<&[u8]> = None;
3019 for e in &mut self.entries {
3020 match e.stage() {
3021 0 => {
3022 last_stage0 = Some(e.path.as_slice());
3023 }
3024 1 if last_stage0.is_some_and(|p| p == e.path.as_slice()) => {
3025 e.set_overlay_tree_skip_output(true);
3026 }
3027 _ => {}
3028 }
3029 }
3030 Ok(())
3031 }
3032}
3033
3034#[allow(clippy::too_many_arguments)]
3035fn read_tree_into_overlay(
3036 repo: &Repository,
3037 tree_oid: &ObjectId,
3038 prefix: &[u8],
3039 rel_base: &[u8],
3040 use_replace_path: bool,
3041 protect: crate::path_validate::ProtectOptions,
3042 out: &mut Vec<IndexEntry>,
3043) -> Result<()> {
3044 let obj = repo.odb().read(tree_oid)?;
3045 if obj.kind != ObjectKind::Tree {
3046 return Err(Error::ObjectNotFound(format!(
3047 "object {tree_oid} is not a tree"
3048 )));
3049 }
3050 let entries = parse_tree(&obj.data)?;
3051 for TreeEntry { mode, name, oid } in entries {
3052 let mut path = rel_base.to_vec();
3053 if !path.is_empty() {
3054 path.push(b'/');
3055 }
3056 path.extend_from_slice(&name);
3057 if !prefix_under_or_equal(prefix, &path) {
3058 continue;
3059 }
3060 crate::path_population::validate_tree_walk_entry(rel_base, &name, mode, protect)?;
3061 if mode == MODE_TREE {
3062 read_tree_into_overlay(repo, &oid, prefix, &path, use_replace_path, protect, out)?;
3063 continue;
3064 }
3065 if mode == MODE_GITLINK {
3066 continue;
3067 }
3068 let entry = synthetic_stage1_index_entry(mode, &path, oid);
3069 if use_replace_path {
3070 if let Some(pos) = out.iter().position(|e| e.path == path && e.stage() == 1) {
3071 out[pos] = entry;
3072 } else {
3073 out.push(entry);
3074 }
3075 } else {
3076 out.push(entry);
3077 }
3078 }
3079 Ok(())
3080}
3081
3082fn dedup_sorted_replacement_entries(replacements: &mut Vec<IndexEntry>) {
3084 if replacements.len() <= 1 {
3085 return;
3086 }
3087 let mut out: Vec<IndexEntry> = Vec::with_capacity(replacements.len());
3088 for entry in replacements.drain(..) {
3089 match out.last_mut() {
3090 Some(last) if last.path == entry.path && last.stage() == entry.stage() => {
3091 *last = entry;
3092 }
3093 _ => out.push(entry),
3094 }
3095 }
3096 *replacements = out;
3097}
3098
3099fn index_entry_path_stage_cmp(a: &IndexEntry, b: &IndexEntry) -> std::cmp::Ordering {
3100 a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage()))
3101}
3102
3103fn merge_sorted_index_entries(
3107 mut kept: Vec<IndexEntry>,
3108 mut insert: Vec<IndexEntry>,
3109) -> Vec<IndexEntry> {
3110 if insert.is_empty() {
3111 return kept;
3112 }
3113 if kept.is_empty() {
3114 return insert;
3115 }
3116 kept.reserve(insert.len());
3117 let mid = kept.len();
3118 kept.append(&mut insert);
3119 merge_sorted_index_adjacent_halves(&mut kept, mid);
3120 kept
3121}
3122
3123fn merge_sorted_index_adjacent_halves(v: &mut Vec<IndexEntry>, mid: usize) {
3125 let n = v.len();
3126 if mid == 0 || mid >= n {
3127 return;
3128 }
3129 let right_len = n - mid;
3130 let smaller_is_right = right_len <= mid;
3131 let mut aux = if smaller_is_right {
3132 v.drain(mid..).collect::<Vec<_>>()
3133 } else {
3134 v.drain(..mid).collect::<Vec<_>>()
3135 };
3136 let left_len = if smaller_is_right { v.len() } else { aux.len() };
3137 let pad = aux.len();
3138 v.reserve(pad);
3139 for _ in 0..pad {
3140 v.push(merge_padding_index_entry());
3141 }
3142 let total = v.len();
3143 merge_backward_into_place(v, left_len, total, &mut aux, smaller_is_right);
3144}
3145
3146fn merge_padding_index_entry() -> IndexEntry {
3148 IndexEntry {
3149 ctime_sec: 0,
3150 ctime_nsec: 0,
3151 mtime_sec: 0,
3152 mtime_nsec: 0,
3153 dev: 0,
3154 ino: 0,
3155 mode: 0,
3156 uid: 0,
3157 gid: 0,
3158 size: 0,
3159 oid: ObjectId::zero(),
3160 flags: 0,
3161 flags_extended: None,
3162 path: Vec::new(),
3163 base_index_pos: 0,
3164 }
3165}
3166
3167fn merge_backward_into_place(
3169 v: &mut Vec<IndexEntry>,
3170 left_len: usize,
3171 total: usize,
3172 aux: &mut Vec<IndexEntry>,
3173 smaller_is_right: bool,
3174) {
3175 let pad = aux.len();
3176 let mut li = left_len;
3177 let mut ri = if smaller_is_right {
3178 aux.len()
3179 } else {
3180 total.saturating_sub(pad)
3181 };
3182 let mut k = total;
3183 while li > 0 && ri > 0 {
3184 k -= 1;
3185 let ord = if smaller_is_right {
3186 index_entry_path_stage_cmp(&v[li - 1], &aux[ri - 1])
3187 } else {
3188 index_entry_path_stage_cmp(&aux[li - 1], &v[ri - 1])
3189 };
3190 match ord {
3191 std::cmp::Ordering::Greater => {
3192 if smaller_is_right {
3193 let entry = std::mem::replace(&mut v[li - 1], merge_padding_index_entry());
3194 drop(std::mem::replace(&mut v[k], entry));
3195 li -= 1;
3196 } else {
3197 let entry = std::mem::replace(&mut aux[li - 1], merge_padding_index_entry());
3198 li -= 1;
3199 drop(std::mem::replace(&mut v[k], entry));
3200 }
3201 }
3202 std::cmp::Ordering::Equal => {
3203 let base = if smaller_is_right {
3204 v[li - 1].base_index_pos
3205 } else {
3206 aux[li - 1].base_index_pos
3207 };
3208 let mut entry = if smaller_is_right {
3209 let Some(e) = aux.pop() else {
3210 break;
3211 };
3212 ri -= 1;
3213 e
3214 } else {
3215 ri -= 1;
3216 std::mem::replace(&mut v[ri], merge_padding_index_entry())
3217 };
3218 entry.base_index_pos = base;
3219 if smaller_is_right {
3220 drop(std::mem::replace(
3221 &mut v[li - 1],
3222 merge_padding_index_entry(),
3223 ));
3224 li -= 1;
3225 } else {
3226 drop(std::mem::replace(
3227 &mut aux[li - 1],
3228 merge_padding_index_entry(),
3229 ));
3230 li -= 1;
3231 }
3232 drop(std::mem::replace(&mut v[k], entry));
3233 }
3234 std::cmp::Ordering::Less => {
3235 if smaller_is_right {
3236 let Some(entry) = aux.pop() else {
3237 break;
3238 };
3239 ri -= 1;
3240 drop(std::mem::replace(&mut v[k], entry));
3241 } else {
3242 ri -= 1;
3243 let entry = std::mem::replace(&mut v[ri], merge_padding_index_entry());
3244 drop(std::mem::replace(&mut v[k], entry));
3245 }
3246 }
3247 }
3248 }
3249 while ri > 0 {
3250 k -= 1;
3251 if smaller_is_right {
3252 let Some(entry) = aux.pop() else {
3253 break;
3254 };
3255 ri -= 1;
3256 drop(std::mem::replace(&mut v[k], entry));
3257 } else {
3258 ri -= 1;
3259 let entry = std::mem::replace(&mut v[ri], merge_padding_index_entry());
3260 drop(std::mem::replace(&mut v[k], entry));
3261 }
3262 }
3263 if !smaller_is_right && li > 0 {
3264 for i in 0..li {
3265 let entry = std::mem::replace(&mut aux[i], merge_padding_index_entry());
3266 drop(std::mem::replace(&mut v[i], entry));
3267 }
3268 }
3269 let tail_len = total - k;
3270 if li < k && tail_len > 0 {
3271 for offset in 0..tail_len {
3272 let entry = std::mem::replace(&mut v[k + offset], merge_padding_index_entry());
3273 drop(std::mem::replace(&mut v[li + offset], entry));
3274 }
3275 }
3276 v.truncate(li + tail_len);
3277}
3278
3279fn prefix_under_or_equal(prefix: &[u8], path: &[u8]) -> bool {
3280 if prefix.is_empty() {
3281 return true;
3282 }
3283 if path == prefix {
3284 return true;
3285 }
3286 path.len() > prefix.len() && path.starts_with(prefix) && path[prefix.len()] == b'/'
3287}
3288
3289fn synthetic_stage1_index_entry(mode: u32, path: &[u8], oid: ObjectId) -> IndexEntry {
3290 let path_len = path.len().min(0xFFF) as u16;
3291 let flags = (1u16 << 12) | path_len;
3292 IndexEntry {
3293 ctime_sec: 0,
3294 ctime_nsec: 0,
3295 mtime_sec: 0,
3296 mtime_nsec: 0,
3297 dev: 0,
3298 ino: 0,
3299 mode,
3300 uid: 0,
3301 gid: 0,
3302 size: 0,
3303 oid,
3304 flags,
3305 flags_extended: None,
3306 path: path.to_vec(),
3307 base_index_pos: 0,
3308 }
3309}
3310
3311fn config_truthy(raw: Option<&str>) -> bool {
3312 let Some(val) = raw else {
3313 return false;
3314 };
3315 let lowered = val.trim().to_lowercase();
3316 matches!(lowered.as_str(), "true" | "yes" | "1" | "on")
3317}
3318
3319pub(crate) fn index_skip_hash_for_write(config: Option<&ConfigSet>) -> bool {
3324 let Some(config) = config else {
3325 return false;
3326 };
3327 let many_files = config
3328 .get_bool("feature.manyFiles")
3329 .and_then(|r| r.ok())
3330 .unwrap_or(false);
3331 if many_files {
3332 if let Some(Ok(false)) = config.get_bool("index.skipHash") {
3333 return false;
3334 }
3335 if let Some(Ok(false)) = config.get_bool("index.skiphash") {
3336 return false;
3337 }
3338 return true;
3339 }
3340 for key in ["index.skipHash", "index.skiphash"] {
3341 if let Some(Ok(true)) = config.get_bool(key) {
3342 return true;
3343 }
3344 }
3345 false
3346}
3347
3348fn trim_trailing_slash_bytes(path: &[u8]) -> &[u8] {
3349 path.strip_suffix(b"/").unwrap_or(path)
3350}
3351
3352fn path_under_prefix(path: &[u8], prefix: &[u8]) -> bool {
3353 if path == prefix {
3354 return true;
3355 }
3356 if prefix.is_empty() {
3357 return true;
3358 }
3359 path.len() > prefix.len() && path.starts_with(prefix) && path[prefix.len()] == b'/'
3360}
3361
3362fn directory_in_cone(dir_path: &str, patterns: &[String], cone_mode: bool) -> bool {
3363 let dir = dir_path.trim_end_matches('/');
3372 if dir.is_empty() {
3373 return true;
3374 }
3375 let with_slash = format!("{dir}/");
3376 crate::sparse_checkout::path_matches_sparse_patterns(&with_slash, patterns, cone_mode)
3377}
3378
3379fn collect_directory_prefixes(path: &[u8], out: &mut BTreeSet<Vec<u8>>) {
3380 for (i, &b) in path.iter().enumerate() {
3381 if b == b'/' {
3382 out.insert(path[..i].to_vec());
3383 }
3384 }
3385}
3386
3387fn tree_oid_for_prefix(odb: &Odb, root_tree: &ObjectId, prefix: &[u8]) -> Result<Option<ObjectId>> {
3388 if prefix.is_empty() {
3389 return Ok(Some(*root_tree));
3390 }
3391 let pref_str = String::from_utf8_lossy(prefix);
3392 let components: Vec<&str> = pref_str.split('/').filter(|c| !c.is_empty()).collect();
3393 let mut current = *root_tree;
3394 for comp in components {
3395 let obj = odb.read(¤t)?;
3396 if obj.kind != ObjectKind::Tree {
3397 return Ok(None);
3398 }
3399 let entries = parse_tree(&obj.data)?;
3400 let mut next = None;
3401 for e in entries {
3402 if e.name == comp.as_bytes() {
3403 if e.mode == MODE_TREE {
3404 next = Some(e.oid);
3405 }
3406 break;
3407 }
3408 }
3409 current = match next {
3410 Some(o) => o,
3411 None => return Ok(None),
3412 };
3413 }
3414 Ok(Some(current))
3415}
3416
3417fn collect_sparse_aware_expected_blobs(
3420 odb: &Odb,
3421 tree_oid: &ObjectId,
3422 prefix: &[u8],
3423 patterns: &[String],
3424 cone_mode: bool,
3425 entries: &[IndexEntry],
3426 protect: crate::path_validate::ProtectOptions,
3427) -> Result<Vec<IndexEntry>> {
3428 let mut out = Vec::new();
3429 walk_sparse_aware(
3430 odb, tree_oid, prefix, patterns, cone_mode, entries, protect, &mut out,
3431 )?;
3432 Ok(out)
3433}
3434
3435#[allow(clippy::too_many_arguments)]
3436fn walk_sparse_aware(
3437 odb: &Odb,
3438 tree_oid: &ObjectId,
3439 prefix: &[u8],
3440 patterns: &[String],
3441 cone_mode: bool,
3442 entries: &[IndexEntry],
3443 protect: crate::path_validate::ProtectOptions,
3444 out: &mut Vec<IndexEntry>,
3445) -> Result<()> {
3446 let obj = odb.read(tree_oid)?;
3447 if obj.kind != ObjectKind::Tree {
3448 return Err(Error::IndexError(format!("expected tree at {}", tree_oid)));
3449 }
3450 let tree_entries = parse_tree(&obj.data)?;
3451 for te in tree_entries {
3452 let path = if prefix.is_empty() {
3453 te.name.clone()
3454 } else {
3455 let mut p = prefix.to_vec();
3456 p.push(b'/');
3457 p.extend_from_slice(&te.name);
3458 p
3459 };
3460 crate::path_population::validate_tree_walk_entry(&path, &te.name, te.mode, protect)?;
3461 if te.mode == MODE_TREE {
3462 let path_slash = {
3463 let mut p = path.clone();
3464 p.push(b'/');
3465 p
3466 };
3467 if entries.iter().any(|e| {
3468 e.stage() == 0
3469 && e.is_sparse_directory_placeholder()
3470 && e.path == path_slash
3471 && e.oid == te.oid
3472 }) {
3473 continue;
3474 }
3475 walk_sparse_aware(
3476 odb, &te.oid, &path, patterns, cone_mode, entries, protect, out,
3477 )?;
3478 } else {
3479 let path_len = path.len().min(0xFFF) as u16;
3480 let path_str = String::from_utf8_lossy(&path);
3481 if crate::sparse_checkout::path_matches_sparse_patterns(&path_str, patterns, cone_mode)
3482 {
3483 continue;
3484 }
3485 let mut e = IndexEntry {
3486 ctime_sec: 0,
3487 ctime_nsec: 0,
3488 mtime_sec: 0,
3489 mtime_nsec: 0,
3490 dev: 0,
3491 ino: 0,
3492 mode: te.mode,
3493 uid: 0,
3494 gid: 0,
3495 size: 0,
3496 oid: te.oid,
3497 flags: path_len,
3498 flags_extended: Some(0),
3499 path,
3500 base_index_pos: 0,
3501 };
3502 e.set_skip_worktree(true);
3503 out.push(e);
3504 }
3505 }
3506 Ok(())
3507}
3508
3509fn flatten_tree_blobs(
3510 odb: &Odb,
3511 tree_oid: &ObjectId,
3512 prefix: &[u8],
3513 protect: crate::path_validate::ProtectOptions,
3514) -> Result<Vec<IndexEntry>> {
3515 let obj = odb.read(tree_oid)?;
3516 if obj.kind != ObjectKind::Tree {
3517 return Err(Error::IndexError(format!("expected tree at {}", tree_oid)));
3518 }
3519 let entries = parse_tree(&obj.data)?;
3520 let mut out = Vec::new();
3521 for te in entries {
3522 let path = if prefix.is_empty() {
3523 te.name.clone()
3524 } else {
3525 let mut p = prefix.to_vec();
3526 p.push(b'/');
3527 p.extend_from_slice(&te.name);
3528 p
3529 };
3530 crate::path_population::validate_tree_walk_entry(prefix, &te.name, te.mode, protect)?;
3531 if te.mode == MODE_TREE {
3532 let sub = flatten_tree_blobs(odb, &te.oid, &path, protect)?;
3533 out.extend(sub);
3534 } else {
3535 let path_len = path.len().min(0xFFF) as u16;
3536 let mut e = IndexEntry {
3537 ctime_sec: 0,
3538 ctime_nsec: 0,
3539 mtime_sec: 0,
3540 mtime_nsec: 0,
3541 dev: 0,
3542 ino: 0,
3543 mode: te.mode,
3544 uid: 0,
3545 gid: 0,
3546 size: 0,
3547 oid: te.oid,
3548 flags: path_len,
3549 flags_extended: Some(0),
3550 path,
3551 base_index_pos: 0,
3552 };
3553 e.set_skip_worktree(true);
3554 out.push(e);
3555 }
3556 }
3557 Ok(out)
3558}
3559
3560fn lockfile_pid_enabled(index_path: &Path, config: Option<&ConfigSet>) -> bool {
3561 let git_dir = match index_path.parent() {
3562 Some(dir) => dir,
3563 None => return false,
3564 };
3565
3566 let cfg = config.cloned().unwrap_or_else(|| {
3567 ConfigSet::load(
3568 &crate::environment::Environment::empty(),
3569 Some(git_dir),
3570 true,
3571 )
3572 .unwrap_or_default()
3573 });
3574 cfg.get_bool("core.lockfilepid")
3575 .and_then(|res| res.ok())
3576 .unwrap_or(false)
3577}
3578
3579fn write_lock_pid_file(pid_path: &Path) -> io::Result<()> {
3580 use std::io::Write as _;
3581 let mut file = fs::OpenOptions::new()
3582 .write(true)
3583 .create(true)
3584 .truncate(true)
3585 .open(pid_path)?;
3586 writeln!(file, "pid {}", std::process::id())?;
3587 Ok(())
3588}
3589
3590pub struct IndexLock {
3597 pub(crate) index_path: PathBuf,
3598 pub(crate) lock_path: PathBuf,
3599 pub(crate) pid_path: PathBuf,
3600 pub(crate) file: fs::File,
3601 pub(crate) wrote_pid: bool,
3602 pub(crate) committed: bool,
3603}
3604
3605impl IndexLock {
3606 pub(crate) fn write_index_contents(&mut self, body: &[u8], checksum: &[u8]) -> Result<()> {
3608 self.file.write_all(body).map_err(Error::Io)?;
3609 self.file.write_all(checksum).map_err(Error::Io)?;
3610 drop(std::mem::replace(
3611 &mut self.file,
3612 fs::OpenOptions::new()
3613 .write(true)
3614 .open(&self.lock_path)
3615 .map_err(Error::Io)?,
3616 ));
3617 Ok(())
3618 }
3619
3620 pub(crate) fn commit_replace_index(&mut self) -> Result<()> {
3622 fs::rename(&self.lock_path, &self.index_path).map_err(Error::Io)?;
3623 self.committed = true;
3624 if self.wrote_pid {
3625 let _ = fs::remove_file(&self.pid_path);
3626 }
3627 Ok(())
3628 }
3629
3630 pub fn acquire(index_path: &Path, config: Option<&ConfigSet>) -> Result<Self> {
3638 let lock_path = index_path.with_extension("lock");
3639 let pid_path = pid_path_for_lock(&lock_path);
3640 let lockfile_pid_enabled = lockfile_pid_enabled(index_path, config);
3641
3642 use crate::lockfile::{LockFile, LockWait};
3643 let wait = LockWait::files_ref_lock(config);
3644 let (lock_path, file) =
3645 LockFile::acquire(&lock_path, wait).and_then(LockFile::into_open_file)?;
3646
3647 let mut wrote_pid = false;
3648 if lockfile_pid_enabled {
3649 write_lock_pid_file(&pid_path).map_err(|e| {
3650 let _ = fs::remove_file(&lock_path);
3651 Error::Io(e)
3652 })?;
3653 wrote_pid = true;
3654 }
3655
3656 Ok(Self {
3657 index_path: index_path.to_path_buf(),
3658 lock_path,
3659 pid_path,
3660 file,
3661 wrote_pid,
3662 committed: false,
3663 })
3664 }
3665}
3666
3667impl Drop for IndexLock {
3668 fn drop(&mut self) {
3669 if self.committed {
3670 return;
3671 }
3672 let _ = fs::remove_file(&self.lock_path);
3673 if self.wrote_pid {
3674 let _ = fs::remove_file(&self.pid_path);
3675 }
3676 }
3677}
3678
3679fn hash_index_body(algo: HashAlgo, body: &[u8]) -> Vec<u8> {
3681 algo.checksum_digest(body)
3682}
3683
3684fn write_index_body_and_trailer(
3686 file: &mut fs::File,
3687 algo: HashAlgo,
3688 skip_hash: bool,
3689 body: &[u8],
3690) -> io::Result<()> {
3691 use std::io::BufWriter;
3692 let mut out = BufWriter::with_capacity(256 * 1024, file);
3693 if skip_hash {
3694 out.write_all(body)?;
3695 out.write_all(&vec![0u8; algo.len()])?;
3696 return out.flush();
3697 }
3698 let mut hasher = algo.checksum_hasher();
3699 for chunk in body.chunks(256 * 1024) {
3700 hasher.update(chunk);
3701 out.write_all(chunk)?;
3702 }
3703 out.write_all(&hasher.finalize())?;
3704 out.flush()
3705}
3706
3707impl Index {
3708 fn estimate_body_capacity(&self, write_version: u32, config: Option<&ConfigSet>) -> usize {
3710 use crate::index_eoie_ieot::record_end_of_index_entries;
3711 let hash_len = self.hash_algo.len();
3712 let mut entries_bytes = 0usize;
3713 for entry in &self.entries {
3714 let mut n = 62usize
3715 .saturating_add(hash_len)
3716 .saturating_add(entry.path.len());
3717 if write_version >= 3
3718 && entry
3719 .flags_extended
3720 .map(IndexEntry::disk_flags_extended)
3721 .filter(|fe| *fe != 0)
3722 .is_some()
3723 {
3724 n += 2;
3725 }
3726 if write_version == 4 {
3727 n += 12;
3728 } else {
3729 n = (n + 7) & !7;
3730 }
3731 entries_bytes = entries_bytes.saturating_add(n);
3732 }
3733 let mut extra = 256usize;
3734 if self.cache_tree.is_some() {
3735 extra += 4096;
3736 }
3737 if self.untracked_cache.is_some() {
3738 extra += 4096;
3739 }
3740 if self.fsmonitor_last_update.is_some() {
3741 extra += 512;
3742 }
3743 if record_end_of_index_entries(config) {
3744 extra += 64;
3745 }
3746 12 + entries_bytes + extra
3747 }
3748}
3749
3750fn append_index_extension(
3751 out: &mut Vec<u8>,
3752 eoie_headers: &mut Option<Vec<u8>>,
3753 signature: u32,
3754 payload: &[u8],
3755) {
3756 let size = payload.len() as u32;
3757 out.extend_from_slice(&signature.to_be_bytes());
3758 out.extend_from_slice(&size.to_be_bytes());
3759 if let Some(headers) = eoie_headers {
3760 headers.extend_from_slice(&signature.to_be_bytes());
3761 headers.extend_from_slice(&size.to_be_bytes());
3762 }
3763 out.extend_from_slice(payload);
3764}
3765
3766fn detect_index_hash_algo(data: &[u8]) -> HashAlgo {
3771 let sha256_len = HashAlgo::Sha256.len();
3774 if data.len() > sha256_len + 12 {
3775 let (body, checksum) = data.split_at(data.len() - sha256_len);
3776 if checksum.iter().any(|&b| b != 0) && hash_index_body(HashAlgo::Sha256, body) == checksum {
3777 return HashAlgo::Sha256;
3778 }
3779 }
3780 HashAlgo::Sha1
3781}
3782
3783pub(crate) fn parse_entry(
3787 data: &[u8],
3788 version: u32,
3789 prev_path: &[u8],
3790 hash_len: usize,
3791) -> Result<(IndexEntry, usize)> {
3792 if data.len() < 40 + hash_len + 2 {
3794 return Err(Error::IndexError("entry too short".to_owned()));
3795 }
3796
3797 let mut pos = 0;
3798
3799 macro_rules! read_u32 {
3800 () => {{
3801 let v = u32::from_be_bytes(
3802 data[pos..pos + 4]
3803 .try_into()
3804 .map_err(|_| Error::IndexError("truncated u32".to_owned()))?,
3805 );
3806 pos += 4;
3807 v
3808 }};
3809 }
3810
3811 let ctime_sec = read_u32!();
3812 let ctime_nsec = read_u32!();
3813 let mtime_sec = read_u32!();
3814 let mtime_nsec = read_u32!();
3815 let dev = read_u32!();
3816 let ino = read_u32!();
3817 let mode = read_u32!();
3818 let uid = read_u32!();
3819 let gid = read_u32!();
3820 let size = read_u32!();
3821
3822 let oid = ObjectId::from_bytes(&data[pos..pos + hash_len])?;
3823 pos += hash_len;
3824
3825 let flags = u16::from_be_bytes(
3826 data[pos..pos + 2]
3827 .try_into()
3828 .map_err(|_| Error::IndexError("truncated flags".to_owned()))?,
3829 );
3830 pos += 2;
3831
3832 let flags_extended = if version >= 3 && flags & 0x4000 != 0 {
3833 let fe = u16::from_be_bytes(
3834 data[pos..pos + 2]
3835 .try_into()
3836 .map_err(|_| Error::IndexError("truncated extended flags".to_owned()))?,
3837 );
3838 pos += 2;
3839 Some(fe)
3840 } else {
3841 None
3842 };
3843
3844 let path;
3845 if version == 4 {
3846 let (strip_len, varint_bytes) = read_varint(&data[pos..]);
3848 pos += varint_bytes;
3849 let nul = data[pos..]
3850 .iter()
3851 .position(|&b| b == 0)
3852 .ok_or_else(|| Error::IndexError("v4 entry path missing NUL".to_owned()))?;
3853 let suffix = &data[pos..pos + nul];
3854 pos += nul + 1;
3855 let keep = prev_path.len().saturating_sub(strip_len);
3856 let mut full_path = Vec::with_capacity(keep + suffix.len());
3857 full_path.extend_from_slice(&prev_path[..keep]);
3858 full_path.extend_from_slice(suffix);
3859 path = full_path;
3860 } else {
3861 let nul = data[pos..]
3863 .iter()
3864 .position(|&b| b == 0)
3865 .ok_or_else(|| Error::IndexError("entry path missing NUL terminator".to_owned()))?;
3866 path = data[pos..pos + nul].to_vec();
3867 pos += nul + 1;
3868 let entry_start = 0usize;
3869 let entry_len = pos - entry_start;
3870 let padded = (entry_len + 7) & !7;
3871 let padding = padded.saturating_sub(entry_len);
3872 pos += padding;
3873 }
3874
3875 Ok((
3876 IndexEntry {
3877 ctime_sec,
3878 ctime_nsec,
3879 mtime_sec,
3880 mtime_nsec,
3881 dev,
3882 ino,
3883 mode,
3884 uid,
3885 gid,
3886 size,
3887 oid,
3888 flags,
3889 flags_extended,
3890 path,
3891 base_index_pos: 0,
3892 },
3893 pos,
3894 ))
3895}
3896
3897fn write_varint(out: &mut Vec<u8>, mut value: usize) {
3901 loop {
3902 let mut b = (value & 0x7F) as u8;
3903 value >>= 7;
3904 if value != 0 {
3905 b |= 0x80;
3906 }
3907 out.push(b);
3908 if value == 0 {
3909 break;
3910 }
3911 }
3912}
3913
3914fn read_varint(data: &[u8]) -> (usize, usize) {
3915 let mut value: usize = 0;
3916 let mut shift = 0usize;
3917 let mut pos = 0;
3918 loop {
3919 if pos >= data.len() {
3920 break;
3921 }
3922 let byte = data[pos] as usize;
3923 pos += 1;
3924 value |= (byte & 0x7F) << shift;
3925 if byte & 0x80 == 0 {
3926 break;
3927 }
3928 shift += 7;
3929 if shift > 28 {
3931 break;
3932 }
3933 }
3934 (value, pos)
3935}
3936
3937fn serialize_entry_v4(entry: &IndexEntry, previous_path: &mut Vec<u8>, out: &mut Vec<u8>) {
3938 let write_u32 = |out: &mut Vec<u8>, v: u32| out.extend_from_slice(&v.to_be_bytes());
3939
3940 write_u32(out, entry.ctime_sec);
3941 write_u32(out, entry.ctime_nsec);
3942 write_u32(out, entry.mtime_sec);
3943 write_u32(out, entry.mtime_nsec);
3944 write_u32(out, entry.dev);
3945 write_u32(out, entry.ino);
3946 write_u32(out, entry.mode);
3947 write_u32(out, entry.uid);
3948 write_u32(out, entry.gid);
3949 write_u32(out, entry.size);
3950 out.extend_from_slice(entry.oid.as_bytes());
3951
3952 let mut flags = entry.flags;
3953 let disk_ext = entry
3954 .flags_extended
3955 .map(IndexEntry::disk_flags_extended)
3956 .filter(|fe| *fe != 0);
3957 if disk_ext.is_some() {
3958 flags |= 0x4000;
3959 } else {
3960 flags &= !0x4000;
3961 }
3962 let path_len = entry.path.len().min(0xFFF) as u16;
3963 flags = (flags & 0xF000) | path_len;
3964 out.extend_from_slice(&flags.to_be_bytes());
3965
3966 if let Some(fe) = disk_ext {
3967 out.extend_from_slice(&fe.to_be_bytes());
3968 }
3969
3970 let common = previous_path
3971 .iter()
3972 .zip(entry.path.iter())
3973 .take_while(|(a, b)| a == b)
3974 .count();
3975 let to_remove = previous_path.len().saturating_sub(common);
3976 write_varint(out, to_remove);
3977 out.extend_from_slice(&entry.path[common..]);
3978 out.push(0);
3979
3980 previous_path.clear();
3981 previous_path.extend_from_slice(&entry.path);
3982}
3983
3984fn serialize_entry(entry: &IndexEntry, version: u32, out: &mut Vec<u8>) {
3985 let start = out.len();
3986
3987 let write_u32 = |out: &mut Vec<u8>, v: u32| out.extend_from_slice(&v.to_be_bytes());
3988
3989 write_u32(out, entry.ctime_sec);
3990 write_u32(out, entry.ctime_nsec);
3991 write_u32(out, entry.mtime_sec);
3992 write_u32(out, entry.mtime_nsec);
3993 write_u32(out, entry.dev);
3994 write_u32(out, entry.ino);
3995 write_u32(out, entry.mode);
3996 write_u32(out, entry.uid);
3997 write_u32(out, entry.gid);
3998 write_u32(out, entry.size);
3999 out.extend_from_slice(entry.oid.as_bytes());
4000
4001 let mut flags = entry.flags;
4003 let disk_ext = entry
4004 .flags_extended
4005 .map(IndexEntry::disk_flags_extended)
4006 .filter(|fe| *fe != 0);
4007 if version >= 3 && disk_ext.is_some() {
4008 flags |= 0x4000;
4009 } else {
4010 flags &= !0x4000;
4011 }
4012 let path_len = entry.path.len().min(0xFFF) as u16;
4014 flags = (flags & 0xF000) | path_len;
4015 out.extend_from_slice(&flags.to_be_bytes());
4016
4017 if version >= 3 {
4018 if let Some(fe) = disk_ext {
4019 out.extend_from_slice(&fe.to_be_bytes());
4020 }
4021 }
4022
4023 out.extend_from_slice(&entry.path);
4024 out.push(0);
4025
4026 let entry_len = out.len() - start;
4028 let padded = (entry_len + 7) & !7;
4029 let padding = padded - entry_len;
4030 for _ in 0..padding {
4031 out.push(0);
4032 }
4033}
4034
4035pub fn entry_from_stat(
4048 path: &Path,
4049 rel_path: &[u8],
4050 oid: ObjectId,
4051 mode: u32,
4052) -> Result<IndexEntry> {
4053 let meta = fs::symlink_metadata(path)?;
4054 Ok(entry_from_metadata(&meta, rel_path, oid, mode))
4055}
4056
4057#[must_use]
4061pub fn index_file_mtime(path: &Path) -> Option<(u32, u32)> {
4062 let meta = fs::metadata(path).ok()?;
4063 Some(index_mtime_from_metadata(&meta))
4064}
4065
4066fn index_mtime_from_metadata(meta: &fs::Metadata) -> (u32, u32) {
4067 let ft = filetime::FileTime::from_last_modification_time(meta);
4068 let secs = ft.unix_seconds();
4069 if secs.is_negative() {
4070 return (0, 0);
4071 }
4072 (secs as u32, ft.nanoseconds())
4073}
4074
4075const INDEX_MMAP_MIN_BYTES: u64 = 64 * 1024;
4077
4078enum IndexFileBytes {
4080 Owned(Vec<u8>),
4081 Mapped(memmap2::Mmap),
4082}
4083
4084impl std::ops::Deref for IndexFileBytes {
4085 type Target = [u8];
4086
4087 fn deref(&self) -> &[u8] {
4088 match self {
4089 Self::Owned(bytes) => bytes,
4090 Self::Mapped(map) => map,
4091 }
4092 }
4093}
4094
4095fn read_index_file(path: &Path) -> io::Result<(IndexFileBytes, (u32, u32))> {
4100 use std::io::Read;
4101
4102 let mut file = fs::File::open(path)?;
4103 let meta = file.metadata()?;
4104 let mtime = index_mtime_from_metadata(&meta);
4105 let len = meta.len();
4106 if meta.is_file() && len >= INDEX_MMAP_MIN_BYTES {
4107 if let Ok(len) = usize::try_from(len) {
4108 if let Ok(map) = unsafe { memmap2::MmapOptions::new().len(len).map(&file) } {
4111 return Ok((IndexFileBytes::Mapped(map), mtime));
4112 }
4113 }
4114 }
4115 let mut bytes = Vec::with_capacity(usize::try_from(len).unwrap_or(0));
4116 file.read_to_end(&mut bytes)?;
4117 Ok((IndexFileBytes::Owned(bytes), mtime))
4118}
4119
4120fn max_stage0_entry_mtime(entries: &[IndexEntry]) -> (u32, u32) {
4121 let mut max_sec = 0u32;
4122 let mut max_nsec = 0u32;
4123 for ie in entries {
4124 if ie.stage() != 0 {
4125 continue;
4126 }
4127 if ie.mtime_sec > max_sec || (ie.mtime_sec == max_sec && ie.mtime_nsec > max_nsec) {
4128 max_sec = ie.mtime_sec;
4129 max_nsec = ie.mtime_nsec;
4130 }
4131 }
4132 (max_sec, max_nsec)
4133}
4134
4135#[must_use]
4137pub(crate) fn max_stage0_mtime_pair(a: (u32, u32), b: (u32, u32)) -> (u32, u32) {
4138 if a.0 > b.0 || (a.0 == b.0 && a.1 > b.1) {
4139 a
4140 } else {
4141 b
4142 }
4143}
4144
4145pub(crate) fn stage0_mtime_max(entries: &[IndexEntry]) -> (u32, u32) {
4147 max_stage0_entry_mtime(entries)
4148}
4149
4150pub(crate) fn advance_index_file_mtime_past_entries(
4167 index: &Index,
4168 index_path: &Path,
4169 touched_only_max: Option<(u32, u32)>,
4170) -> Result<()> {
4171 use filetime::FileTime;
4172 let (max_sec, max_nsec) = match touched_only_max {
4173 Some(pair) => pair,
4174 None => max_stage0_entry_mtime(&index.entries),
4175 };
4176 if max_sec == 0 && max_nsec == 0 {
4177 return Ok(());
4178 }
4179 let needs_bump = index_file_mtime(index_path)
4180 .is_none_or(|(sec, nsec)| sec < max_sec || (sec == max_sec && nsec <= max_nsec));
4181 if !needs_bump {
4182 return Ok(());
4183 }
4184 let ft = if max_nsec < 999_999_999 {
4185 FileTime::from_unix_time(i64::from(max_sec), max_nsec + 1)
4186 } else {
4187 FileTime::from_unix_time(i64::from(max_sec) + 1, 0)
4188 };
4189 filetime::set_file_mtime(index_path, ft).map_err(Error::Io)
4190}
4191
4192#[must_use]
4194pub(crate) fn index_path_outcome_key(path: &[u8]) -> Cow<'_, str> {
4195 match std::str::from_utf8(path) {
4196 Ok(s) => Cow::Borrowed(s),
4197 Err(_) => Cow::Owned(String::from_utf8_lossy(path).into_owned()),
4198 }
4199}
4200
4201#[must_use]
4205pub fn worktree_path_from_index_rel(work_tree: &Path, rel: &[u8]) -> PathBuf {
4206 #[cfg(unix)]
4207 {
4208 use std::ffi::OsStr;
4209 use std::os::unix::ffi::OsStrExt;
4210 work_tree.join(OsStr::from_bytes(rel))
4211 }
4212 #[cfg(not(unix))]
4213 {
4214 let rel = std::str::from_utf8(rel)
4215 .map(Cow::Borrowed)
4216 .unwrap_or_else(|_| Cow::Owned(String::from_utf8_lossy(rel).into_owned()));
4217 work_tree.join(rel.as_ref())
4218 }
4219}
4220
4221#[derive(Debug, Clone, Copy)]
4223pub struct IndexStatFields {
4224 ctime_sec: u32,
4225 ctime_nsec: u32,
4226 mtime_sec: u32,
4227 mtime_nsec: u32,
4228 dev: u32,
4229 ino: u32,
4230 uid: u32,
4231 gid: u32,
4232 size: u32,
4233}
4234
4235impl IndexStatFields {
4236 #[cfg(all(target_os = "linux", target_pointer_width = "64"))]
4238 pub(crate) fn from_linux_stat(stat: &libc::stat) -> Self {
4239 Self {
4240 ctime_sec: stat.st_ctime as u32,
4241 ctime_nsec: stat.st_ctime_nsec as u32,
4242 mtime_sec: stat.st_mtime as u32,
4243 mtime_nsec: stat.st_mtime_nsec as u32,
4244 dev: stat.st_dev as u32,
4245 ino: stat.st_ino as u32,
4246 uid: stat.st_uid,
4247 gid: stat.st_gid,
4248 size: munge_index_size(stat.st_size as u64),
4249 }
4250 }
4251
4252 #[must_use]
4254 pub(crate) fn index_size(&self) -> u32 {
4255 self.size
4256 }
4257
4258 #[must_use]
4260 pub fn from_metadata(meta: &fs::Metadata) -> Self {
4261 #[cfg(unix)]
4262 {
4263 use std::os::unix::fs::MetadataExt;
4264 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4265 Self {
4266 ctime_sec,
4267 ctime_nsec,
4268 mtime_sec,
4269 mtime_nsec,
4270 dev: meta.dev() as u32,
4271 ino: meta.ino() as u32,
4272 uid: meta.uid(),
4273 gid: meta.gid(),
4274 size: munge_index_size(meta.size()),
4275 }
4276 }
4277 #[cfg(not(unix))]
4278 {
4279 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4280 Self {
4281 ctime_sec,
4282 ctime_nsec,
4283 mtime_sec,
4284 mtime_nsec,
4285 dev: 0,
4286 ino: 0,
4287 uid: 0,
4288 gid: 0,
4289 size: munge_index_size(meta.len()),
4290 }
4291 }
4292 }
4293
4294 #[must_use]
4296 pub(crate) fn of_entry(ie: &IndexEntry) -> Self {
4297 Self {
4298 ctime_sec: ie.ctime_sec,
4299 ctime_nsec: ie.ctime_nsec,
4300 mtime_sec: ie.mtime_sec,
4301 mtime_nsec: ie.mtime_nsec,
4302 dev: ie.dev,
4303 ino: ie.ino,
4304 uid: ie.uid,
4305 gid: ie.gid,
4306 size: ie.size,
4307 }
4308 }
4309
4310 #[must_use]
4312 pub(crate) fn equivalent_to(&self, other: &Self, policy: StatMatchPolicy) -> bool {
4313 if self.size != other.size || self.mtime_sec != other.mtime_sec {
4314 return false;
4315 }
4316 if policy.use_nanosec && policy.check_stat && self.mtime_nsec != other.mtime_nsec {
4317 return false;
4318 }
4319 if policy.trust_ctime && policy.check_stat {
4320 if self.ctime_sec != other.ctime_sec {
4321 return false;
4322 }
4323 if policy.use_nanosec && self.ctime_nsec != other.ctime_nsec {
4324 return false;
4325 }
4326 }
4327 !policy.check_stat
4328 || (self.dev == other.dev
4329 && self.ino == other.ino
4330 && self.uid == other.uid
4331 && self.gid == other.gid)
4332 }
4333
4334 pub(crate) fn store_in(&self, ie: &mut IndexEntry) {
4336 ie.ctime_sec = self.ctime_sec;
4337 ie.ctime_nsec = self.ctime_nsec;
4338 ie.mtime_sec = self.mtime_sec;
4339 ie.mtime_nsec = self.mtime_nsec;
4340 ie.dev = self.dev;
4341 ie.ino = self.ino;
4342 ie.uid = self.uid;
4343 ie.gid = self.gid;
4344 ie.size = self.size;
4345 }
4346
4347 #[must_use]
4349 pub fn index_entry(self, rel_path: &[u8], oid: ObjectId, mode: u32) -> IndexEntry {
4350 IndexEntry {
4351 ctime_sec: self.ctime_sec,
4352 ctime_nsec: self.ctime_nsec,
4353 mtime_sec: self.mtime_sec,
4354 mtime_nsec: self.mtime_nsec,
4355 dev: self.dev,
4356 ino: self.ino,
4357 mode,
4358 uid: self.uid,
4359 gid: self.gid,
4360 size: self.size,
4361 oid,
4362 flags: rel_path.len().min(0xFFF) as u16,
4363 flags_extended: None,
4364 path: rel_path.to_vec(),
4365 base_index_pos: 0,
4366 }
4367 }
4368}
4369
4370impl From<&fs::Metadata> for IndexStatFields {
4371 fn from(meta: &fs::Metadata) -> Self {
4372 Self::from_metadata(meta)
4373 }
4374}
4375
4376impl IndexStatFields {
4377 #[must_use]
4379 #[cfg_attr(not(windows), allow(dead_code))]
4380 pub(crate) fn mtime_and_size_match_index(&self, ie: &IndexEntry) -> bool {
4381 self.size == ie.size && self.mtime_sec == ie.mtime_sec
4382 }
4383
4384 #[must_use]
4386 pub(crate) fn matches_index_entry(&self, ie: &IndexEntry, policy: StatMatchPolicy) -> bool {
4387 if self.size != ie.size {
4388 return false;
4389 }
4390 if self.mtime_sec != ie.mtime_sec {
4391 return false;
4392 }
4393 if policy.use_nanosec && policy.check_stat && self.mtime_nsec != ie.mtime_nsec {
4394 return false;
4395 }
4396 if policy.trust_ctime && policy.check_stat {
4397 if self.ctime_sec != ie.ctime_sec {
4398 return false;
4399 }
4400 if policy.use_nanosec && self.ctime_nsec != ie.ctime_nsec {
4401 return false;
4402 }
4403 }
4404 if policy.check_stat {
4405 #[cfg(windows)]
4409 {
4410 if self.ino != ie.ino && self.ino != 0 {
4411 return false;
4412 }
4413 if self.dev != ie.dev && self.dev != 0 {
4414 return false;
4415 }
4416 if self.uid != ie.uid && self.uid != 0 {
4417 return false;
4418 }
4419 if self.gid != ie.gid && self.gid != 0 {
4420 return false;
4421 }
4422 }
4423 #[cfg(not(windows))]
4424 if self.ino != ie.ino {
4425 return false;
4426 }
4427 }
4428 true
4429 }
4430}
4431
4432#[must_use]
4437pub fn entry_from_metadata(
4438 meta: &fs::Metadata,
4439 rel_path: &[u8],
4440 oid: ObjectId,
4441 mode: u32,
4442) -> IndexEntry {
4443 #[cfg(unix)]
4444 {
4445 use std::os::unix::fs::MetadataExt;
4446 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4447 IndexEntry {
4448 ctime_sec,
4449 ctime_nsec,
4450 mtime_sec,
4451 mtime_nsec,
4452 dev: meta.dev() as u32,
4453 ino: meta.ino() as u32,
4454 mode,
4455 uid: meta.uid(),
4456 gid: meta.gid(),
4457 size: munge_index_size(meta.size()),
4458 oid,
4459 flags: rel_path.len().min(0xFFF) as u16,
4460 flags_extended: None,
4461 path: rel_path.to_vec(),
4462 base_index_pos: 0,
4463 }
4464 }
4465 #[cfg(not(unix))]
4466 {
4467 let ((ctime_sec, ctime_nsec), (mtime_sec, mtime_nsec)) = metadata_times(meta);
4468 IndexEntry {
4469 ctime_sec,
4470 ctime_nsec,
4471 mtime_sec,
4472 mtime_nsec,
4473 dev: 0,
4474 ino: 0,
4475 mode,
4476 uid: 0,
4477 gid: 0,
4478 size: munge_index_size(meta.len()),
4479 oid,
4480 flags: rel_path.len().min(0xFFF) as u16,
4481 flags_extended: None,
4482 path: rel_path.to_vec(),
4483 base_index_pos: 0,
4484 }
4485 }
4486}
4487
4488#[must_use]
4498pub fn normalize_mode(raw_mode: u32) -> u32 {
4499 const S_IFMT: u32 = 0o170000;
4500 const S_IFLNK: u32 = 0o120000;
4501 const S_IFREG: u32 = 0o100000;
4502
4503 let fmt = raw_mode & S_IFMT;
4504 if fmt == S_IFLNK {
4505 return MODE_SYMLINK;
4506 }
4507 if fmt == S_IFREG {
4508 if raw_mode & 0o111 != 0 {
4510 return MODE_EXECUTABLE;
4511 }
4512 return MODE_REGULAR;
4513 }
4514 MODE_REGULAR
4516}
4517
4518#[cfg(test)]
4519pub(crate) mod index_path_cmp {
4520 use std::cell::Cell;
4521
4522 thread_local! { static PATH_CMP_COUNT: Cell<usize> = const { Cell::new(0) };
4524 }
4525
4526 pub fn reset() {
4527 PATH_CMP_COUNT.with(|c| c.set(0));
4528 }
4529
4530 pub fn take() -> usize {
4531 PATH_CMP_COUNT.with(|c| {
4532 let n = c.get();
4533 c.set(0);
4534 n
4535 })
4536 }
4537
4538 pub fn bump() {
4539 PATH_CMP_COUNT.with(|c| c.set(c.get() + 1));
4540 }
4541}
4542
4543fn record_path_cmp_for_tests() {
4544 #[cfg(test)]
4545 index_path_cmp::bump();
4546}
4547
4548#[cfg(test)]
4549mod tests {
4550 #![allow(clippy::expect_used, clippy::unwrap_used)]
4551
4552 use super::*;
4553 use tempfile::TempDir;
4554
4555 pub(super) mod fsmonitor_rebuild_counter {
4558 use std::cell::Cell;
4559
4560 thread_local! { static COUNT: Cell<usize> = const { Cell::new(0) };
4562 }
4563
4564 pub fn reset() {
4565 COUNT.with(|c| c.set(0));
4566 }
4567
4568 pub fn count() -> usize {
4569 COUNT.with(Cell::get)
4570 }
4571
4572 pub fn note_build() {
4573 COUNT.with(|c| c.set(c.get() + 1));
4574 }
4575 }
4576
4577 fn dummy_oid() -> ObjectId {
4578 ObjectId::from_bytes(&[0u8; 20]).unwrap()
4579 }
4580
4581 fn make_entry(path: &str) -> IndexEntry {
4582 IndexEntry {
4583 ctime_sec: 0,
4584 ctime_nsec: 0,
4585 mtime_sec: 0,
4586 mtime_nsec: 0,
4587 dev: 0,
4588 ino: 0,
4589 mode: MODE_REGULAR,
4590 uid: 0,
4591 gid: 0,
4592 size: 0,
4593 oid: dummy_oid(),
4594 flags: path.len().min(0xFFF) as u16,
4595 flags_extended: None,
4596 path: path.as_bytes().to_vec(),
4597 base_index_pos: 0,
4598 }
4599 }
4600
4601 #[test]
4602 fn round_trip_empty_index() {
4603 let dir = TempDir::new().unwrap();
4604 let path = dir.path().join("index");
4605
4606 let mut idx = Index::new();
4607 idx.write(&path).unwrap();
4608
4609 let loaded = Index::load(&path).unwrap();
4610 assert_eq!(loaded.entries.len(), 0);
4611 }
4612
4613 #[test]
4614 fn read_index_file_maps_only_large_files() {
4615 let dir = TempDir::new().unwrap();
4616 let threshold = usize::try_from(INDEX_MMAP_MIN_BYTES).unwrap();
4617 for (len, mapped) in [(0, false), (threshold - 1, false), (threshold, true)] {
4618 let path = dir.path().join(format!("index-{len}"));
4619 let contents: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
4620 fs::write(&path, &contents).unwrap();
4621 let (bytes, mtime) = read_index_file(&path).unwrap();
4622 assert_eq!(matches!(bytes, IndexFileBytes::Mapped(_)), mapped, "{len}");
4623 assert_eq!(&*bytes, contents.as_slice());
4624 assert_eq!(Some(mtime), index_file_mtime(&path));
4625 }
4626 let missing = read_index_file(&dir.path().join("missing")).err().unwrap();
4627 assert_eq!(missing.kind(), io::ErrorKind::NotFound);
4628 }
4629
4630 #[test]
4631 fn round_trip_with_entries() {
4632 let dir = TempDir::new().unwrap();
4633 let path = dir.path().join("index");
4634
4635 let mut idx = Index::new();
4636 idx.add_or_replace(make_entry("foo.txt"));
4637 idx.add_or_replace(make_entry("bar/baz.txt"));
4638 idx.write(&path).unwrap(); let loaded = Index::load(&path).unwrap();
4641 assert_eq!(loaded.entries.len(), 2);
4642 assert_eq!(loaded.entries[0].path, b"bar/baz.txt");
4643 assert_eq!(loaded.entries[1].path, b"foo.txt");
4644 }
4645
4646 fn overwrite_fixture() -> Index {
4647 let mut idx = Index::new();
4648 for i in 0..200 {
4649 let mut e = make_entry(&format!("d{}/f{i:03}.txt", i % 4));
4650 e.base_index_pos = i + 1;
4651 idx.add_or_replace(e);
4652 }
4653 for stage in 1..=3u16 {
4654 let mut e = make_entry("d0/conflict.txt");
4655 e.flags |= stage << 12;
4656 idx.add_or_replace(e);
4657 }
4658 let leaf =
4659 |name: &str| CacheTreeNode::valid(name.as_bytes().to_vec(), 50, dummy_oid(), vec![]);
4660 idx.cache_tree_root = Some(dummy_oid());
4661 idx.cache_tree = Some(CacheTreeNode::valid(
4662 Vec::new(),
4663 203,
4664 dummy_oid(),
4665 vec![leaf("d0"), leaf("d1"), leaf("d2"), leaf("d3")],
4666 ));
4667 assert!(idx.entries_sorted);
4668 idx
4669 }
4670
4671 fn overwrite_replacements(count: usize) -> Vec<IndexEntry> {
4672 let changed = ObjectId::from_bytes(&[7u8; 20]).unwrap();
4673 let mut out: Vec<IndexEntry> = (0..count)
4674 .map(|i| {
4675 let mut e = make_entry(&format!("d{}/f{:03}.txt", (i * 3) % 4, i * 3));
4676 e.oid = changed;
4677 e.size = 9;
4678 e
4679 })
4680 .collect();
4681 out.sort_by(|a, b| a.path.cmp(&b.path));
4682 out
4683 }
4684
4685 #[test]
4686 fn overwrite_stage0_entries_matches_sorted_merge() {
4687 for count in [3, 64] {
4688 let mut merged = overwrite_fixture();
4689 merged.replace_stage0_entries_batch(overwrite_replacements(count));
4690 let mut overwritten = overwrite_fixture();
4691 overwritten.overwrite_stage0_entries(overwrite_replacements(count));
4692 assert_eq!(overwritten.entries, merged.entries, "count {count}");
4693 assert_eq!(overwritten.cache_tree, merged.cache_tree, "count {count}");
4694 assert_eq!(overwritten.cache_tree_root, merged.cache_tree_root);
4695 assert_eq!(overwritten.entries.len(), 203);
4696 }
4697 }
4698
4699 #[test]
4700 fn overwrite_stage0_entries_keeps_split_positions_and_invalidates_touched_dirs() {
4701 let mut idx = overwrite_fixture();
4702 let target = b"d1/f001.txt".to_vec();
4703 let pos = idx.find_entry_pos(&target, 0).unwrap();
4704 let before = idx.entries[pos].base_index_pos;
4705 let mut e = make_entry("d1/f001.txt");
4706 e.size = 42;
4707 idx.overwrite_stage0_entries(vec![e]);
4708 assert_eq!(idx.entries[pos].size, 42);
4709 assert_eq!(idx.entries[pos].base_index_pos, before);
4710 let tree = idx.cache_tree.as_ref().unwrap();
4711 assert!(!tree.is_valid());
4712 let child = |name: &[u8]| tree.children.iter().find(|c| c.name == name).unwrap();
4713 assert!(!child(b"d1").is_valid());
4714 assert!(child(b"d0").is_valid());
4715 }
4716
4717 #[test]
4718 fn overwrite_stage0_entries_merges_paths_without_a_stage0_row() {
4719 let mut idx = overwrite_fixture();
4720 let mut new_path = make_entry("d2/new.txt");
4721 new_path.size = 1;
4722 idx.overwrite_stage0_entries(vec![make_entry("d0/f000.txt"), new_path]);
4723 assert_eq!(idx.entries.len(), 204);
4724 assert!(idx.get(b"d2/new.txt", 0).is_some());
4725 assert_eq!(
4726 idx.entries
4727 .iter()
4728 .filter(|e| e.path == b"d0/conflict.txt")
4729 .count(),
4730 3
4731 );
4732 assert!(idx.entries.windows(2).all(|w| {
4733 Index::cmp_paths(&w[0].path, &w[1].path)
4734 .then_with(|| w[0].stage().cmp(&w[1].stage()))
4735 .is_lt()
4736 }));
4737 }
4738
4739 #[test]
4740 fn remove_descendants_under_path_drops_nested_only() {
4741 let mut idx = Index::new();
4742 idx.add_or_replace(make_entry("d/e"));
4743 idx.add_or_replace(make_entry("d-other"));
4744 idx.add_or_replace(make_entry("prefix/d"));
4745 idx.remove_descendants_under_path("d");
4746 let paths: Vec<_> = idx.entries.iter().map(|e| e.path.as_slice()).collect();
4747 assert_eq!(paths, vec![b"d-other".as_slice(), b"prefix/d".as_slice()]);
4748 }
4749
4750 #[test]
4751 fn fsmonitor_extension_round_trip_matches_git_layout() {
4752 let fsmn_payload: [u8; 41] = [
4754 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,
4755 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4756 ];
4757 let (token, dirty) = parse_fsmonitor_extension(&fsmn_payload).unwrap();
4758 assert_eq!(token, "builtin:fake");
4759 assert_eq!(dirty.bit_size, 0);
4760
4761 let dir = TempDir::new().unwrap();
4762 let path = dir.path().join("index");
4763 let mut idx = Index::new();
4764 idx.add_or_replace(make_entry("a"));
4765 if let Some(e) = idx.get_mut(b"a", 0) {
4766 e.set_fsmonitor_valid(true);
4767 }
4768 idx.fsmonitor_last_update = Some(token);
4769 idx.fsmonitor_dirty = Some(dirty);
4770 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
4771 idx.write(&path).unwrap(); let loaded = Index::load(&path).unwrap();
4774 assert_eq!(
4775 loaded.fsmonitor_last_update.as_deref(),
4776 Some("builtin:fake")
4777 );
4778 assert_eq!(loaded.fsmonitor_dirty.as_ref().map(|b| b.bit_size), Some(0));
4779 assert_eq!(
4780 loaded.fsmonitor_raw_extension.as_deref(),
4781 Some(fsmn_payload.as_slice())
4782 );
4783
4784 let mut body = Vec::new();
4785 loaded.serialize_into(&mut body).unwrap();
4786 assert!(
4787 body.windows(4).any(|w| w == b"FSMN"),
4788 "serialized index must include FSMN"
4789 );
4790 let off = body.windows(4).position(|w| w == b"FSMN").unwrap();
4791 let ext_sz = u32::from_be_bytes(body[off + 4..off + 8].try_into().unwrap()) as usize;
4792 let written = &body[off + 8..off + 8 + ext_sz];
4793 assert_eq!(written, fsmn_payload);
4794 }
4795
4796 #[test]
4797 fn fsmonitor_extension_written_from_token_only_state() {
4798 let dir = TempDir::new().unwrap();
4799 let path = dir.path().join("index");
4800 let mut idx = Index::new();
4801 idx.add_or_replace(make_entry("a"));
4802 idx.set_fsmonitor_last_update(Some("builtin:fake".to_owned()));
4803 idx.write(&path).unwrap(); let data = fs::read(&path).unwrap();
4806 assert!(
4807 data.windows(4).any(|w| w == b"FSMN"),
4808 "token-only enable must emit a full FSMN extension"
4809 );
4810 let loaded = Index::load(&path).unwrap();
4811 assert_eq!(
4812 loaded.fsmonitor_last_update.as_deref(),
4813 Some("builtin:fake")
4814 );
4815 }
4816
4817 #[test]
4818 fn fsmonitor_raw_not_reused_after_token_change() {
4819 let fsmn_payload: [u8; 41] = [
4820 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,
4821 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4822 ];
4823 let mut idx = Index::new();
4824 idx.add_or_replace(make_entry("a"));
4825 idx.fsmonitor_last_update = Some("builtin:fake".to_owned());
4826 idx.fsmonitor_dirty = Some(parse_fsmonitor_extension(&fsmn_payload).unwrap().1);
4827 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
4828
4829 idx.set_fsmonitor_last_update(Some("last_update_token".to_owned()));
4830 let mut body = Vec::new();
4831 idx.serialize_into(&mut body).unwrap();
4832 let off = body.windows(4).position(|w| w == b"FSMN").unwrap();
4833 let ext_sz = u32::from_be_bytes(body[off + 4..off + 8].try_into().unwrap()) as usize;
4834 let written = &body[off + 8..off + 8 + ext_sz];
4835 let (token, _) = parse_fsmonitor_extension(written).unwrap();
4836 assert_eq!(token, "last_update_token");
4837 assert_ne!(written, fsmn_payload);
4838 }
4839
4840 #[test]
4841 fn fsmonitor_raw_not_reused_after_validity_change() {
4842 let fsmn_payload: [u8; 41] = [
4843 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,
4844 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4845 ];
4846 let mut idx = Index::new();
4847 idx.add_or_replace(make_entry("a"));
4848 idx.fsmonitor_last_update = Some("builtin:fake".to_owned());
4849 idx.fsmonitor_dirty = Some(parse_fsmonitor_extension(&fsmn_payload).unwrap().1);
4850 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
4851 if let Some(e) = idx.get_mut(b"a", 0) {
4852 e.set_fsmonitor_valid(false);
4853 }
4854 idx.fsmonitor_validity_changed();
4855
4856 let mut body = Vec::new();
4857 idx.serialize_into(&mut body).unwrap();
4858 let off = body.windows(4).position(|w| w == b"FSMN").unwrap();
4859 let ext_sz = u32::from_be_bytes(body[off + 4..off + 8].try_into().unwrap()) as usize;
4860 let written = &body[off + 8..off + 8 + ext_sz];
4861 assert_ne!(written, fsmn_payload);
4862 let (_, dirty) = parse_fsmonitor_extension(written).unwrap();
4863 assert!(dirty.bit_size >= 1);
4864 }
4865
4866 fn make_staged_entry(path: &str, stage: u8) -> IndexEntry {
4867 let mut e = make_entry(path);
4868 e.set_stage(stage);
4869 e
4870 }
4871
4872 struct LinearIndex {
4874 entries: Vec<IndexEntry>,
4875 resolve_undo: Option<BTreeMap<Vec<u8>, ResolveUndoRecord>>,
4876 }
4877
4878 impl LinearIndex {
4879 fn from_index(idx: &Index) -> Self {
4880 Self {
4881 entries: idx.entries.clone(),
4882 resolve_undo: idx.resolve_undo.clone(),
4883 }
4884 }
4885
4886 fn assert_matches(&self, idx: &Index) {
4887 let mut actual = idx.entries.clone();
4888 actual.sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
4889 let mut expect = self.entries.clone();
4890 expect.sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
4891 assert_eq!(actual, expect);
4892 assert_eq!(idx.resolve_undo, self.resolve_undo);
4893 }
4894
4895 fn add_or_replace(&mut self, entry: IndexEntry) {
4896 if let Some(pos) = self
4897 .entries
4898 .iter()
4899 .position(|e| e.path == entry.path && e.stage() == entry.stage())
4900 {
4901 self.entries[pos] = entry;
4902 } else {
4903 self.entries.push(entry);
4904 }
4905 self.entries
4906 .sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
4907 }
4908
4909 fn remove(&mut self, path: &[u8]) -> bool {
4910 let mut removed_any = false;
4911 for e in &self.entries {
4912 if e.path == path {
4913 if e.stage() != 0 {
4914 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
4915 }
4916 removed_any = true;
4917 }
4918 }
4919 if !removed_any {
4920 return false;
4921 }
4922 self.entries.retain(|e| e.path != path);
4923 removed_any
4924 }
4925
4926 fn stage_file(&mut self, entry: IndexEntry) {
4927 let path = entry.path.clone();
4928 for e in &self.entries {
4929 if e.path == path && e.stage() != 0 {
4930 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
4931 }
4932 }
4933 self.entries.retain(|e| e.path != path || e.stage() == 0);
4934 self.add_or_replace(entry);
4935 }
4936
4937 fn remove_descendants_under_path(&mut self, path: &str) {
4938 let prefix = path.as_bytes();
4939 if prefix.is_empty() {
4940 return;
4941 }
4942 let plen = prefix.len();
4943 for e in self.entries.iter() {
4944 let ep = e.path.as_slice();
4945 if ep.len() > plen && ep.starts_with(prefix) && ep[plen] == b'/' && e.stage() != 0 {
4946 resolve_undo::record_resolve_undo_for_entry(&mut self.resolve_undo, e);
4947 }
4948 }
4949 self.entries.retain(|e| {
4950 let ep = e.path.as_slice();
4951 if ep.len() <= plen {
4952 return true;
4953 }
4954 if !ep.starts_with(prefix) {
4955 return true;
4956 }
4957 ep[plen] != b'/'
4958 });
4959 }
4960 }
4961
4962 #[test]
4963 fn sorted_ops_match_linear_oracle() {
4964 let paths: Vec<Vec<u8>> = [
4965 "a", "a-b", "a/b", "a/b/c", "aa", "b", "dir/x", "dir/x/y", "dir-xy",
4966 ]
4967 .into_iter()
4968 .map(|p| p.as_bytes().to_vec())
4969 .collect();
4970
4971 let mut idx = Index::new();
4972 let mut oracle = LinearIndex::from_index(&idx);
4973 let mut rng_state: u64 = 376;
4974
4975 fn next_u64(state: &mut u64) -> u64 {
4976 *state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
4977 *state
4978 }
4979
4980 for step in 0..400 {
4981 let op = next_u64(&mut rng_state) % 4;
4982 match op {
4983 0 => {
4984 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
4985 let stage = (next_u64(&mut rng_state) % 4) as u8;
4986 let mut e = make_staged_entry(std::str::from_utf8(p).unwrap(), stage);
4987 e.oid = ObjectId::from_bytes(&[(step % 256) as u8; 20]).unwrap();
4988 idx.add_or_replace(e.clone());
4989 oracle.add_or_replace(e);
4990 }
4991 1 => {
4992 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
4993 idx.remove(p);
4994 oracle.remove(p);
4995 }
4996 2 => {
4997 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
4998 let e = make_entry(std::str::from_utf8(p).unwrap());
4999 idx.stage_file(e.clone());
5000 oracle.stage_file(e);
5001 }
5002 _ => {
5003 let p = &paths[(next_u64(&mut rng_state) as usize) % paths.len()];
5004 let ps = std::str::from_utf8(p).unwrap();
5005 idx.remove_descendants_under_path(ps);
5006 oracle.remove_descendants_under_path(ps);
5007 }
5008 }
5009 oracle.assert_matches(&idx);
5010 }
5011 }
5012
5013 #[test]
5014 fn stale_sorted_flag_miss_until_resorted() {
5015 let mut idx = Index::new();
5016 idx.entries.push(make_entry("z"));
5017 idx.entries.push(make_entry("a"));
5018 idx.entries.push(make_entry("b"));
5019 idx.entries_sorted = true;
5020 assert!(!idx.entries_are_sorted());
5021 assert!(idx.get(b"z", 0).is_none());
5022 idx.sort();
5023 assert!(idx.get(b"z", 0).is_some());
5024 assert!(idx.remove(b"z"));
5025 assert!(idx.get(b"z", 0).is_none());
5026 }
5027
5028 #[test]
5029 fn write_restores_canonical_order_when_sorted_flag_is_stale() {
5030 let mut idx = Index::new();
5031 idx.entries.push(make_entry("zzz.txt"));
5032 idx.entries.push(make_entry("aaa.txt"));
5033 idx.entries_sorted = true;
5034 assert!(!idx.entries_are_sorted());
5035 let dir = tempfile::tempdir().expect("tempdir");
5036 let path = dir.path().join("index");
5037 idx.write(&path).expect("write index");
5038 let parsed = Index::load(&path).expect("load index");
5039 assert!(parsed.entries_are_sorted());
5040 assert_eq!(parsed.entries[0].path, b"aaa.txt".as_slice());
5041 }
5042
5043 #[test]
5044 fn find_entry_pos_sorted_miss_is_constant() {
5045 const N: usize = 100_000;
5046 const M: usize = 1_000;
5047 let mut idx = Index::new();
5048 for i in 0..N {
5049 idx.add_or_replace(make_entry(&format!("file/{i:06}.txt")));
5050 }
5051 assert!(idx.entries_sorted);
5052 let misses: Vec<Vec<u8>> = (0..M)
5053 .map(|i| format!("missing/{i:06}.txt").into_bytes())
5054 .collect();
5055 index_path_cmp::reset();
5056 for path in &misses {
5057 assert!(idx.get(path, 0).is_none());
5058 }
5059 let cmps = index_path_cmp::take();
5060 let log_n = (N as f64).log2().ceil() as usize;
5061 let bound = 100 * M * log_n;
5062 assert!(
5063 cmps < bound,
5064 "sorted miss path comparisons {cmps} should stay under {bound} (N={N}, M={M})"
5065 );
5066 }
5067
5068 #[test]
5069 fn unsorted_remove_drops_every_matching_stage_globally() {
5070 let mut idx = Index::new();
5071 idx.mark_entries_unsorted();
5072 idx.entries.push(make_staged_entry("a", 1));
5073 idx.entries.push(make_entry("z"));
5074 idx.entries.push(make_staged_entry("a", 2));
5075 assert!(idx.remove(b"a"));
5076 assert!(idx.get(b"a", 1).is_none());
5077 assert!(idx.get(b"a", 2).is_none());
5078 assert!(idx.get(b"z", 0).is_some());
5079 }
5080
5081 #[test]
5082 fn unsorted_remove_descendants_drops_noncontiguous_matches() {
5083 let mut idx = Index::new();
5084 idx.mark_entries_unsorted();
5085 idx.entries.push(make_entry("a/x"));
5086 idx.entries.push(make_entry("z"));
5087 idx.entries.push(make_entry("a/y"));
5088 idx.remove_descendants_under_path("a");
5089 let paths: Vec<_> = idx.entries.iter().map(|e| e.path.as_slice()).collect();
5090 assert_eq!(paths, vec![b"z".as_slice()]);
5091 }
5092
5093 #[test]
5094 fn sorted_ops_match_linear_oracle_unsorted_noncontiguous() {
5095 let mut idx = Index::new();
5096 idx.mark_entries_unsorted();
5097 idx.entries.push(make_entry("a/x"));
5098 idx.entries.push(make_entry("z"));
5099 idx.entries.push(make_staged_entry("a", 1));
5100 idx.remove(b"a");
5101 idx.entries.push(make_entry("a/y"));
5102 idx.sort();
5103 let mut oracle = LinearIndex::from_index(&idx);
5104 oracle.entries = idx.entries.clone();
5105 oracle.assert_matches(&idx);
5106 idx.remove_descendants_under_path("a");
5107 oracle.remove_descendants_under_path("a");
5108 oracle.assert_matches(&idx);
5109 }
5110
5111 #[test]
5112 fn merge_replacements_preserves_base_index_pos() {
5113 let mut idx = Index::new();
5114 let mut kept = make_entry("path/a.txt");
5115 kept.base_index_pos = 42;
5116 idx.add_or_replace(kept);
5117 let mut replacement = make_entry("path/a.txt");
5118 replacement.oid = dummy_oid();
5119 idx.remove_paths_and_insert([], [replacement]);
5120 assert_eq!(idx.entries[0].base_index_pos, 42);
5121 }
5122
5123 #[test]
5124 fn remove_paths_and_insert_replaces_equal_path_without_duplicates() {
5125 let mut idx = Index::new();
5126 idx.add_or_replace(make_entry("path/a.txt"));
5127 idx.add_or_replace(make_entry("path/b.txt"));
5128 let mut replacement = make_entry("path/a.txt");
5129 replacement.oid = dummy_oid();
5130 idx.remove_paths_and_insert(["ghost.txt".as_bytes()], [replacement.clone()]);
5131 assert_eq!(idx.entries.len(), 2);
5132 assert_eq!(idx.entries[0].path, b"path/a.txt");
5133 assert_eq!(idx.entries[0].oid, replacement.oid);
5134 assert_eq!(idx.entries[1].path, b"path/b.txt");
5135 }
5136
5137 #[test]
5138 fn remove_directory_prefixes_drops_nested_paths() {
5139 let mut idx = Index::new();
5140 idx.add_or_replace(make_entry("d005/f0.txt"));
5141 idx.add_or_replace(make_entry("d005/f1.txt"));
5142 idx.add_or_replace(make_entry("d006/f0.txt"));
5143 idx.add_or_replace(make_entry("keep.txt"));
5144 idx.remove_directory_prefixes(&BTreeSet::from(["d005".to_owned()]));
5145 assert!(idx.get(b"d005/f0.txt", 0).is_none());
5146 assert!(idx.get(b"d005/f1.txt", 0).is_none());
5147 assert!(idx.get(b"d006/f0.txt", 0).is_some());
5148 assert!(idx.get(b"keep.txt", 0).is_some());
5149 }
5150
5151 #[test]
5152 fn remove_prefixes_paths_and_insert_combines_prefix_exact_and_replacements() {
5153 let mut idx = Index::new();
5154 idx.add_or_replace(make_entry("d005/f0.txt"));
5155 idx.add_or_replace(make_entry("d005/f1.txt"));
5156 idx.add_or_replace(make_entry("d006/f0.txt"));
5157 idx.add_or_replace(make_entry("solo.txt"));
5158 let mut replacement = make_entry("solo.txt");
5159 replacement.oid = dummy_oid();
5160 idx.remove_prefixes_paths_and_insert(
5161 &BTreeSet::from(["d005".to_owned()]),
5162 [b"d006/f0.txt".as_slice()],
5163 [replacement.clone()],
5164 );
5165 assert!(idx.get(b"d005/f0.txt", 0).is_none());
5166 assert!(idx.get(b"d005/f1.txt", 0).is_none());
5167 assert!(idx.get(b"d006/f0.txt", 0).is_none());
5168 assert_eq!(idx.get(b"solo.txt", 0).unwrap().oid, replacement.oid);
5169 }
5170
5171 #[test]
5172 fn remove_directory_prefixes_does_not_skip_lexically_later_prefix_children() {
5173 let mut idx = Index::new();
5174 idx.add_or_replace(make_entry("d005/f0.txt"));
5175 idx.add_or_replace(make_entry("d005/f1.txt"));
5176 idx.remove_directory_prefixes(&BTreeSet::from(["d005".to_owned(), "d0050".to_owned()]));
5177 assert!(idx.get(b"d005/f0.txt", 0).is_none());
5178 assert!(idx.get(b"d005/f1.txt", 0).is_none());
5179 }
5180
5181 #[test]
5182 fn remove_is_sublinear() {
5183 const N: usize = 100_000;
5184 const M: usize = 1_000;
5185 let mut idx = Index::new();
5186 for i in 0..N {
5187 idx.add_or_replace(make_entry(&format!("file/{i:06}.txt")));
5188 }
5189 let paths: Vec<Vec<u8>> = (0..M)
5190 .map(|i| format!("file/{i:06}.txt").into_bytes())
5191 .collect();
5192 index_path_cmp::reset();
5193 idx.remove_paths(paths.iter().map(|p| p.as_slice()));
5194 let cmps = index_path_cmp::take();
5195 let log_n = (N as f64).log2().ceil() as usize;
5196 let bound = 100 * M * log_n;
5197 assert!(
5198 cmps < bound,
5199 "path comparisons {cmps} should stay under {bound} (N={N}, M={M})"
5200 );
5201 assert_eq!(idx.entries.len(), N - M);
5202 }
5203
5204 #[test]
5205 fn fsmonitor_rebuild_once_per_batch() {
5206 let fsmn_payload: [u8; 41] = [
5207 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,
5208 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5209 ];
5210 let (token, dirty) = parse_fsmonitor_extension(&fsmn_payload).unwrap();
5211 let dir = TempDir::new().unwrap();
5212 let path = dir.path().join("index");
5213
5214 let mut idx = Index::new();
5215 for i in 0..1000 {
5216 idx.add_or_replace(make_entry(&format!("path-{i:04}.txt")));
5217 }
5218 idx.sort();
5219 for e in &mut idx.entries {
5220 e.set_fsmonitor_valid(true);
5221 }
5222 idx.fsmonitor_last_update = Some(token);
5223 idx.fsmonitor_dirty = Some(dirty);
5224 idx.fsmonitor_raw_extension = Some(fsmn_payload.to_vec());
5225
5226 fsmonitor_rebuild_counter::reset();
5227 for i in 0..1000 {
5228 idx.add_or_replace(make_entry(&format!("mut-{i:04}.txt")));
5229 }
5230 assert_eq!(
5231 fsmonitor_rebuild_counter::count(),
5232 0,
5233 "entry mutations must not scan entries for the EWAH"
5234 );
5235 idx.write(&path).unwrap(); assert_eq!(
5237 fsmonitor_rebuild_counter::count(),
5238 1,
5239 "write must build the EWAH once for the batch"
5240 );
5241 }
5242
5243 #[test]
5244 fn requested_v4_writes_v4_on_disk() {
5245 let dir = TempDir::new().unwrap();
5246 let path = dir.path().join("index");
5247
5248 let mut idx = Index {
5249 version: 4,
5250 ..Index::default()
5251 };
5252 idx.add_or_replace(make_entry("one"));
5253 idx.add_or_replace(make_entry("two/one"));
5254 idx.write(&path).unwrap(); let data = fs::read(&path).unwrap();
5257 assert_eq!(&data[4..8], &4u32.to_be_bytes());
5258
5259 let loaded = Index::load(&path).unwrap();
5260 assert_eq!(loaded.version, 4);
5261 assert_eq!(loaded.entries[0].path, b"one");
5262 assert_eq!(loaded.entries[1].path, b"two/one");
5263 }
5264
5265 #[test]
5266 fn stat_match_policy_from_repo_config() {
5267 let dir = TempDir::new().unwrap();
5268 let git_dir = dir.path().join(".git");
5269 fs::create_dir_all(&git_dir).unwrap();
5270 fs::write(
5271 git_dir.join("config"),
5272 "[core]\n\ttrustctime = false\n\tcheckstat = minimal\n\tusenanosec = true\n",
5273 )
5274 .unwrap();
5275 let cfg = crate::config::ConfigSet::load(
5276 &crate::environment::Environment::empty(),
5277 Some(&git_dir),
5278 true,
5279 )
5280 .unwrap();
5281 let policy = StatMatchPolicy::from_config(Some(&cfg));
5282 assert!(!policy.trust_ctime);
5283 assert!(!policy.check_stat);
5284 assert!(policy.use_nanosec);
5285 }
5286
5287 #[test]
5288 fn index_entry_extended_flags_and_placeholders() {
5289 let mut e = make_entry("p");
5290 e.set_intent_to_add(true);
5291 assert!(e.intent_to_add());
5292 e.set_assume_unchanged(true);
5293 assert!(e.assume_unchanged());
5294 e.set_skip_worktree(true);
5295 assert!(e.skip_worktree());
5296 e.mode = MODE_TREE;
5297 e.set_skip_worktree(true);
5298 assert!(e.is_sparse_directory_placeholder());
5299 e.set_overlay_tree_skip_output(true);
5300 assert!(e.overlay_tree_skip_output());
5301 }
5302
5303 #[test]
5304 fn cache_tree_formatting_and_invalidation_helpers() {
5305 let mut idx = Index::new();
5306 idx.add_or_replace(make_entry("a/b"));
5307 let child = CacheTreeNode::valid(b"a".to_vec(), 1, dummy_oid(), vec![]);
5308 let root = CacheTreeNode::valid(b"".to_vec(), 1, dummy_oid(), vec![child]);
5309 idx.set_cache_tree(root);
5310 let dump = idx.format_cache_tree_dump();
5311 assert!(dump.contains("a/") || dump.contains("invalid"));
5312 idx.invalidate_cache_tree_for_path(b"a/b");
5313 idx.clear_cache_tree();
5314 assert!(idx.cache_tree.is_none());
5315 }
5316
5317 #[test]
5318 fn dedup_paths_and_stage0_descendant_queries() {
5319 let mut idx = Index::new();
5320 idx.add_or_replace(make_entry("dir/x"));
5321 idx.add_or_replace(make_entry("dir/x"));
5322 idx.dedup_paths_keep_last();
5323 assert_eq!(idx.entries.len(), 1);
5324 assert!(idx.has_stage0_descendant_under("dir"));
5325 assert!(!idx.has_stage0_descendant_under("missing"));
5326 }
5327
5328 #[test]
5329 fn load_missing_index_is_empty() {
5330 let dir = TempDir::new().unwrap();
5331 let path = dir.path().join("no-index");
5332 let idx = Index::load(&path).unwrap();
5333 assert!(idx.entries.is_empty());
5334 }
5335
5336 #[test]
5337 fn munge_and_stat_equivalence_helpers() {
5338 assert_eq!(munge_index_size(0x1_0000_0000), 0x8000_0000);
5339 let a = make_entry("x");
5340 let b = make_entry("x");
5341 assert!(index_entries_stat_equivalent(
5342 &a,
5343 &b,
5344 StatMatchPolicy::default()
5345 ));
5346 }
5347
5348 #[test]
5349 fn new_from_config_invalid_index_version_falls_back() {
5350 let dir = TempDir::new().unwrap();
5351 let git_dir = dir.path().join(".git");
5352 fs::create_dir_all(&git_dir).unwrap();
5353 fs::write(
5354 git_dir.join("config"),
5355 "[index]\n\tversion = not-a-number\n",
5356 )
5357 .unwrap();
5358 let cfg = crate::config::ConfigSet::load(
5359 &crate::environment::Environment::empty(),
5360 Some(&git_dir),
5361 true,
5362 )
5363 .unwrap();
5364 let idx = Index::new_from_config(&cfg);
5365 assert_eq!(idx.version, INDEX_CONFIG_INVALID_FALLBACK);
5366 }
5367
5368 #[test]
5369 fn fsmonitor_invalidate_directory_prefix_without_trailing_slash() {
5370 let mut idx = Index::new();
5371 idx.add_or_replace(make_entry("dir/a.txt"));
5372 idx.add_or_replace(make_entry("dir/b.txt"));
5373 idx.set_fsmonitor_last_update(Some("tok".into()));
5374 for entry in idx.entries_mut() {
5375 entry.set_fsmonitor_valid(true);
5376 }
5377 idx.invalidate_fsmonitor_for_path("dir", false);
5378 assert!(!idx.get(b"dir/a.txt", 0).unwrap().fsmonitor_valid());
5379 assert!(!idx.get(b"dir/b.txt", 0).unwrap().fsmonitor_valid());
5380 }
5381
5382 #[test]
5383 fn parse_fsmonitor_extension_rejects_truncated_ewah() {
5384 let mut payload = vec![0, 0, 0, 2, b't', 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0];
5385 let err = parse_fsmonitor_extension(&payload).unwrap_err();
5386 assert!(matches!(
5387 err,
5388 Error::IndexFsmonitorExtensionTruncated | Error::IndexFsmonitorExtensionEwahInvalid
5389 ));
5390 payload[12..16].copy_from_slice(&2u32.to_be_bytes());
5391 let err2 = parse_fsmonitor_extension(&payload).unwrap_err();
5392 assert!(matches!(
5393 err2,
5394 Error::IndexFsmonitorExtensionTruncated | Error::IndexFsmonitorExtensionEwahInvalid
5395 ));
5396 }
5397
5398 #[test]
5399 fn unmerge_from_resolve_undo_installs_conflict_stages() {
5400 use crate::resolve_undo::ResolveUndoRecord;
5401 use std::collections::BTreeMap;
5402
5403 let mut idx = Index::new();
5404 idx.add_or_replace(make_entry("f.txt"));
5405 let oid1 = dummy_oid();
5406 let oid2 = ObjectId::from_bytes(&[1u8; 20]).unwrap();
5407 idx.resolve_undo = Some(BTreeMap::from([(
5408 b"f.txt".to_vec(),
5409 ResolveUndoRecord {
5410 modes: [MODE_REGULAR, MODE_REGULAR, 0],
5411 oids: [oid1, oid2, ObjectId::zero()],
5412 },
5413 )]));
5414 assert!(idx.unmerge_path_from_resolve_undo(b"f.txt"));
5415 assert!(idx.has_unmerged_entries());
5416 }
5417
5418 #[test]
5419 fn parse_rejects_truncated_headers() {
5420 for n in 0..12 {
5421 assert!(Index::parse(&vec![0u8; n]).is_err());
5422 }
5423 }
5424
5425 #[test]
5426 fn entry_from_stat_populates_index_metadata() {
5427 let dir = TempDir::new().unwrap();
5428 fs::write(dir.path().join("f.txt"), b"payload").unwrap();
5429 let oid = dummy_oid();
5430 let entry = entry_from_stat(dir.path(), b"f.txt", oid, MODE_REGULAR).unwrap();
5431 assert_eq!(entry.path, b"f.txt");
5432 assert!(entry.size > 0);
5433 }
5434
5435 #[test]
5436 fn write_to_path_honors_skip_hash_flag() {
5437 let dir = TempDir::new().unwrap();
5438 let path = dir.path().join("index");
5439 let mut idx = Index::new();
5440 idx.add_or_replace(make_entry("x"));
5441 idx.write_to_path(&path, true).unwrap();
5442 let bytes = fs::read(&path).unwrap();
5443 assert!(bytes.ends_with(&[0u8; 20]));
5444 }
5445
5446 #[test]
5447 fn index_lock_acquire_blocks_second_holder() {
5448 let dir = TempDir::new().unwrap();
5449 let path = dir.path().join("index");
5450 let mut idx = Index::new();
5451 idx.add_or_replace(make_entry("tracked.txt"));
5452 let lock = IndexLock::acquire(&path, None).unwrap();
5453 assert!(matches!(
5454 IndexLock::acquire(&path, None),
5455 Err(Error::LockHeld { .. })
5456 ));
5457 drop(lock);
5458 idx.write(&path).unwrap(); assert_eq!(Index::load(&path).unwrap().entries.len(), 1);
5460 }
5461
5462 #[test]
5463 fn held_index_lock_is_a_typed_error_naming_the_holder() {
5464 let dir = TempDir::new().unwrap();
5465 let path = dir.path().join("index");
5466 let lock_path = dir.path().join("index.lock");
5467 fs::write(&lock_path, b"foreign").unwrap();
5468 fs::write(
5469 dir.path().join("index~pid.lock"),
5470 format!("pid {}\n", std::process::id()),
5471 )
5472 .unwrap();
5473
5474 let Err(acquire_err) = IndexLock::acquire(&path, None) else {
5475 panic!("lock must be refused");
5476 };
5477 let write_err = Index::new().write(&path).unwrap_err();
5478 for err in [acquire_err, write_err] {
5479 match &err {
5480 Error::LockHeld { path: p, holder } => {
5481 assert_eq!(p, &lock_path);
5482 assert_eq!(
5483 *holder,
5484 Some(crate::lockfile::LockHolder {
5485 pid: u64::from(std::process::id()),
5486 running: true,
5487 })
5488 );
5489 }
5490 other => panic!("expected LockHeld, got {other:?}"),
5491 }
5492 assert!(
5493 err.to_string().contains(&lock_path.display().to_string()),
5494 "{err}"
5495 );
5496 }
5497 assert_eq!(fs::read(&lock_path).unwrap(), b"foreign");
5498 assert!(!path.exists());
5499 }
5500
5501 #[test]
5502 fn write_with_config_and_parallel_policy() {
5503 let dir = TempDir::new().unwrap();
5504 let path = dir.path().join("index");
5505 let mut idx = Index::new();
5506 idx.add_or_replace(make_entry("p"));
5507 let cfg = crate::config::ConfigSet::new();
5508 idx.write_with_config_and_policy(&path, &cfg, crate::parallel::ThreadPolicy::default())
5509 .unwrap();
5510 assert!(path.is_file());
5511 }
5512
5513 #[test]
5514 fn fsmonitor_sync_and_clear_helpers() {
5515 let mut idx = Index::new();
5516 idx.add_or_replace(make_entry("a"));
5517 idx.set_fsmonitor_last_update(Some("token".to_owned()));
5518 idx.sync_fsmonitor_dirty_bitmap();
5519 if let Some(e) = idx.get_mut(b"a", 0) {
5520 e.set_fsmonitor_valid(false);
5521 }
5522 idx.fsmonitor_validity_changed();
5523 idx.clear_fsmonitor_extension();
5524 assert!(idx.fsmonitor_last_update.is_none());
5525 }
5526
5527 #[test]
5528 fn intent_to_add_entry_forces_empty_blob_on_write_tree_path() {
5529 let dir = TempDir::new().unwrap();
5530 let odb = crate::odb::Odb::new(dir.path());
5531 let mut idx = Index::new();
5532 let mut e = make_entry("ita.txt");
5533 e.set_intent_to_add(true);
5534 idx.add_or_replace(e);
5535 crate::write_tree::write_tree_from_index(&odb, &idx, "").unwrap();
5536 }
5537
5538 #[test]
5539 fn optional_load_is_empty_only_when_the_index_is_missing() {
5540 let dir = TempDir::new().unwrap();
5541 let odb = crate::odb::Odb::new(dir.path());
5542 let path = dir.path().join("index");
5543 let missing = Index::load_expand_sparse_optional(&path, &odb).unwrap();
5544 assert!(missing.entries.is_empty());
5545
5546 for len in [0, 12, 31] {
5547 std::fs::write(&path, b"DIRC\0\0\0\x02\0\0\0\0".repeat(3)[..len].to_vec()).unwrap();
5548 assert!(
5549 matches!(
5550 Index::load_expand_sparse_optional(&path, &odb),
5551 Err(Error::IndexError(_))
5552 ),
5553 "len={len}"
5554 );
5555 }
5556 }
5557
5558 fn naive_merge_sorted(kept: &[IndexEntry], insert: &[IndexEntry]) -> Vec<IndexEntry> {
5559 let mut out: Vec<IndexEntry> = kept.iter().chain(insert.iter()).cloned().collect();
5560 out.sort_by(index_entry_path_stage_cmp);
5561 let mut deduped = Vec::with_capacity(out.len());
5562 for entry in out {
5563 if deduped
5564 .last()
5565 .is_some_and(|last| index_entry_path_stage_cmp(last, &entry).is_eq())
5566 {
5567 *deduped.last_mut().unwrap() = entry;
5568 } else {
5569 deduped.push(entry);
5570 }
5571 }
5572 deduped
5573 }
5574
5575 #[test]
5576 fn merge_sorted_index_halves_matches_naive_merge() {
5577 let paths = [
5578 ".gitattributes",
5579 "a.txt",
5580 "b.txt",
5581 "f.txt",
5582 "z.txt",
5583 "dir/nested.txt",
5584 ];
5585 let mut kept = Vec::new();
5586 let mut insert = Vec::new();
5587 for (i, path) in paths.iter().enumerate() {
5588 let mut e = make_entry(path);
5589 e.oid = ObjectId::from_bytes(&[i as u8; 20]).unwrap();
5590 kept.push(e);
5591 }
5592 for (i, path) in paths.iter().enumerate() {
5593 if i % 2 == 0 {
5594 continue;
5595 }
5596 let mut e = make_entry(path);
5597 e.oid = ObjectId::from_bytes(&[(i + 100) as u8; 20]).unwrap();
5598 insert.push(e);
5599 }
5600 kept.sort_by(index_entry_path_stage_cmp);
5601 insert.sort_by(index_entry_path_stage_cmp);
5602
5603 let expected = naive_merge_sorted(&kept, &insert);
5604 let merged = merge_sorted_index_entries(kept, insert);
5605 assert_eq!(merged, expected, "fixed scenario merge mismatch");
5606
5607 for seed in 0u64..200 {
5608 let mut kept = Vec::new();
5609 let mut insert = Vec::new();
5610 for n in 0..((seed % 7) + 1) {
5611 let path = format!("p/{seed}/{n}.txt");
5612 let mut e = make_entry(&path);
5613 e.oid = ObjectId::from_bytes(&[(seed as u8).wrapping_add(n as u8); 20]).unwrap();
5614 kept.push(e);
5615 }
5616 for n in 0..((seed % 5) + 1) {
5617 let path = format!("p/{seed}/{n}.txt");
5618 let mut e = make_entry(&path);
5619 e.oid = ObjectId::from_bytes(
5620 &[(seed as u8).wrapping_add(n as u8).wrapping_add(50); 20],
5621 )
5622 .unwrap();
5623 insert.push(e);
5624 }
5625 kept.sort_by(index_entry_path_stage_cmp);
5626 insert.sort_by(index_entry_path_stage_cmp);
5627 let expected = naive_merge_sorted(&kept, &insert);
5628 let merged = merge_sorted_index_entries(kept, insert);
5629 assert_eq!(merged, expected, "seed {seed} merge mismatch");
5630 }
5631 }
5632}