1use std::collections::BTreeSet;
19use std::fs;
20use std::io::{self, Write};
21use std::path::Path;
22
23use sha1::{Digest, Sha1};
24
25use crate::config::ConfigSet;
26use crate::error::{Error, Result};
27use crate::objects::{parse_tree, ObjectId, ObjectKind, TreeEntry};
28use crate::odb::Odb;
29use crate::repo::Repository;
30use crate::rev_parse;
31
32pub const MODE_REGULAR: u32 = 0o100644;
34pub const MODE_EXECUTABLE: u32 = 0o100755;
36pub const MODE_SYMLINK: u32 = 0o120000;
38pub const MODE_GITLINK: u32 = 0o160000;
40pub const MODE_TREE: u32 = 0o040000;
42
43const INDEX_EXT_SPARSE_DIRECTORIES: u32 = u32::from_be_bytes(*b"sdir");
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct IndexEntry {
49 pub ctime_sec: u32,
51 pub ctime_nsec: u32,
53 pub mtime_sec: u32,
55 pub mtime_nsec: u32,
57 pub dev: u32,
59 pub ino: u32,
61 pub mode: u32,
63 pub uid: u32,
65 pub gid: u32,
67 pub size: u32,
69 pub oid: ObjectId,
71 pub flags: u16,
73 pub flags_extended: Option<u16>,
75 pub path: Vec<u8>,
77}
78
79impl IndexEntry {
80 #[must_use]
82 pub fn stage(&self) -> u8 {
83 ((self.flags >> 12) & 0x3) as u8
84 }
85
86 pub(crate) fn set_stage(&mut self, stage: u8) {
87 self.flags = (self.flags & 0x0FFF) | ((stage as u16 & 0x3) << 12);
88 }
89
90 #[must_use]
92 pub fn assume_unchanged(&self) -> bool {
93 self.flags & 0x8000 != 0
94 }
95
96 #[must_use]
98 pub fn skip_worktree(&self) -> bool {
99 self.flags_extended
100 .map(|f| f & 0x4000 != 0)
101 .unwrap_or(false)
102 }
103
104 pub fn set_assume_unchanged(&mut self, value: bool) {
106 if value {
107 self.flags |= 0x8000;
108 } else {
109 self.flags &= !0x8000;
110 }
111 }
112
113 pub fn set_skip_worktree(&mut self, value: bool) {
115 let fe = self.flags_extended.get_or_insert(0);
116 if value {
117 *fe |= 0x4000;
118 } else {
119 *fe &= !0x4000;
120 if *fe == 0 {
121 self.flags_extended = None;
122 }
123 }
124 }
125
126 #[must_use]
128 pub fn intent_to_add(&self) -> bool {
129 self.flags_extended
130 .map(|f| f & 0x2000 != 0)
131 .unwrap_or(false)
132 }
133
134 pub fn set_intent_to_add(&mut self, value: bool) {
136 let fe = self.flags_extended.get_or_insert(0);
137 if value {
138 *fe |= 0x2000;
139 } else {
140 *fe &= !0x2000;
141 if *fe == 0 {
142 self.flags_extended = None;
143 }
144 }
145 }
146
147 #[must_use]
149 pub fn is_sparse_directory_placeholder(&self) -> bool {
150 self.mode == MODE_TREE && self.stage() == 0 && self.skip_worktree()
151 }
152
153 const FLAG_EXT_OVERLAY_TREE_SKIP: u16 = 0x8000;
155
156 #[must_use]
157 pub fn overlay_tree_skip_output(&self) -> bool {
158 self.flags_extended
159 .is_some_and(|fe| fe & Self::FLAG_EXT_OVERLAY_TREE_SKIP != 0)
160 }
161
162 fn set_overlay_tree_skip_output(&mut self, value: bool) {
163 let fe = self.flags_extended.get_or_insert(0);
164 if value {
165 *fe |= Self::FLAG_EXT_OVERLAY_TREE_SKIP;
166 } else {
167 *fe &= !Self::FLAG_EXT_OVERLAY_TREE_SKIP;
168 if *fe == 0 {
169 self.flags_extended = None;
170 }
171 }
172 }
173}
174
175#[derive(Debug, Clone, Default)]
177pub struct Index {
178 pub version: u32,
180 pub entries: Vec<IndexEntry>,
182 pub sparse_directories: bool,
184}
185
186#[derive(Debug, Clone, Copy)]
188pub struct IndexLoadOptions {
189 pub expand_sparse_directories: bool,
191}
192
193impl Default for IndexLoadOptions {
194 fn default() -> Self {
195 Self {
196 expand_sparse_directories: true,
197 }
198 }
199}
200
201const INDEX_ENV_INVALID_FALLBACK: u32 = 3;
203const INDEX_CONFIG_INVALID_FALLBACK: u32 = 3;
205const INDEX_FORMAT_LB: u32 = 2;
207const INDEX_FORMAT_UB: u32 = 4;
210
211pub fn get_index_format_from_env() -> Option<u32> {
217 let val = std::env::var("GIT_INDEX_VERSION").ok()?;
218 if val.is_empty() {
219 return None;
220 }
221 match val.parse::<u32>() {
222 Ok(v) if (INDEX_FORMAT_LB..=INDEX_FORMAT_UB).contains(&v) => Some(v),
223 _ => {
224 eprintln!(
225 "warning: GIT_INDEX_VERSION set, but the value is invalid.\n\
226 Using version {INDEX_ENV_INVALID_FALLBACK}"
227 );
228 Some(INDEX_ENV_INVALID_FALLBACK)
229 }
230 }
231}
232
233impl Index {
234 #[must_use]
238 pub fn new() -> Self {
239 let version = get_index_format_from_env().unwrap_or(2);
240 Self {
241 version,
242 entries: Vec::new(),
243 sparse_directories: false,
244 }
245 }
246
247 pub fn new_with_config(
252 config_index_version: Option<&str>,
253 config_many_files: Option<&str>,
254 ) -> Self {
255 if let Some(v) = get_index_format_from_env() {
256 return Self {
257 version: v,
258 entries: Vec::new(),
259 sparse_directories: false,
260 };
261 }
262
263 let many_files = config_truthy(config_many_files);
264 let mut version = if many_files { 4 } else { 2 };
265
266 if let Some(val) = config_index_version {
267 let trimmed = val.trim();
268 if !trimmed.is_empty() {
269 match trimmed.parse::<u32>() {
270 Ok(v) if (INDEX_FORMAT_LB..=INDEX_FORMAT_UB).contains(&v) => {
271 version = v;
272 }
273 _ => {
274 eprintln!(
275 "warning: index.version set, but the value is invalid.\n\
276 Using version {INDEX_CONFIG_INVALID_FALLBACK}"
277 );
278 version = INDEX_CONFIG_INVALID_FALLBACK;
279 }
280 }
281 }
282 }
283
284 Self {
285 version,
286 entries: Vec::new(),
287 sparse_directories: false,
288 }
289 }
290
291 #[must_use]
296 pub fn new_from_config(config: &ConfigSet) -> Self {
297 if let Some(v) = get_index_format_from_env() {
298 return Self {
299 version: v,
300 entries: Vec::new(),
301 sparse_directories: false,
302 };
303 }
304
305 let many_files = config
306 .get_bool("feature.manyFiles")
307 .and_then(|r| r.ok())
308 .unwrap_or(false);
309 let mut version = if many_files { 4 } else { 2 };
310
311 if let Some(val) = config.get("index.version") {
312 let trimmed = val.trim();
313 if !trimmed.is_empty() {
314 match trimmed.parse::<u32>() {
315 Ok(v) if (INDEX_FORMAT_LB..=INDEX_FORMAT_UB).contains(&v) => {
316 version = v;
317 }
318 _ => {
319 eprintln!(
320 "warning: index.version set, but the value is invalid.\n\
321 Using version {INDEX_CONFIG_INVALID_FALLBACK}"
322 );
323 version = INDEX_CONFIG_INVALID_FALLBACK;
324 }
325 }
326 }
327 }
328
329 Self {
330 version,
331 entries: Vec::new(),
332 sparse_directories: false,
333 }
334 }
335
336 pub fn load(path: &Path) -> Result<Self> {
344 match fs::read(path) {
345 Ok(data) => Self::parse(&data),
346 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(Self {
347 sparse_directories: false,
348 ..Self::new()
349 }),
350 Err(e) => Err(Error::Io(e)),
351 }
352 }
353
354 pub fn load_expand_sparse(path: &Path, odb: &Odb) -> Result<Self> {
359 let mut idx = Self::load(path)?;
360 idx.expand_sparse_directory_placeholders(odb)?;
361 Ok(idx)
362 }
363
364 pub fn load_expand_sparse_optional(path: &Path, odb: &Odb) -> Result<Self> {
367 let mut idx = match fs::read(path) {
368 Ok(data) => Self::parse(&data).or_else(|e| match e {
369 Error::IndexError(msg) if msg == "file too short" => Ok(Self::new()),
370 other => Err(other),
371 })?,
372 Err(e) if e.kind() == io::ErrorKind::NotFound => Self::new(),
373 Err(e) => return Err(Error::Io(e)),
374 };
375 idx.expand_sparse_directory_placeholders(odb)?;
376 Ok(idx)
377 }
378
379 #[must_use]
381 pub fn has_sparse_directory_placeholders(&self) -> bool {
382 self.entries
383 .iter()
384 .any(IndexEntry::is_sparse_directory_placeholder)
385 }
386
387 pub fn expand_sparse_directory_placeholders(&mut self, odb: &Odb) -> Result<()> {
392 if !self.has_sparse_directory_placeholders() {
393 return Ok(());
394 }
395 let mut out: Vec<IndexEntry> = Vec::with_capacity(self.entries.len());
396 for entry in self.entries.drain(..) {
397 if entry.is_sparse_directory_placeholder() {
398 let prefix = trim_trailing_slash_bytes(&entry.path);
399 let blobs = flatten_tree_blobs(odb, &entry.oid, prefix)?;
400 out.extend(blobs);
401 } else {
402 out.push(entry);
403 }
404 }
405 self.entries = out;
406 self.sparse_directories = false;
407 self.sort();
408 Ok(())
409 }
410
411 pub fn try_collapse_sparse_directories(
417 &mut self,
418 odb: &Odb,
419 head_tree: &ObjectId,
420 patterns: &[String],
421 cone_mode: bool,
422 enable_sparse_index: bool,
423 ) -> Result<()> {
424 if !enable_sparse_index || !cone_mode {
425 self.sparse_directories = false;
426 return Ok(());
427 }
428
429 let mut prefixes = BTreeSet::<Vec<u8>>::new();
430 for e in &self.entries {
431 if e.stage() != 0 || e.mode == MODE_TREE || !e.skip_worktree() {
432 continue;
433 }
434 collect_directory_prefixes(&e.path, &mut prefixes);
435 }
436
437 let mut collapsed_any = false;
438 let mut ordered: Vec<Vec<u8>> = prefixes.into_iter().collect();
440 ordered.sort_by_key(|p| std::cmp::Reverse(p.len()));
441
442 for pref in ordered {
443 let pref_str = String::from_utf8_lossy(&pref);
444 if directory_in_cone(&pref_str, patterns, cone_mode) {
445 continue;
446 }
447 let Some(subtree_oid) = tree_oid_for_prefix(odb, head_tree, &pref)? else {
448 continue;
449 };
450 let expected = collect_sparse_aware_expected_blobs(
451 odb,
452 &subtree_oid,
453 &pref,
454 patterns,
455 cone_mode,
456 &self.entries,
457 )?;
458 if expected.is_empty() {
459 continue;
460 }
461 let mut matched = Vec::new();
462 for e in &self.entries {
463 if e.stage() != 0 {
464 continue;
465 }
466 if path_under_prefix(&e.path, &pref) && e.mode != MODE_TREE {
467 matched.push(e.clone());
468 }
469 }
470 if matched.len() != expected.len() {
471 continue;
472 }
473 matched.sort_by(|a, b| a.path.cmp(&b.path));
474 let mut exp_sorted = expected;
475 exp_sorted.sort_by(|a, b| a.path.cmp(&b.path));
476 if !matched
477 .iter()
478 .zip(exp_sorted.iter())
479 .all(|(a, b)| a.path == b.path && a.oid == b.oid && a.mode == b.mode)
480 {
481 continue;
482 }
483 if !matched.iter().all(|e| e.skip_worktree()) {
484 continue;
485 }
486
487 let mut path_with_slash = pref.clone();
488 if !path_with_slash.ends_with(b"/") {
489 path_with_slash.push(b'/');
490 }
491 self.entries
492 .retain(|e| e.stage() != 0 || !path_under_prefix(&e.path, &pref));
493 let mut placeholder = IndexEntry {
494 ctime_sec: 0,
495 ctime_nsec: 0,
496 mtime_sec: 0,
497 mtime_nsec: 0,
498 dev: 0,
499 ino: 0,
500 mode: MODE_TREE,
501 uid: 0,
502 gid: 0,
503 size: 0,
504 oid: subtree_oid,
505 flags: path_with_slash.len().min(0xFFF) as u16,
506 flags_extended: Some(0),
507 path: path_with_slash,
508 };
509 placeholder.set_skip_worktree(true);
510 self.add_or_replace(placeholder);
511 collapsed_any = true;
512 }
513
514 if collapsed_any {
515 self.sort();
516 self.sparse_directories = true;
517 } else {
518 self.sparse_directories = false;
519 }
520 Ok(())
521 }
522
523 pub fn parse(data: &[u8]) -> Result<Self> {
529 if data.len() < 12 {
530 return Err(Error::IndexError("file too short".to_owned()));
531 }
532
533 let (body, checksum) = data.split_at(data.len() - 20);
536 if !checksum.iter().all(|&b| b == 0) {
537 let mut hasher = Sha1::new();
538 hasher.update(body);
539 let computed = hasher.finalize();
540 if computed.as_slice() != checksum {
541 return Err(Error::IndexError("SHA-1 checksum mismatch".to_owned()));
542 }
543 }
544
545 let magic = &body[..4];
547 if magic != b"DIRC" {
548 return Err(Error::IndexError("bad magic: expected DIRC".to_owned()));
549 }
550 let version = u32::from_be_bytes(
551 body[4..8]
552 .try_into()
553 .map_err(|_| Error::IndexError("cannot read version".to_owned()))?,
554 );
555 if version != 2 && version != 3 && version != 4 {
556 return Err(Error::IndexError(format!(
557 "unsupported index version {version}"
558 )));
559 }
560 let count = u32::from_be_bytes(
561 body[8..12]
562 .try_into()
563 .map_err(|_| Error::IndexError("cannot read entry count".to_owned()))?,
564 );
565
566 let mut pos = 12usize;
567 let mut entries = Vec::with_capacity(count as usize);
568
569 let mut prev_path: Vec<u8> = Vec::new();
570 for _ in 0..count {
571 let (entry, consumed) = parse_entry(&body[pos..], version, &prev_path)?;
572 prev_path = entry.path.clone();
573 entries.push(entry);
574 pos += consumed;
575 }
576
577 let mut sparse_directories = false;
578 while pos + 8 <= body.len() {
579 let sig = u32::from_be_bytes(
580 body[pos..pos + 4]
581 .try_into()
582 .map_err(|_| Error::IndexError("truncated extension sig".to_owned()))?,
583 );
584 let ext_sz = u32::from_be_bytes(
585 body[pos + 4..pos + 8]
586 .try_into()
587 .map_err(|_| Error::IndexError("truncated extension size".to_owned()))?,
588 ) as usize;
589 pos += 8;
590 if pos + ext_sz > body.len() {
591 return Err(Error::IndexError(
592 "extension overruns index body".to_owned(),
593 ));
594 }
595 if sig == INDEX_EXT_SPARSE_DIRECTORIES {
596 sparse_directories = true;
597 }
598 pos += ext_sz;
599 }
600 if pos != body.len() {
601 return Err(Error::IndexError("junk after index extensions".to_owned()));
602 }
603
604 Ok(Self {
605 version,
606 entries,
607 sparse_directories,
608 })
609 }
610
611 pub fn write(&self, path: &Path) -> Result<()> {
617 let mut sorted = self.clone();
618 sorted.sort();
619
620 let mut body = Vec::new();
621 sorted.serialize_into(&mut body)?;
622
623 let git_dir = path.parent();
624 let config = git_dir.and_then(|d| ConfigSet::load(Some(d), true).ok());
625 let skip_hash = index_skip_hash_for_write(config.as_ref());
626 let checksum: [u8; 20] = if skip_hash {
627 [0u8; 20]
628 } else {
629 let mut hasher = Sha1::new();
630 hasher.update(&body);
631 hasher.finalize().into()
632 };
633
634 let tmp_path = path.with_extension("lock");
635 let pid_path = pid_path_for_lock(&tmp_path);
636 let lockfile_pid_enabled = lockfile_pid_enabled(path);
637
638 let mut lock_file = match fs::OpenOptions::new()
639 .write(true)
640 .create_new(true)
641 .open(&tmp_path)
642 {
643 Ok(file) => file,
644 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
645 let message = build_lock_exists_message(&tmp_path, &pid_path, &e);
646 return Err(Error::Io(io::Error::new(
647 io::ErrorKind::AlreadyExists,
648 message,
649 )));
650 }
651 Err(e) => return Err(Error::Io(e)),
652 };
653
654 let mut wrote_pid_file = false;
655 if lockfile_pid_enabled {
656 if let Err(e) = write_lock_pid_file(&pid_path) {
657 let _ = fs::remove_file(&tmp_path);
658 return Err(Error::Io(e));
659 }
660 wrote_pid_file = true;
661 }
662
663 if let Err(e) = (|| -> io::Result<()> {
664 lock_file.write_all(&body)?;
665 lock_file.write_all(&checksum)?;
666 Ok(())
667 })() {
668 let _ = fs::remove_file(&tmp_path);
669 if wrote_pid_file {
670 let _ = fs::remove_file(&pid_path);
671 }
672 return Err(Error::Io(e));
673 }
674 drop(lock_file);
675
676 if let Err(e) = fs::rename(&tmp_path, path) {
677 let _ = fs::remove_file(&tmp_path);
678 if wrote_pid_file {
679 let _ = fs::remove_file(&pid_path);
680 }
681 return Err(Error::Io(e));
682 }
683 {
684 if wrote_pid_file {
685 let _ = fs::remove_file(&pid_path);
686 }
687 }
688 Ok(())
689 }
690
691 fn serialize_into(&self, out: &mut Vec<u8>) -> Result<()> {
695 let has_extended_flags = self.entries.iter().any(|e| e.flags_extended.is_some());
696 let write_version = if self.version >= 4 {
697 4
698 } else if has_extended_flags {
699 3
700 } else if self.version >= 3 {
701 2
702 } else {
703 self.version
704 };
705 out.extend_from_slice(b"DIRC");
707 out.extend_from_slice(&write_version.to_be_bytes());
708 out.extend_from_slice(&(self.entries.len() as u32).to_be_bytes());
709
710 if write_version == 4 {
711 let mut previous_path: Vec<u8> = Vec::new();
712 for entry in &self.entries {
713 serialize_entry_v4(entry, &mut previous_path, out);
714 }
715 } else {
716 for entry in &self.entries {
717 serialize_entry(entry, write_version, out);
718 }
719 }
720 if self.sparse_directories {
721 out.extend_from_slice(&INDEX_EXT_SPARSE_DIRECTORIES.to_be_bytes());
722 out.extend_from_slice(&0u32.to_be_bytes());
723 }
724 Ok(())
725 }
726
727 pub fn add_or_replace(&mut self, entry: IndexEntry) {
729 let path = &entry.path;
730 let stage = entry.stage();
731 let result = self.entries.binary_search_by(|e| {
733 e.path
734 .as_slice()
735 .cmp(path.as_slice())
736 .then_with(|| e.stage().cmp(&stage))
737 });
738 match result {
739 Ok(pos) => {
740 self.entries[pos] = entry;
742 }
743 Err(pos) => {
744 self.entries.insert(pos, entry);
746 }
747 }
748 }
749
750 pub fn stage_file(&mut self, entry: IndexEntry) {
754 let path = entry.path.clone();
755 self.entries.retain(|e| e.path != path || e.stage() == 0);
757 self.add_or_replace(entry);
759 }
760
761 pub fn remove(&mut self, path: &[u8]) -> bool {
765 let before = self.entries.len();
766 self.entries.retain(|e| e.path != path);
767 self.entries.len() < before
768 }
769
770 pub fn remove_descendants_under_path(&mut self, path: &str) {
776 let prefix = path.as_bytes();
777 if prefix.is_empty() {
778 return;
779 }
780 let plen = prefix.len();
781 self.entries.retain(|e| {
782 let ep = e.path.as_slice();
783 if ep.len() <= plen {
784 return true;
785 }
786 if !ep.starts_with(prefix) {
787 return true;
788 }
789 ep[plen] != b'/'
791 });
792 }
793
794 pub fn sort(&mut self) {
796 self.entries
797 .sort_by(|a, b| a.path.cmp(&b.path).then_with(|| a.stage().cmp(&b.stage())));
798 }
799
800 #[must_use]
802 pub fn get(&self, path: &[u8], stage: u8) -> Option<&IndexEntry> {
803 self.entries
804 .iter()
805 .find(|e| e.path == path && e.stage() == stage)
806 }
807
808 pub fn get_mut(&mut self, path: &[u8], stage: u8) -> Option<&mut IndexEntry> {
810 self.entries
811 .iter_mut()
812 .find(|e| e.path == path && e.stage() == stage)
813 }
814
815 pub fn overlay_tree_on_index(
834 &mut self,
835 repo: &Repository,
836 treeish: &str,
837 prefix: &[u8],
838 ) -> Result<()> {
839 let oid = rev_parse::resolve_revision(repo, treeish)?;
840 let tree_oid = peel_to_tree_oid(repo, oid)?;
841 for e in self.entries.iter_mut() {
842 if e.stage() != 0 {
843 e.set_stage(3);
844 }
845 }
846 self.sort();
847 let has_stage1 = self.entries.iter().any(|e| e.stage() == 1);
848 let mut appended: Vec<IndexEntry> = Vec::new();
849 read_tree_into_overlay(repo, &tree_oid, prefix, &[], has_stage1, &mut appended)?;
850 for e in appended {
851 self.add_or_replace(e);
852 }
853 if !has_stage1 {
854 self.sort();
855 }
856 let mut last_stage0: Option<&[u8]> = None;
857 for e in &mut self.entries {
858 match e.stage() {
859 0 => {
860 last_stage0 = Some(e.path.as_slice());
861 }
862 1 => {
863 if last_stage0.is_some_and(|p| p == e.path.as_slice()) {
864 e.set_overlay_tree_skip_output(true);
865 }
866 }
867 _ => {}
868 }
869 }
870 Ok(())
871 }
872}
873
874fn peel_to_tree_oid(repo: &Repository, oid: ObjectId) -> Result<ObjectId> {
875 let obj = repo.odb.read(&oid)?;
876 match obj.kind {
877 ObjectKind::Tree => Ok(oid),
878 ObjectKind::Commit => {
879 let commit = crate::objects::parse_commit(&obj.data)?;
880 Ok(commit.tree)
881 }
882 ObjectKind::Tag => {
883 let tag = crate::objects::parse_tag(&obj.data)?;
884 peel_to_tree_oid(repo, tag.object)
885 }
886 _ => Err(Error::ObjectNotFound(format!(
887 "cannot peel {oid} to tree for --with-tree"
888 ))),
889 }
890}
891
892fn read_tree_into_overlay(
893 repo: &Repository,
894 tree_oid: &ObjectId,
895 prefix: &[u8],
896 rel_base: &[u8],
897 use_replace_path: bool,
898 out: &mut Vec<IndexEntry>,
899) -> Result<()> {
900 let obj = repo.odb.read(tree_oid)?;
901 if obj.kind != ObjectKind::Tree {
902 return Err(Error::ObjectNotFound(format!(
903 "object {tree_oid} is not a tree"
904 )));
905 }
906 let entries = parse_tree(&obj.data)?;
907 for TreeEntry { mode, name, oid } in entries {
908 if mode == MODE_TREE {
909 let mut path = rel_base.to_vec();
910 if !path.is_empty() {
911 path.push(b'/');
912 }
913 path.extend_from_slice(&name);
914 if !prefix_under_or_equal(prefix, &path) {
915 continue;
916 }
917 read_tree_into_overlay(repo, &oid, prefix, &path, use_replace_path, out)?;
918 continue;
919 }
920 if mode == MODE_GITLINK {
921 continue;
922 }
923 let mut path = rel_base.to_vec();
924 if !path.is_empty() {
925 path.push(b'/');
926 }
927 path.extend_from_slice(&name);
928 if !prefix_under_or_equal(prefix, &path) {
929 continue;
930 }
931 let entry = synthetic_stage1_index_entry(mode, &path, oid);
932 if use_replace_path {
933 if let Some(pos) = out.iter().position(|e| e.path == path && e.stage() == 1) {
934 out[pos] = entry;
935 } else {
936 out.push(entry);
937 }
938 } else {
939 out.push(entry);
940 }
941 }
942 Ok(())
943}
944
945fn prefix_under_or_equal(prefix: &[u8], path: &[u8]) -> bool {
946 if prefix.is_empty() {
947 return true;
948 }
949 if path == prefix {
950 return true;
951 }
952 path.len() > prefix.len() && path.starts_with(prefix) && path[prefix.len()] == b'/'
953}
954
955fn synthetic_stage1_index_entry(mode: u32, path: &[u8], oid: ObjectId) -> IndexEntry {
956 let path_len = path.len().min(0xFFF) as u16;
957 let flags = (1u16 << 12) | path_len;
958 IndexEntry {
959 ctime_sec: 0,
960 ctime_nsec: 0,
961 mtime_sec: 0,
962 mtime_nsec: 0,
963 dev: 0,
964 ino: 0,
965 mode,
966 uid: 0,
967 gid: 0,
968 size: 0,
969 oid,
970 flags,
971 flags_extended: None,
972 path: path.to_vec(),
973 }
974}
975
976fn config_truthy(raw: Option<&str>) -> bool {
977 let Some(val) = raw else {
978 return false;
979 };
980 let lowered = val.trim().to_lowercase();
981 matches!(lowered.as_str(), "true" | "yes" | "1" | "on")
982}
983
984fn index_skip_hash_for_write(config: Option<&ConfigSet>) -> bool {
989 let Some(config) = config else {
990 return false;
991 };
992 let many_files = config
993 .get_bool("feature.manyFiles")
994 .and_then(|r| r.ok())
995 .unwrap_or(false);
996 if many_files {
997 if let Some(Ok(false)) = config.get_bool("index.skipHash") {
998 return false;
999 }
1000 if let Some(Ok(false)) = config.get_bool("index.skiphash") {
1001 return false;
1002 }
1003 return true;
1004 }
1005 for key in ["index.skipHash", "index.skiphash"] {
1006 if let Some(Ok(true)) = config.get_bool(key) {
1007 return true;
1008 }
1009 }
1010 false
1011}
1012
1013fn trim_trailing_slash_bytes(path: &[u8]) -> &[u8] {
1014 path.strip_suffix(b"/").unwrap_or(path)
1015}
1016
1017fn path_under_prefix(path: &[u8], prefix: &[u8]) -> bool {
1018 if path == prefix {
1019 return true;
1020 }
1021 if prefix.is_empty() {
1022 return true;
1023 }
1024 path.len() > prefix.len() && path.starts_with(prefix) && path[prefix.len()] == b'/'
1025}
1026
1027fn directory_in_cone(dir_path: &str, patterns: &[String], cone_mode: bool) -> bool {
1028 crate::sparse_checkout::path_matches_sparse_patterns(dir_path, patterns, cone_mode)
1029}
1030
1031fn collect_directory_prefixes(path: &[u8], out: &mut BTreeSet<Vec<u8>>) {
1032 for (i, &b) in path.iter().enumerate() {
1033 if b == b'/' {
1034 out.insert(path[..i].to_vec());
1035 }
1036 }
1037}
1038
1039fn tree_oid_for_prefix(odb: &Odb, root_tree: &ObjectId, prefix: &[u8]) -> Result<Option<ObjectId>> {
1040 if prefix.is_empty() {
1041 return Ok(Some(*root_tree));
1042 }
1043 let pref_str = String::from_utf8_lossy(prefix);
1044 let components: Vec<&str> = pref_str.split('/').filter(|c| !c.is_empty()).collect();
1045 let mut current = *root_tree;
1046 for comp in components {
1047 let obj = odb.read(¤t)?;
1048 if obj.kind != ObjectKind::Tree {
1049 return Ok(None);
1050 }
1051 let entries = parse_tree(&obj.data)?;
1052 let mut next = None;
1053 for e in entries {
1054 if e.name == comp.as_bytes() {
1055 if e.mode == MODE_TREE {
1056 next = Some(e.oid);
1057 }
1058 break;
1059 }
1060 }
1061 current = match next {
1062 Some(o) => o,
1063 None => return Ok(None),
1064 };
1065 }
1066 Ok(Some(current))
1067}
1068
1069fn collect_sparse_aware_expected_blobs(
1072 odb: &Odb,
1073 tree_oid: &ObjectId,
1074 prefix: &[u8],
1075 patterns: &[String],
1076 cone_mode: bool,
1077 entries: &[IndexEntry],
1078) -> Result<Vec<IndexEntry>> {
1079 let mut out = Vec::new();
1080 walk_sparse_aware(
1081 odb, tree_oid, prefix, patterns, cone_mode, entries, &mut out,
1082 )?;
1083 Ok(out)
1084}
1085
1086fn walk_sparse_aware(
1087 odb: &Odb,
1088 tree_oid: &ObjectId,
1089 prefix: &[u8],
1090 patterns: &[String],
1091 cone_mode: bool,
1092 entries: &[IndexEntry],
1093 out: &mut Vec<IndexEntry>,
1094) -> Result<()> {
1095 let obj = odb.read(tree_oid)?;
1096 if obj.kind != ObjectKind::Tree {
1097 return Err(Error::IndexError(format!("expected tree at {}", tree_oid)));
1098 }
1099 let tree_entries = parse_tree(&obj.data)?;
1100 for te in tree_entries {
1101 let path = if prefix.is_empty() {
1102 te.name.clone()
1103 } else {
1104 let mut p = prefix.to_vec();
1105 p.push(b'/');
1106 p.extend_from_slice(&te.name);
1107 p
1108 };
1109 if te.mode == MODE_TREE {
1110 let path_slash = {
1111 let mut p = path.clone();
1112 p.push(b'/');
1113 p
1114 };
1115 if entries.iter().any(|e| {
1116 e.stage() == 0
1117 && e.is_sparse_directory_placeholder()
1118 && e.path == path_slash
1119 && e.oid == te.oid
1120 }) {
1121 continue;
1122 }
1123 walk_sparse_aware(odb, &te.oid, &path, patterns, cone_mode, entries, out)?;
1124 } else {
1125 let path_len = path.len().min(0xFFF) as u16;
1126 let path_str = String::from_utf8_lossy(&path);
1127 if crate::sparse_checkout::path_matches_sparse_patterns(&path_str, patterns, cone_mode)
1128 {
1129 continue;
1130 }
1131 let mut e = IndexEntry {
1132 ctime_sec: 0,
1133 ctime_nsec: 0,
1134 mtime_sec: 0,
1135 mtime_nsec: 0,
1136 dev: 0,
1137 ino: 0,
1138 mode: te.mode,
1139 uid: 0,
1140 gid: 0,
1141 size: 0,
1142 oid: te.oid,
1143 flags: path_len,
1144 flags_extended: Some(0),
1145 path,
1146 };
1147 e.set_skip_worktree(true);
1148 out.push(e);
1149 }
1150 }
1151 Ok(())
1152}
1153
1154fn flatten_tree_blobs(odb: &Odb, tree_oid: &ObjectId, prefix: &[u8]) -> Result<Vec<IndexEntry>> {
1155 let obj = odb.read(tree_oid)?;
1156 if obj.kind != ObjectKind::Tree {
1157 return Err(Error::IndexError(format!("expected tree at {}", tree_oid)));
1158 }
1159 let entries = parse_tree(&obj.data)?;
1160 let mut out = Vec::new();
1161 for te in entries {
1162 let path = if prefix.is_empty() {
1163 te.name.clone()
1164 } else {
1165 let mut p = prefix.to_vec();
1166 p.push(b'/');
1167 p.extend_from_slice(&te.name);
1168 p
1169 };
1170 if te.mode == MODE_TREE {
1171 let sub = flatten_tree_blobs(odb, &te.oid, &path)?;
1172 out.extend(sub);
1173 } else {
1174 let path_len = path.len().min(0xFFF) as u16;
1175 let mut e = IndexEntry {
1176 ctime_sec: 0,
1177 ctime_nsec: 0,
1178 mtime_sec: 0,
1179 mtime_nsec: 0,
1180 dev: 0,
1181 ino: 0,
1182 mode: te.mode,
1183 uid: 0,
1184 gid: 0,
1185 size: 0,
1186 oid: te.oid,
1187 flags: path_len,
1188 flags_extended: Some(0),
1189 path,
1190 };
1191 e.set_skip_worktree(true);
1192 out.push(e);
1193 }
1194 }
1195 Ok(out)
1196}
1197
1198fn lockfile_pid_enabled(index_path: &Path) -> bool {
1199 let git_dir = match index_path.parent() {
1200 Some(dir) => dir,
1201 None => return false,
1202 };
1203
1204 ConfigSet::load(Some(git_dir), true)
1205 .ok()
1206 .and_then(|cfg| cfg.get_bool("core.lockfilepid"))
1207 .and_then(|res| res.ok())
1208 .unwrap_or(false)
1209}
1210
1211fn pid_path_for_lock(lock_path: &Path) -> std::path::PathBuf {
1212 let file_name = lock_path
1213 .file_name()
1214 .map(|s| s.to_string_lossy().to_string())
1215 .unwrap_or_else(|| "index.lock".to_owned());
1216 let pid_name = if let Some(base) = file_name.strip_suffix(".lock") {
1217 format!("{base}~pid.lock")
1218 } else {
1219 format!("{file_name}~pid.lock")
1220 };
1221 lock_path.with_file_name(pid_name)
1222}
1223
1224fn write_lock_pid_file(pid_path: &Path) -> io::Result<()> {
1225 use std::io::Write as _;
1226 let mut file = fs::OpenOptions::new()
1227 .write(true)
1228 .create(true)
1229 .truncate(true)
1230 .open(pid_path)?;
1231 writeln!(file, "pid {}", std::process::id())?;
1232 Ok(())
1233}
1234
1235fn build_lock_exists_message(lock_path: &Path, pid_path: &Path, err: &io::Error) -> String {
1236 let mut msg = format!("Unable to create '{}': {}.\n\n", lock_path.display(), err);
1237
1238 if let Some(pid) = read_lock_pid(pid_path) {
1239 if is_process_running(pid) {
1240 msg.push_str(&format!(
1241 "Lock is held by process {pid}; if no git process is running, the lock file may be stale (PIDs can be reused)"
1242 ));
1243 } else {
1244 msg.push_str(&format!(
1245 "Lock was held by process {pid}, which is no longer running; the lock file appears to be stale"
1246 ));
1247 }
1248 } else {
1249 msg.push_str(
1250 "Another git process seems to be running in this repository, or the lock file may be stale",
1251 );
1252 }
1253
1254 msg
1255}
1256
1257fn read_lock_pid(pid_path: &Path) -> Option<u64> {
1258 let raw = fs::read_to_string(pid_path).ok()?;
1259 let trimmed = raw.trim();
1260 if let Some(v) = trimmed.strip_prefix("pid ") {
1261 return v.trim().parse::<u64>().ok();
1262 }
1263 trimmed.parse::<u64>().ok()
1264}
1265
1266fn is_process_running(pid: u64) -> bool {
1267 #[cfg(target_os = "linux")]
1268 {
1269 let proc_path = std::path::PathBuf::from(format!("/proc/{pid}"));
1270 proc_path.exists()
1271 }
1272
1273 #[cfg(not(target_os = "linux"))]
1274 {
1275 let status = std::process::Command::new("kill")
1276 .arg("-0")
1277 .arg(pid.to_string())
1278 .status();
1279 status.map(|s| s.success()).unwrap_or(false)
1280 }
1281}
1282
1283fn parse_entry(data: &[u8], version: u32, prev_path: &[u8]) -> Result<(IndexEntry, usize)> {
1285 if data.len() < 62 {
1286 return Err(Error::IndexError("entry too short".to_owned()));
1287 }
1288
1289 let mut pos = 0;
1290
1291 macro_rules! read_u32 {
1292 () => {{
1293 let v = u32::from_be_bytes(
1294 data[pos..pos + 4]
1295 .try_into()
1296 .map_err(|_| Error::IndexError("truncated u32".to_owned()))?,
1297 );
1298 pos += 4;
1299 v
1300 }};
1301 }
1302
1303 let ctime_sec = read_u32!();
1304 let ctime_nsec = read_u32!();
1305 let mtime_sec = read_u32!();
1306 let mtime_nsec = read_u32!();
1307 let dev = read_u32!();
1308 let ino = read_u32!();
1309 let mode = read_u32!();
1310 let uid = read_u32!();
1311 let gid = read_u32!();
1312 let size = read_u32!();
1313
1314 let oid = ObjectId::from_bytes(&data[pos..pos + 20])?;
1315 pos += 20;
1316
1317 let flags = u16::from_be_bytes(
1318 data[pos..pos + 2]
1319 .try_into()
1320 .map_err(|_| Error::IndexError("truncated flags".to_owned()))?,
1321 );
1322 pos += 2;
1323
1324 let flags_extended = if version >= 3 && flags & 0x4000 != 0 {
1325 let fe = u16::from_be_bytes(
1326 data[pos..pos + 2]
1327 .try_into()
1328 .map_err(|_| Error::IndexError("truncated extended flags".to_owned()))?,
1329 );
1330 pos += 2;
1331 Some(fe)
1332 } else {
1333 None
1334 };
1335
1336 let path;
1337 if version == 4 {
1338 let (strip_len, varint_bytes) = read_varint(&data[pos..]);
1340 pos += varint_bytes;
1341 let nul = data[pos..]
1342 .iter()
1343 .position(|&b| b == 0)
1344 .ok_or_else(|| Error::IndexError("v4 entry path missing NUL".to_owned()))?;
1345 let suffix = &data[pos..pos + nul];
1346 pos += nul + 1;
1347 let keep = prev_path.len().saturating_sub(strip_len);
1348 let mut full_path = prev_path[..keep].to_vec();
1349 full_path.extend_from_slice(suffix);
1350 path = full_path;
1351 } else {
1352 let nul = data[pos..]
1354 .iter()
1355 .position(|&b| b == 0)
1356 .ok_or_else(|| Error::IndexError("entry path missing NUL terminator".to_owned()))?;
1357 path = data[pos..pos + nul].to_vec();
1358 pos += nul + 1;
1359 let entry_start = 0usize;
1360 let entry_len = pos - entry_start;
1361 let padded = (entry_len + 7) & !7;
1362 let padding = padded.saturating_sub(entry_len);
1363 pos += padding;
1364 }
1365
1366 Ok((
1367 IndexEntry {
1368 ctime_sec,
1369 ctime_nsec,
1370 mtime_sec,
1371 mtime_nsec,
1372 dev,
1373 ino,
1374 mode,
1375 uid,
1376 gid,
1377 size,
1378 oid,
1379 flags,
1380 flags_extended,
1381 path,
1382 },
1383 pos,
1384 ))
1385}
1386
1387fn write_varint(out: &mut Vec<u8>, mut value: usize) {
1391 loop {
1392 let mut b = (value & 0x7F) as u8;
1393 value >>= 7;
1394 if value != 0 {
1395 b |= 0x80;
1396 }
1397 out.push(b);
1398 if value == 0 {
1399 break;
1400 }
1401 }
1402}
1403
1404fn read_varint(data: &[u8]) -> (usize, usize) {
1405 let mut value: usize = 0;
1406 let mut shift = 0usize;
1407 let mut pos = 0;
1408 loop {
1409 if pos >= data.len() {
1410 break;
1411 }
1412 let byte = data[pos] as usize;
1413 pos += 1;
1414 value |= (byte & 0x7F) << shift;
1415 if byte & 0x80 == 0 {
1416 break;
1417 }
1418 shift += 7;
1419 if shift > 28 {
1421 break;
1422 }
1423 }
1424 (value, pos)
1425}
1426
1427fn serialize_entry_v4(entry: &IndexEntry, previous_path: &mut Vec<u8>, out: &mut Vec<u8>) {
1428 let write_u32 = |out: &mut Vec<u8>, v: u32| out.extend_from_slice(&v.to_be_bytes());
1429
1430 write_u32(out, entry.ctime_sec);
1431 write_u32(out, entry.ctime_nsec);
1432 write_u32(out, entry.mtime_sec);
1433 write_u32(out, entry.mtime_nsec);
1434 write_u32(out, entry.dev);
1435 write_u32(out, entry.ino);
1436 write_u32(out, entry.mode);
1437 write_u32(out, entry.uid);
1438 write_u32(out, entry.gid);
1439 write_u32(out, entry.size);
1440 out.extend_from_slice(entry.oid.as_bytes());
1441
1442 let mut flags = entry.flags;
1443 if entry.flags_extended.is_some() {
1444 flags |= 0x4000;
1445 } else {
1446 flags &= !0x4000;
1447 }
1448 let path_len = entry.path.len().min(0xFFF) as u16;
1449 flags = (flags & 0xF000) | path_len;
1450 out.extend_from_slice(&flags.to_be_bytes());
1451
1452 if let Some(fe) = entry.flags_extended {
1453 out.extend_from_slice(&fe.to_be_bytes());
1454 }
1455
1456 let common = previous_path
1457 .iter()
1458 .zip(entry.path.iter())
1459 .take_while(|(a, b)| a == b)
1460 .count();
1461 let to_remove = previous_path.len().saturating_sub(common);
1462 write_varint(out, to_remove);
1463 out.extend_from_slice(&entry.path[common..]);
1464 out.push(0);
1465
1466 previous_path.clear();
1467 previous_path.extend_from_slice(&entry.path);
1468}
1469
1470fn serialize_entry(entry: &IndexEntry, version: u32, out: &mut Vec<u8>) {
1471 let start = out.len();
1472
1473 let write_u32 = |out: &mut Vec<u8>, v: u32| out.extend_from_slice(&v.to_be_bytes());
1474
1475 write_u32(out, entry.ctime_sec);
1476 write_u32(out, entry.ctime_nsec);
1477 write_u32(out, entry.mtime_sec);
1478 write_u32(out, entry.mtime_nsec);
1479 write_u32(out, entry.dev);
1480 write_u32(out, entry.ino);
1481 write_u32(out, entry.mode);
1482 write_u32(out, entry.uid);
1483 write_u32(out, entry.gid);
1484 write_u32(out, entry.size);
1485 out.extend_from_slice(entry.oid.as_bytes());
1486
1487 let mut flags = entry.flags;
1489 if version >= 3 && entry.flags_extended.is_some() {
1490 flags |= 0x4000;
1491 } else {
1492 flags &= !0x4000;
1493 }
1494 let path_len = entry.path.len().min(0xFFF) as u16;
1496 flags = (flags & 0xF000) | path_len;
1497 out.extend_from_slice(&flags.to_be_bytes());
1498
1499 if version >= 3 {
1500 if let Some(fe) = entry.flags_extended {
1501 out.extend_from_slice(&fe.to_be_bytes());
1502 }
1503 }
1504
1505 out.extend_from_slice(&entry.path);
1506 out.push(0);
1507
1508 let entry_len = out.len() - start;
1510 let padded = (entry_len + 7) & !7;
1511 let padding = padded - entry_len;
1512 for _ in 0..padding {
1513 out.push(0);
1514 }
1515}
1516
1517pub fn entry_from_stat(
1530 path: &Path,
1531 rel_path: &[u8],
1532 oid: ObjectId,
1533 mode: u32,
1534) -> Result<IndexEntry> {
1535 let meta = fs::symlink_metadata(path)?;
1536 Ok(entry_from_metadata(&meta, rel_path, oid, mode))
1537}
1538
1539#[must_use]
1544pub fn entry_from_metadata(
1545 meta: &fs::Metadata,
1546 rel_path: &[u8],
1547 oid: ObjectId,
1548 mode: u32,
1549) -> IndexEntry {
1550 use std::os::unix::fs::MetadataExt;
1551 IndexEntry {
1552 ctime_sec: meta.ctime() as u32,
1553 ctime_nsec: meta.ctime_nsec() as u32,
1554 mtime_sec: meta.mtime() as u32,
1555 mtime_nsec: meta.mtime_nsec() as u32,
1556 dev: meta.dev() as u32,
1557 ino: meta.ino() as u32,
1558 mode,
1559 uid: meta.uid(),
1560 gid: meta.gid(),
1561 size: meta.size() as u32,
1562 oid,
1563 flags: rel_path.len().min(0xFFF) as u16,
1564 flags_extended: None,
1565 path: rel_path.to_vec(),
1566 }
1567}
1568
1569#[must_use]
1579pub fn normalize_mode(raw_mode: u32) -> u32 {
1580 const S_IFMT: u32 = 0o170000;
1581 const S_IFLNK: u32 = 0o120000;
1582 const S_IFREG: u32 = 0o100000;
1583
1584 let fmt = raw_mode & S_IFMT;
1585 if fmt == S_IFLNK {
1586 return MODE_SYMLINK;
1587 }
1588 if fmt == S_IFREG {
1589 if raw_mode & 0o111 != 0 {
1591 return MODE_EXECUTABLE;
1592 }
1593 return MODE_REGULAR;
1594 }
1595 MODE_REGULAR
1597}
1598
1599#[cfg(test)]
1600mod tests {
1601 #![allow(clippy::expect_used, clippy::unwrap_used)]
1602
1603 use super::*;
1604 use tempfile::TempDir;
1605
1606 fn dummy_oid() -> ObjectId {
1607 ObjectId::from_bytes(&[0u8; 20]).unwrap()
1608 }
1609
1610 fn make_entry(path: &str) -> IndexEntry {
1611 IndexEntry {
1612 ctime_sec: 0,
1613 ctime_nsec: 0,
1614 mtime_sec: 0,
1615 mtime_nsec: 0,
1616 dev: 0,
1617 ino: 0,
1618 mode: MODE_REGULAR,
1619 uid: 0,
1620 gid: 0,
1621 size: 0,
1622 oid: dummy_oid(),
1623 flags: path.len().min(0xFFF) as u16,
1624 flags_extended: None,
1625 path: path.as_bytes().to_vec(),
1626 }
1627 }
1628
1629 #[test]
1630 fn round_trip_empty_index() {
1631 let dir = TempDir::new().unwrap();
1632 let path = dir.path().join("index");
1633
1634 let idx = Index::new();
1635 idx.write(&path).unwrap();
1636
1637 let loaded = Index::load(&path).unwrap();
1638 assert_eq!(loaded.entries.len(), 0);
1639 }
1640
1641 #[test]
1642 fn round_trip_with_entries() {
1643 let dir = TempDir::new().unwrap();
1644 let path = dir.path().join("index");
1645
1646 let mut idx = Index::new();
1647 idx.add_or_replace(make_entry("foo.txt"));
1648 idx.add_or_replace(make_entry("bar/baz.txt"));
1649 idx.write(&path).unwrap();
1650
1651 let loaded = Index::load(&path).unwrap();
1652 assert_eq!(loaded.entries.len(), 2);
1653 assert_eq!(loaded.entries[0].path, b"bar/baz.txt");
1654 assert_eq!(loaded.entries[1].path, b"foo.txt");
1655 }
1656
1657 #[test]
1658 fn remove_descendants_under_path_drops_nested_only() {
1659 let mut idx = Index::new();
1660 idx.add_or_replace(make_entry("d/e"));
1661 idx.add_or_replace(make_entry("d-other"));
1662 idx.add_or_replace(make_entry("prefix/d"));
1663 idx.remove_descendants_under_path("d");
1664 let paths: Vec<_> = idx.entries.iter().map(|e| e.path.as_slice()).collect();
1665 assert_eq!(paths, vec![b"d-other".as_slice(), b"prefix/d".as_slice()]);
1666 }
1667
1668 #[test]
1669 fn requested_v4_writes_v4_on_disk() {
1670 let dir = TempDir::new().unwrap();
1671 let path = dir.path().join("index");
1672
1673 let mut idx = Index {
1674 version: 4,
1675 ..Index::default()
1676 };
1677 idx.add_or_replace(make_entry("one"));
1678 idx.add_or_replace(make_entry("two/one"));
1679 idx.write(&path).unwrap();
1680
1681 let data = fs::read(&path).unwrap();
1682 assert_eq!(&data[4..8], &4u32.to_be_bytes());
1683
1684 let loaded = Index::load(&path).unwrap();
1685 assert_eq!(loaded.version, 4);
1686 assert_eq!(loaded.entries[0].path, b"one");
1687 assert_eq!(loaded.entries[1].path, b"two/one");
1688 }
1689}