gix_index/extension/
link.rs

1use crate::extension::{Link, Signature};
2
3/// The signature of the link extension.
4pub const SIGNATURE: Signature = *b"link";
5
6/// Bitmaps to know which entries to delete or replace, even though details are still unknown.
7#[derive(Clone)]
8pub struct Bitmaps {
9    /// A bitmap to signal which entries to delete, maybe.
10    pub delete: gix_bitmap::ewah::Vec,
11    /// A bitmap to signal which entries to replace, maybe.
12    pub replace: gix_bitmap::ewah::Vec,
13}
14
15///
16pub mod decode {
17
18    /// The error returned when decoding link extensions.
19    #[derive(Debug, thiserror::Error)]
20    #[allow(missing_docs)]
21    pub enum Error {
22        #[error("{0}")]
23        Corrupt(&'static str),
24        #[error("{kind} bitmap corrupt")]
25        BitmapDecode {
26            err: gix_bitmap::ewah::decode::Error,
27            kind: &'static str,
28        },
29    }
30
31    impl From<std::num::TryFromIntError> for Error {
32        fn from(_: std::num::TryFromIntError) -> Self {
33            Self::Corrupt("error in bitmap iteration trying to convert from u64 to usize")
34        }
35    }
36}
37
38pub(crate) fn decode(data: &[u8], object_hash: gix_hash::Kind) -> Result<Link, decode::Error> {
39    let (id, data) = data
40        .split_at_checked(object_hash.len_in_bytes())
41        .ok_or(decode::Error::Corrupt(
42            "link extension too short to read share index checksum",
43        ))
44        .map(|(id, d)| (gix_hash::ObjectId::from_bytes_or_panic(id), d))?;
45
46    if data.is_empty() {
47        return Ok(Link {
48            shared_index_checksum: id,
49            bitmaps: None,
50        });
51    }
52
53    let (delete, data) =
54        gix_bitmap::ewah::decode(data).map_err(|err| decode::Error::BitmapDecode { kind: "delete", err })?;
55    let (replace, data) =
56        gix_bitmap::ewah::decode(data).map_err(|err| decode::Error::BitmapDecode { kind: "replace", err })?;
57
58    if !data.is_empty() {
59        return Err(decode::Error::Corrupt("garbage trailing link extension"));
60    }
61
62    Ok(Link {
63        shared_index_checksum: id,
64        bitmaps: Some(Bitmaps { delete, replace }),
65    })
66}
67
68impl Link {
69    pub(crate) fn dissolve_into(
70        self,
71        split_index: &mut crate::File,
72        object_hash: gix_hash::Kind,
73        skip_hash: bool,
74        options: crate::decode::Options,
75    ) -> Result<(), crate::file::init::Error> {
76        let shared_index_path = split_index
77            .path
78            .parent()
79            .expect("split index file in .git folder")
80            .join(format!("sharedindex.{}", self.shared_index_checksum));
81        let mut shared_index = crate::File::at(
82            shared_index_path,
83            object_hash,
84            skip_hash,
85            crate::decode::Options {
86                expected_checksum: self.shared_index_checksum.into(),
87                ..options
88            },
89        )?;
90
91        if let Some(bitmaps) = self.bitmaps {
92            let mut split_entry_index = 0;
93
94            let mut err = None;
95            bitmaps.replace.for_each_set_bit(|replace_index| {
96                let shared_entry = match shared_index.entries.get_mut(replace_index) {
97                    Some(e) => e,
98                    None => {
99                        err = decode::Error::Corrupt("replace bitmap length exceeds shared index length - more entries in bitmap than found in shared index").into();
100                        return None
101                    }
102                };
103
104                if shared_entry.flags.contains(crate::entry::Flags::REMOVE) {
105                    err = decode::Error::Corrupt("entry is marked as both replace and delete").into();
106                    return None
107                }
108
109                let split_entry = match split_index.entries.get(split_entry_index) {
110                    Some(e) => e,
111                    None => {
112                        err = decode::Error::Corrupt("replace bitmap length exceeds split index length - more entries in bitmap than found in split index").into();
113                        return None
114                    }
115                };
116                if !split_entry.path.is_empty() {
117                    err = decode::Error::Corrupt("paths in split index entries that are for replacement should be empty").into();
118                    return None
119                }
120                if shared_entry.path.is_empty() {
121                    err = decode::Error::Corrupt("paths in shared index entries that are replaced should not be empty").into();
122                    return None
123                }
124                shared_entry.stat = split_entry.stat;
125                shared_entry.id = split_entry.id;
126                shared_entry.flags = split_entry.flags;
127                shared_entry.mode = split_entry.mode;
128
129                split_entry_index += 1;
130                Some(())
131            });
132            if let Some(err) = err {
133                return Err(err.into());
134            }
135
136            let split_index_path_backing = std::mem::take(&mut split_index.path_backing);
137            for mut split_entry in split_index.entries.drain(split_entry_index..) {
138                let start = shared_index.path_backing.len();
139                let split_index_path = split_entry.path.clone();
140
141                split_entry.path = start..start + split_entry.path.len();
142                shared_index.entries.push(split_entry);
143
144                shared_index
145                    .path_backing
146                    .extend_from_slice(&split_index_path_backing[split_index_path]);
147            }
148
149            bitmaps.delete.for_each_set_bit(|delete_index| {
150                let shared_entry = match shared_index.entries.get_mut(delete_index) {
151                    Some(e) => e,
152                    None => {
153                        err = decode::Error::Corrupt("delete bitmap length exceeds shared index length - more entries in bitmap than found in shared index").into();
154                        return None
155                    }
156                };
157                shared_entry.flags.insert(crate::entry::Flags::REMOVE);
158                Some(())
159            });
160            if let Some(err) = err {
161                return Err(err.into());
162            }
163
164            shared_index
165                .entries
166                .retain(|e| !e.flags.contains(crate::entry::Flags::REMOVE));
167
168            let mut shared_entries = std::mem::take(&mut shared_index.entries);
169            shared_entries.sort_by(|a, b| a.cmp(b, &shared_index.state));
170
171            split_index.entries = shared_entries;
172            split_index.path_backing = std::mem::take(&mut shared_index.path_backing);
173        }
174
175        Ok(())
176    }
177}