1use std::collections::{HashSet, VecDeque};
8
9use std::collections::HashMap;
10
11use crate::error::{Error, Result};
12use crate::objects::{parse_commit, parse_tag, parse_tree, Object, ObjectId, ObjectKind};
13use crate::refs;
14use crate::repo::Repository;
15
16fn commit_closure_from_all_refs(repo: &Repository) -> Result<HashSet<ObjectId>> {
22 let mut seeds = Vec::new();
23
24 for prefix in &[
25 "refs/heads/",
26 "refs/tags/",
27 "refs/remotes/",
28 "refs/bundles/",
29 ] {
30 if let Ok(entries) = refs::list_refs(&repo.git_dir, prefix) {
31 for (_, oid) in entries {
32 seeds.push(oid);
33 }
34 }
35 }
36
37 let mut seen = HashSet::new();
38 let mut queue: VecDeque<ObjectId> = VecDeque::new();
39
40 for oid in seeds {
41 let obj = match repo.odb.read(&oid) {
42 Ok(o) => o,
43 Err(_) => continue,
44 };
45 match obj.kind {
46 ObjectKind::Commit => {
47 if seen.insert(oid) {
48 queue.push_back(oid);
49 }
50 }
51 ObjectKind::Tag => {
52 if let Some(target) = peel_tag_to_object(repo, &obj.data)? {
53 if let Ok(tobj) = repo.odb.read(&target) {
54 if tobj.kind == ObjectKind::Commit && seen.insert(target) {
55 queue.push_back(target);
56 }
57 }
58 }
59 }
60 _ => {}
61 }
62 }
63
64 while let Some(c) = queue.pop_front() {
65 let obj = repo.odb.read(&c)?;
66 if obj.kind != ObjectKind::Commit {
67 continue;
68 }
69 let commit = parse_commit(&obj.data)?;
70 for p in commit.parents {
71 if seen.insert(p) {
72 queue.push_back(p);
73 }
74 }
75 }
76
77 Ok(seen)
78}
79
80pub fn bundle_prerequisites_connected_to_refs(
86 repo: &Repository,
87 prerequisites: &[ObjectId],
88) -> Result<bool> {
89 if prerequisites.is_empty() {
90 return Ok(true);
91 }
92 let closure = commit_closure_from_all_refs(repo)?;
93 Ok(prerequisites.iter().all(|p| closure.contains(p)))
94}
95
96fn peel_tag_to_object(repo: &Repository, tag_data: &[u8]) -> Result<Option<ObjectId>> {
97 let tag = parse_tag(tag_data)?;
98 let mut oid = tag.object;
99 loop {
100 let obj = repo.odb.read(&oid)?;
101 match obj.kind {
102 ObjectKind::Tag => {
103 let inner = parse_tag(&obj.data)?;
104 oid = inner.object;
105 }
106 _ => return Ok(Some(oid)),
107 }
108 }
109}
110
111fn tree_entry_is_tree(mode: u32) -> bool {
112 mode == 0o040000
113}
114
115fn tree_entry_is_blob(mode: u32) -> bool {
116 matches!(mode, 0o100644 | 0o100755 | 0o120000)
117}
118
119fn walk_commit_graph_for_push(
124 repo: &Repository,
125 roots: &[ObjectId],
126 parent_stop: Option<&HashSet<ObjectId>>,
127) -> Result<bool> {
128 let mut seen_commits = HashSet::new();
129 let mut seen_trees = HashSet::new();
130 let mut seen_blobs = HashSet::new();
131 let mut commit_q: VecDeque<ObjectId> = VecDeque::new();
132 let mut tree_q: VecDeque<ObjectId> = VecDeque::new();
133
134 for &root in roots {
135 let tip_obj = match repo.odb.read(&root) {
136 Err(_) => return Ok(false),
137 Ok(o) => o,
138 };
139 if tip_obj.kind != ObjectKind::Commit {
140 return Err(Error::CorruptObject(format!(
141 "object {root} is not a commit"
142 )));
143 }
144 if seen_commits.insert(root) {
145 commit_q.push_back(root);
146 }
147 }
148
149 while let Some(c) = commit_q.pop_front() {
150 let obj = match repo.odb.read(&c) {
151 Err(_) => return Ok(false),
152 Ok(o) => o,
153 };
154 if obj.kind != ObjectKind::Commit {
155 return Err(Error::CorruptObject(format!("object {c} is not a commit")));
156 }
157 let commit = parse_commit(&obj.data)?;
158 for p in &commit.parents {
159 if parent_stop.is_some_and(|s| s.contains(p)) {
160 continue;
161 }
162 if seen_commits.insert(*p) {
163 commit_q.push_back(*p);
164 }
165 }
166 if seen_trees.insert(commit.tree) {
167 tree_q.push_back(commit.tree);
168 }
169 }
170
171 while let Some(t) = tree_q.pop_front() {
172 let obj = match repo.odb.read(&t) {
173 Err(_) => return Ok(false),
174 Ok(o) => o,
175 };
176 if obj.kind != ObjectKind::Tree {
177 return Err(Error::CorruptObject(format!("object {t} is not a tree")));
178 }
179 for entry in parse_tree(&obj.data)? {
180 if entry.mode == 0o160000 {
181 } else if tree_entry_is_tree(entry.mode) {
183 if seen_trees.insert(entry.oid) {
184 tree_q.push_back(entry.oid);
185 }
186 } else if tree_entry_is_blob(entry.mode) {
187 if !seen_blobs.insert(entry.oid) {
188 continue;
189 }
190 if repo.odb.read(&entry.oid).is_err() {
191 return Ok(false);
192 }
193 }
194 }
195 }
196
197 Ok(true)
198}
199
200pub fn push_tip_objects_exist(repo: &Repository, tip: ObjectId) -> Result<bool> {
206 walk_commit_graph_for_push(repo, &[tip], None)
207}
208
209pub fn push_tip_objects_exist_with_parent_exceptions(
212 repo: &Repository,
213 tip: ObjectId,
214 parent_exceptions: &HashSet<ObjectId>,
215) -> Result<bool> {
216 walk_commit_graph_for_push(repo, &[tip], Some(parent_exceptions))
217}
218
219fn read_object_for_push(
220 repo: &Repository,
221 pack_objects: Option<&HashMap<ObjectId, Object>>,
222 oid: &ObjectId,
223) -> Result<Object> {
224 if let Some(m) = pack_objects {
225 if let Some(o) = m.get(oid) {
226 return Ok(o.clone());
227 }
228 }
229 repo.odb.read(oid)
230}
231
232pub fn push_tip_connected_to_refs(
236 repo: &Repository,
237 tip: ObjectId,
238 extra_root_commits: &HashSet<ObjectId>,
239 pack_objects: Option<&HashMap<ObjectId, Object>>,
240) -> Result<bool> {
241 let mut existing = commit_closure_from_all_refs(repo)?;
242 existing.extend(extra_root_commits.iter().copied());
243
244 if existing.contains(&tip) {
245 return Ok(true);
246 }
247
248 let mut seen_commits = HashSet::new();
249 let mut seen_trees = HashSet::new();
250 let mut seen_blobs = HashSet::new();
251 let mut commit_q: VecDeque<ObjectId> = VecDeque::new();
252 let mut tree_q: VecDeque<ObjectId> = VecDeque::new();
253
254 seen_commits.insert(tip);
255 commit_q.push_back(tip);
256
257 while let Some(c) = commit_q.pop_front() {
258 let obj = match read_object_for_push(repo, pack_objects, &c) {
259 Err(_) => return Ok(false),
260 Ok(o) => o,
261 };
262 if obj.kind != ObjectKind::Commit {
263 return Err(Error::CorruptObject(format!("object {c} is not a commit")));
264 }
265 let commit = parse_commit(&obj.data)?;
266 for p in &commit.parents {
267 if existing.contains(p) {
268 continue;
269 }
270 if seen_commits.insert(*p) {
271 commit_q.push_back(*p);
272 }
273 }
274 if seen_trees.insert(commit.tree) {
275 tree_q.push_back(commit.tree);
276 }
277 }
278
279 while let Some(t) = tree_q.pop_front() {
280 let obj = match read_object_for_push(repo, pack_objects, &t) {
281 Err(_) => return Ok(false),
282 Ok(o) => o,
283 };
284 if obj.kind != ObjectKind::Tree {
285 return Err(Error::CorruptObject(format!("object {t} is not a tree")));
286 }
287 for entry in parse_tree(&obj.data)? {
288 if entry.mode == 0o160000 {
289 } else if tree_entry_is_tree(entry.mode) {
291 if seen_trees.insert(entry.oid) {
292 tree_q.push_back(entry.oid);
293 }
294 } else if tree_entry_is_blob(entry.mode) {
295 if !seen_blobs.insert(entry.oid) {
296 continue;
297 }
298 if read_object_for_push(repo, pack_objects, &entry.oid).is_err() {
299 return Ok(false);
300 }
301 }
302 }
303 }
304
305 Ok(true)
306}
307
308pub fn diagnose_push_connectivity_failure(
313 repo: &Repository,
314 tip: ObjectId,
315 extra_root_commits: &HashSet<ObjectId>,
316 pack_objects: Option<&HashMap<ObjectId, Object>>,
317) -> Result<Option<(ObjectId, ObjectId)>> {
318 let mut existing = commit_closure_from_all_refs(repo)?;
319 existing.extend(extra_root_commits.iter().copied());
320
321 if existing.contains(&tip) {
322 return Ok(None);
323 }
324
325 let mut seen_commits = HashSet::new();
326 let mut seen_trees = HashSet::new();
327 let mut seen_blobs = HashSet::new();
328 let mut commit_q: VecDeque<ObjectId> = VecDeque::new();
329 let mut tree_q: VecDeque<ObjectId> = VecDeque::new();
330
331 seen_commits.insert(tip);
332 commit_q.push_back(tip);
333
334 while let Some(c) = commit_q.pop_front() {
335 let obj = match read_object_for_push(repo, pack_objects, &c) {
336 Err(_) => return Ok(Some((c, tip))),
337 Ok(o) => o,
338 };
339 if obj.kind != ObjectKind::Commit {
340 return Err(Error::CorruptObject(format!("object {c} is not a commit")));
341 }
342 let commit = parse_commit(&obj.data)?;
343 for p in &commit.parents {
344 if existing.contains(p) {
345 continue;
346 }
347 if read_object_for_push(repo, pack_objects, p).is_err() {
348 return Ok(Some((*p, c)));
349 }
350 if seen_commits.insert(*p) {
351 commit_q.push_back(*p);
352 }
353 }
354 if seen_trees.insert(commit.tree) {
355 tree_q.push_back(commit.tree);
356 }
357 }
358
359 while let Some(t) = tree_q.pop_front() {
360 let obj = match read_object_for_push(repo, pack_objects, &t) {
361 Err(_) => return Ok(Some((t, tip))),
362 Ok(o) => o,
363 };
364 if obj.kind != ObjectKind::Tree {
365 return Err(Error::CorruptObject(format!("object {t} is not a tree")));
366 }
367 for entry in parse_tree(&obj.data)? {
368 if entry.mode == 0o160000 {
369 continue;
370 } else if tree_entry_is_tree(entry.mode) {
371 if seen_trees.insert(entry.oid) {
372 tree_q.push_back(entry.oid);
373 }
374 } else if tree_entry_is_blob(entry.mode) {
375 if !seen_blobs.insert(entry.oid) {
376 continue;
377 }
378 if read_object_for_push(repo, pack_objects, &entry.oid).is_err() {
379 return Ok(Some((entry.oid, tip)));
380 }
381 }
382 }
383 }
384
385 Ok(None)
386}