git-ents.gitmain
⌘K
foforge
inbox.rs105 lines · 3.3 KB · rusthistorycomment on this file
1//! `git ents inbox`: list entities awaiting adoption and adopt them onto
2//! their canonical ref (`sync.adoption-machinery`,
3//! `sync.adoption-no-cherry-pick`).
4
5use ents_model::namespace;
6use ents_sync::resolve::{Heads, Merged, merge_heads};
7use gix_ref_store::RefStoreRead;
8
9use super::{actor, signer};
10use crate::error::{Error, Result};
11use crate::mutate::outcome_to_result;
12use crate::root::LocalRoot;
13
14/// `git ents inbox list`: every `refs/meta/inbox/<member>/<id>` entry.
15///
16/// # Errors
17///
18/// Propagates a ref-store read failure.
19pub fn list(root: &LocalRoot) -> Result<Vec<String>> {
20 let mut out = Vec::new();
21 for entry in root.refs.iter_prefix("refs/meta/inbox/")? {
22 let (name, _) = entry?;
23 let path = name.as_bstr().to_string();
24 if let Some(rest) = path.strip_prefix("refs/meta/inbox/") {
25 out.push(rest.to_owned());
26 }
27 }
28 Ok(out)
29}
30
31/// `git ents inbox adopt`: fold `entry` (`<member>/<id>`) onto its
32/// canonical ref (`refs/meta/<id>`) via [`merge_heads`], keeping the
33/// author's original signed commit in ancestry
34/// (`sync.adoption-no-cherry-pick`).
35///
36/// # Errors
37///
38/// [`Error::NotFound`] if `entry` has no inbox ref; [`Error::InvalidArgument`]
39/// on a merge conflict (a human must resolve it before adoption can
40/// complete — this phase does not implement interactive conflict
41/// resolution); otherwise see [`crate::mutate::outcome_to_result`].
42pub fn adopt(root: &LocalRoot, entry: &str, key: Option<std::path::PathBuf>) -> Result<()> {
43 let Some((member, id)) = entry.split_once('/') else {
44 return Err(Error::InvalidArgument(format!(
45 "expected <member>/<id>, got {entry:?}"
46 )));
47 };
48 let inbox_ref = namespace::inbox_ref(&ents_model::MemberId::new(member), id)?;
49 let Some(theirs) = root.refs.get(inbox_ref.as_ref())? else {
50 return Err(Error::NotFound {
51 what: format!("inbox entry {entry}"),
52 });
53 };
54 let canonical: gix::refs::FullName = format!("refs/meta/{id}")
55 .try_into()
56 .map_err(|_source| Error::InvalidArgument(format!("bad canonical ref for {id}")))?;
57 let ours = root.refs.get(canonical.as_ref())?;
58
59 let signer = signer(root, key)?;
60 let author = actor(&signer);
61 let heads = Heads {
62 refname: canonical.clone(),
63 ours,
64 theirs,
65 };
66 let merged = merge_heads(
67 &root.objects,
68 &heads,
69 &author,
70 &format!("Adopt {entry}"),
71 |payload| signer.sign(payload),
72 )?;
73 let tip = match merged {
74 Merged::Tip(tip) => tip,
75 Merged::Conflict(paths) => {
76 let rendered = paths
77 .iter()
78 .map(|p| p.to_string())
79 .collect::<Vec<_>>()
80 .join(", ");
81 return Err(Error::InvalidArgument(format!(
82 "adoption conflict at: {rendered}"
83 )));
84 }
85 };
86
87 let proposal = ents_receive::Proposal {
88 transitions: vec![ents_receive::RefTransition {
89 name: canonical,
90 old: ours,
91 new: Some(tip),
92 }],
93 objects: vec![tip],
94 auth: None,
95 };
96 let outcome = ents_receive::receive(
97 &root.refs,
98 &root.objects,
99 &root.events,
100 &proposal,
101 root.mode(),
102 )?;
103 outcome_to_result(outcome, ours)?;
104 Ok(())
105}