git_index/extension/
link.rs1use crate::{
2 extension::{Link, Signature},
3 util::split_at_pos,
4};
5
6pub const SIGNATURE: Signature = *b"link";
8
9#[derive(Clone)]
11pub struct Bitmaps {
12 pub delete: git_bitmap::ewah::Vec,
14 pub replace: git_bitmap::ewah::Vec,
16}
17
18pub mod decode {
20
21 #[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}