git_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: git_bitmap::ewah::Vec,
14    /// A bitmap to signal which entries to replace, maybe.
15    pub replace: git_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: git_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: git_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)| (git_hash::ObjectId::from(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        git_bitmap::ewah::decode(data).map_err(|err| decode::Error::BitmapDecode { kind: "delete", err })?;
57    let (replace, data) =
58        git_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: git_hash::Kind,
75        options: crate::decode::Options,
76    ) -> Result<(), crate::file::init::Error> {
77        let shared_index_path = split_index
78            .path
79            .parent()
80            .expect("split index file in .git folder")
81            .join(format!("sharedindex.{}", self.shared_index_checksum));
82        let mut shared_index = crate::File::at(
83            &shared_index_path,
84            object_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}