git_index/extension/
untracked_cache.rs

1use std::convert::TryInto;
2
3use bstr::BString;
4use git_hash::ObjectId;
5
6use crate::{
7    entry,
8    extension::{Signature, UntrackedCache},
9    util::{read_u32, split_at_byte_exclusive, split_at_pos, var_int},
10};
11
12/// A structure to track filesystem stat information along with an object id, linking a worktree file with what's in our ODB.
13#[derive(Clone)]
14pub struct OidStat {
15    /// The file system stat information
16    pub stat: entry::Stat,
17    /// The id of the file in our ODB.
18    pub id: ObjectId,
19}
20
21/// A directory with information about its untracked files, and its sub-directories
22#[derive(Clone)]
23pub struct Directory {
24    /// The directories name, or an empty string if this is the root directory.
25    pub name: BString,
26    /// Untracked files and directory names
27    pub untracked_entries: Vec<BString>,
28    /// indices for sub-directories similar to this one.
29    pub sub_directories: Vec<usize>,
30
31    /// The directories stat data, if available or valid // TODO: or is it the exclude file?
32    pub stat: Option<entry::Stat>,
33    /// The oid of a .gitignore file, if it exists
34    pub exclude_file_oid: Option<ObjectId>,
35    /// TODO: figure out what this really does
36    pub check_only: bool,
37}
38
39/// Only used as an indicator
40pub const SIGNATURE: Signature = *b"UNTR";
41
42// #[allow(unused)]
43/// Decode an untracked cache extension from `data`, assuming object hashes are of type `object_hash`.
44pub fn decode(data: &[u8], object_hash: git_hash::Kind) -> Option<UntrackedCache> {
45    if !data.last().map(|b| *b == 0).unwrap_or(false) {
46        return None;
47    }
48    let (identifier_len, data) = var_int(data)?;
49    let (identifier, data) = split_at_pos(data, identifier_len.try_into().ok()?)?;
50
51    let hash_len = object_hash.len_in_bytes();
52    let (info_exclude, data) = decode_oid_stat(data, hash_len)?;
53    let (excludes_file, data) = decode_oid_stat(data, hash_len)?;
54    let (dir_flags, data) = read_u32(data)?;
55    let (exclude_filename_per_dir, data) = split_at_byte_exclusive(data, 0)?;
56
57    let (num_directory_blocks, data) = var_int(data)?;
58
59    let mut res = UntrackedCache {
60        identifier: identifier.into(),
61        info_exclude: (!info_exclude.id.is_null()).then_some(info_exclude),
62        excludes_file: (!excludes_file.id.is_null()).then_some(excludes_file),
63        exclude_filename_per_dir: exclude_filename_per_dir.into(),
64        dir_flags,
65        directories: Vec::new(),
66    };
67    if num_directory_blocks == 0 {
68        return data.is_empty().then_some(res);
69    }
70
71    let num_directory_blocks = num_directory_blocks.try_into().ok()?;
72    let directories = &mut res.directories;
73    directories.reserve(num_directory_blocks);
74
75    let data = decode_directory_block(data, directories)?;
76    if directories.len() != num_directory_blocks {
77        return None;
78    }
79    let (valid, data) = git_bitmap::ewah::decode(data).ok()?;
80    let (check_only, data) = git_bitmap::ewah::decode(data).ok()?;
81    let (hash_valid, mut data) = git_bitmap::ewah::decode(data).ok()?;
82
83    if valid.num_bits() > num_directory_blocks
84        || check_only.num_bits() > num_directory_blocks
85        || hash_valid.num_bits() > num_directory_blocks
86    {
87        return None;
88    }
89
90    check_only.for_each_set_bit(|index| {
91        directories[index].check_only = true;
92        Some(())
93    })?;
94    valid.for_each_set_bit(|index| {
95        let (stat, rest) = crate::decode::stat(data)?;
96        directories[index].stat = stat.into();
97        data = rest;
98        Some(())
99    });
100    hash_valid.for_each_set_bit(|index| {
101        let (hash, rest) = split_at_pos(data, hash_len)?;
102        data = rest;
103        directories[index].exclude_file_oid = ObjectId::from(hash).into();
104        Some(())
105    });
106
107    // null-byte checked in the beginning
108    if data.len() != 1 {
109        return None;
110    }
111    res.into()
112}
113
114fn decode_directory_block<'a>(data: &'a [u8], directories: &mut Vec<Directory>) -> Option<&'a [u8]> {
115    let (num_untracked, data) = var_int(data)?;
116    let (num_dirs, data) = var_int(data)?;
117    let (name, mut data) = split_at_byte_exclusive(data, 0)?;
118    let mut untracked_entries = Vec::<BString>::with_capacity(num_untracked.try_into().ok()?);
119    for _ in 0..num_untracked {
120        let (name, rest) = split_at_byte_exclusive(data, 0)?;
121        data = rest;
122        untracked_entries.push(name.into());
123    }
124
125    let index = directories.len();
126    directories.push(Directory {
127        name: name.into(),
128        untracked_entries,
129        sub_directories: Vec::with_capacity(num_dirs.try_into().ok()?),
130        // the following are set later through their bitmaps
131        stat: None,
132        exclude_file_oid: None,
133        check_only: false,
134    });
135
136    for _ in 0..num_dirs {
137        let subdir_index = directories.len();
138        let rest = decode_directory_block(data, directories)?;
139        data = rest;
140        directories[index].sub_directories.push(subdir_index);
141    }
142
143    data.into()
144}
145
146fn decode_oid_stat(data: &[u8], hash_len: usize) -> Option<(OidStat, &[u8])> {
147    let (stat, data) = crate::decode::stat(data)?;
148    let (hash, data) = split_at_pos(data, hash_len)?;
149    Some((
150        OidStat {
151            stat,
152            id: ObjectId::from(hash),
153        },
154        data,
155    ))
156}