1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! Publishing a complete branch that no session holds.
//!
//! The verb that was missing. `publish` takes a session token, `integrate` lands a
//! branch on a **local** base, and `recover` publishes interrupted work — so a
//! finished branch belonging to an identity whose merge path is a change request
//! had no onevcs verb at all, and the only way out of the refusals was raw `git
//! push` and `gh pr create`. This is that way out, under the identity's own
//! rules-resolved policy.
//!
//! It is [`crate::branch`] plus one precondition, which is the mirror of
//! `recover`'s: interrupted work is refused here and named over there, because
//! publishing a branch whose step never finished means writing an attestation, and
//! only the verb that earns one may write it.
use std::path::Path;
use crate::branch::{self, Verb};
use crate::error::{Error, Result};
use crate::host::Hosting;
use crate::publish::{PublishOutcome, Subject};
use crate::registry::Registry;
use crate::rules::MergePolicy;
use crate::stream::Stream;
/// Verify and publish a complete branch under its identity's policy.
///
/// One parameter per operand the verb takes, which is one more than clippy counts
/// as a list worth grouping: the four a caller may say something with — the title,
/// the body, the policy, and the host to publish through — are exactly what
/// `onevcs publish-branch` accepts, and a struct holding them here would be a
/// second shape for the same arguments that `recover::run` passes positionally.
#[expect(
clippy::too_many_arguments,
reason = "the parameters are the verb's own operands, kept in step with recover::run"
)]
pub fn run(
registry: &Registry,
repo: &Path,
branch: &str,
title: Option<Subject>,
body: Option<String>,
policy: Option<MergePolicy>,
hosting: &dyn Hosting,
stream: &mut Stream,
) -> Result<PublishOutcome> {
let landing = branch::prepare(registry, Verb::PublishBranch, repo, branch, policy)?;
let unattested = landing.unattested()?;
if !unattested.is_empty() {
return Err(Error::Invalid {
reason: format!(
"branch {branch:?} carries incomplete provenance ({} unattested marker(s)): a \
step stopped before it finished, and publishing it means attesting that a green \
verification cleared what stopped. `publish-branch` publishes completed work; \
land this one with `{}`, which writes that attestation",
unattested.len(),
landing.command_for(Verb::Recover),
),
});
}
if let Some(prefix) = landing.unrecognized()?.first() {
// A marker under a prefix this host does not read is still a marker, and it
// is the one shape that would otherwise be published here as finished work:
// nothing recognizes it, so nothing refuses it.
return Err(Error::Invalid {
reason: landing.unreadable_prefix(prefix),
});
}
landing.sync_change_base(stream)?;
landing.publish(title, body, hosting, stream)
}