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