git-ents.gitmain
⌘K
foforge
objects.rs111 lines · 3.8 KB · rusthistorycomment on this file
1//! Object-graph walks over gitoxide's `Find`/`Write` seams — the plumbing
2//! shared by the merge machinery ([`crate::resolve`]) and forge transfer
3//! ([`crate::transfer`]). No private object-access trait: gitoxide's own
4//! traits are the seam (`arch.no-object-store-trait`).
5
6use std::collections::HashSet;
7
8use gix_hash::ObjectId;
9use gix_object::{CommitRef, Find, Kind, TreeRef, Write};
10
11use crate::error::{Error, Result};
12
13/// The tree recorded by the commit at `oid`.
14pub(crate) fn commit_tree(objects: &impl Find, oid: ObjectId) -> Result<ObjectId> {
15 let mut buf = Vec::new();
16 let data = objects
17 .try_find(&oid, &mut buf)
18 .map_err(|source| Error::Object { oid, source })?
19 .ok_or(Error::Missing { oid })?;
20 let commit = CommitRef::from_bytes(data.data, oid.kind()).map_err(|e| Error::Decode {
21 oid,
22 detail: e.to_string(),
23 })?;
24 Ok(commit.tree())
25}
26
27/// The parents of the commit at `oid`; an empty vec for a non-commit or a
28/// root commit, so an incomplete (shallow) history simply ends a walk.
29pub(crate) fn parents(objects: &impl Find, oid: ObjectId) -> Result<Vec<ObjectId>> {
30 let mut buf = Vec::new();
31 let Some(data) = objects
32 .try_find(&oid, &mut buf)
33 .map_err(|source| Error::Object { oid, source })?
34 else {
35 return Ok(Vec::new());
36 };
37 if data.kind != Kind::Commit {
38 return Ok(Vec::new());
39 }
40 let commit = CommitRef::from_bytes(data.data, oid.kind()).map_err(|e| Error::Decode {
41 oid,
42 detail: e.to_string(),
43 })?;
44 Ok(commit.parents().collect())
45}
46
47/// Whether `descendant` reaches `ancestor` by parent edges (inclusive) —
48/// the DAG sense of a fast-forward. Mirrors the gate's own descent check so
49/// fetch advances a ref only when the remote truly descends from the local
50/// tip (`gate.fast-forward`).
51pub(crate) fn descends_from(
52 objects: &impl Find,
53 descendant: ObjectId,
54 ancestor: ObjectId,
55) -> Result<bool> {
56 let mut stack = vec![descendant];
57 let mut seen = HashSet::new();
58 while let Some(oid) = stack.pop() {
59 if oid == ancestor {
60 return Ok(true);
61 }
62 if !seen.insert(oid) {
63 continue;
64 }
65 stack.extend(parents(objects, oid)?);
66 }
67 Ok(false)
68}
69
70/// Copy every object reachable from `root` — the commit, its whole parent
71/// chain, and every tree and blob those commits record — from `src` into
72/// `dst`. Commit objects are copied verbatim, so their `gpgsig` signatures
73/// travel with them: fetching a ref moves the complete audit history and
74/// the signatures needed to verify it (`sync.forge-transfer`).
75pub(crate) fn copy_closure(src: &impl Find, dst: &impl Write, root: ObjectId) -> Result<()> {
76 let mut stack = vec![root];
77 let mut seen = HashSet::new();
78 while let Some(oid) = stack.pop() {
79 if !seen.insert(oid) {
80 continue;
81 }
82 let mut buf = Vec::new();
83 let data = src
84 .try_find(&oid, &mut buf)
85 .map_err(|source| Error::Object { oid, source })?
86 .ok_or(Error::Missing { oid })?;
87 let kind = data.kind;
88 let bytes = data.data.to_vec();
89 dst.write_buf(kind, &bytes)?;
90 match kind {
91 Kind::Commit => {
92 let commit =
93 CommitRef::from_bytes(&bytes, oid.kind()).map_err(|e| Error::Decode {
94 oid,
95 detail: e.to_string(),
96 })?;
97 stack.push(commit.tree());
98 stack.extend(commit.parents());
99 }
100 Kind::Tree => {
101 let tree = TreeRef::from_bytes(&bytes, oid.kind()).map_err(|e| Error::Decode {
102 oid,
103 detail: e.to_string(),
104 })?;
105 stack.extend(tree.entries.iter().map(|e| e.oid.to_owned()));
106 }
107 Kind::Blob | Kind::Tag => {}
108 }
109 }
110 Ok(())
111}