gix_index/extension/
link.rs

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