crates/kernel/ents-sync/src/objects.rs
objects.rshistorycomment 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 | |
| 6 | use std::collections::HashSet; |
| 7 | |
| 8 | use gix_hash::ObjectId; |
| 9 | use gix_object::{CommitRef, Find, Kind, TreeRef, Write}; |
| 10 | |
| 11 | use crate::error::{Error, Result}; |
| 12 | |
| 13 | /// The tree recorded by the commit at `oid`. |
| 14 | pub(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. |
| 29 | pub(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`). |
| 51 | pub(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`). |
| 75 | pub(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 | } |