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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! `ostraka promote` — give an approved run a branch someone can merge.
//!
//! Deliberately stops at the branch. Merging, pushing and opening a pull
//! request are acts a person takes; this command's job is to make sure that
//! what they take them on actually passed the gate.
use crate::workspace::{Repository, Workspace};
use ostraka_runtime::promote::{self, NotPromoted};
use ostraka_runtime::{index, orchestrator};
type Outcome = Result<bool, Box<dyn std::error::Error>>;
pub fn run(workspace: &Workspace, run_id: &str, branch: Option<&str>, json: bool) -> Outcome {
let records_root = workspace.records();
let repo = repository_of(workspace, &records_root, run_id)?;
let config = workspace.config_for(&repo)?;
let result = promote::promote(&repo.path, &records_root, run_id, &config, branch)?;
match result {
Ok(p) => {
if json {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"promoted": true,
"run_id": run_id,
"branch": p.branch,
"commit": p.commit,
"author": p.token.author().to_string(),
"reviewer": p.token.reviewer().to_string(),
}))?
);
} else {
println!(
"promoted {run_id} to {} ({})",
p.branch,
&p.commit[..p.commit.len().min(12)]
);
println!(
" written by {}, reviewed by {}",
p.token.author(),
p.token.reviewer()
);
// Named, not run. Merging is the person's decision, and a tool
// that offers to take it for them is the thing this project
// exists not to be.
println!("\nnothing has been merged. To take it further:");
println!(" git merge --no-ff {}", p.branch);
println!(" gh pr create --head {}", p.branch);
}
Ok(true)
}
Err(why) => {
if json {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"promoted": false,
"run_id": run_id,
"reason": why.to_string(),
}))?
);
} else {
println!("not promoted — {why}");
if let NotPromoted::Refused(_) = why {
println!("\nA run that did not pass the gate cannot be promoted by promoting.");
}
}
Ok(false)
}
}
}
/// The repository a recorded run was made in.
///
/// Read from the record, because a workspace can hold several and promoting
/// into the wrong one would be promoting a commit that is not there. A record
/// written before a workspace could hold more than one names none, and the
/// answer for those is the only repository there was.
pub fn repository_of(
workspace: &Workspace,
records_root: &std::path::Path,
run_id: &str,
) -> Result<Repository, Box<dyn std::error::Error>> {
let named = orchestrator::replay(records_root, run_id)
.ok()
.map(|(record, _)| record.repository)
.filter(|name| !name.is_empty());
workspace.repository(named.as_deref())
}
/// The repository a listed run was made in, for the callers that already have
/// the summary and should not read the record again to learn one field.
pub fn repository_for(
workspace: &Workspace,
run: &index::RunSummary,
) -> Result<Repository, Box<dyn std::error::Error>> {
workspace.repository(Some(run.repository.as_str()).filter(|n| !n.is_empty()))
}