mod activity;
mod command_record;
mod effects;
pub(crate) mod owner;
mod process_identity;
mod reading;
pub(crate) use reading::catalog;
mod recording;
pub mod status;
pub mod stop;
use std::fmt::{self, Display};
use std::path::{Path, PathBuf};
use std::sync::{LazyLock, Mutex, MutexGuard, PoisonError};
use crate::config::UserConfig;
use crate::harness::Choice;
use crate::issue::{IssueUrl, Repo};
use crate::session::Logged;
use activity::Kind;
pub use command_record::StageName as Stage;
pub(crate) use command_record::{TicketRecord, TicketState};
use effects::OnMachine;
use recording::Record;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandKind {
Issue,
Architect,
Pickup,
Secure,
}
impl CommandKind {
pub fn folder(self) -> &'static str {
match self {
Self::Issue => "issue",
Self::Architect => "architect",
Self::Pickup => "pickup",
Self::Secure => "secure",
}
}
pub fn named(name: &str) -> Option<Self> {
match name {
"issue" => Some(Self::Issue),
"architect" => Some(Self::Architect),
"pickup" => Some(Self::Pickup),
"secure" => Some(Self::Secure),
_ => None,
}
}
}
pub enum Begin<'a> {
Run(&'a IssueUrl),
ChildRun(&'a str, CommandKind),
ArchitectRun,
PickupRun,
SecurityRun,
}
#[derive(Clone, Copy)]
pub enum Work<'a> {
Run(&'a IssueUrl),
BaseFix(&'a IssueUrl),
SpecRun(&'a IssueUrl),
PickupRun(&'a IssueUrl),
ArchitectRun(&'a Repo),
SecurityRun(&'a Repo),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Pass {
Architect,
Pickup,
Security,
}
impl Display for Pass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Pass::Architect => "Architect run",
Pass::Pickup => "Pickup run",
Pass::Security => "Security run",
})
}
}
static RECORD: LazyLock<Mutex<Record<OnMachine>>> =
LazyLock::new(|| Mutex::new(Record::new(OnMachine)));
const PARENT_RECORD: &str = "THIRDSHIFT_PARENT_RECORD";
const PARENT_DISPATCH: &str = "THIRDSHIFT_PARENT_DISPATCH";
pub fn begin(begin: Begin) {
let mut record = record();
if matches!(begin, Begin::ChildRun(..)) {
record.inherit_owner(owner::Owner::inherited());
record.child_of(std::env::var_os(PARENT_RECORD).map(PathBuf::from));
record.child_dispatch(std::env::var(PARENT_DISPATCH).ok());
}
record.begin(begin);
}
pub fn inherit_record(command: &mut std::process::Command) {
if let Some(path) = record().record_path() {
command.env(PARENT_RECORD, path);
} else {
command.env_remove(PARENT_RECORD);
}
if let Some(issue) = record().dispatch_issue() {
command.env(PARENT_DISPATCH, issue);
} else {
command.env_remove(PARENT_DISPATCH);
}
}
pub(crate) fn tickets(tickets: Vec<TicketRecord>) {
record().tickets(tickets);
}
pub fn configured(config: &UserConfig) {
record().configured(config);
}
pub fn started(work: Work, harness: &Choice) {
record().started(work, harness);
}
pub fn skipped(pass: Pass, repo: &Repo, reason: impl Display) {
record().skipped(pass, repo, reason);
}
pub fn ended(outcome: impl Display) {
record().ended(outcome);
}
pub fn issue_title(title: Option<String>) {
record().issue_title(title);
}
pub fn stage(stage: Stage) {
record().stage(stage, None);
}
pub fn session(session: Logged, path: &Path) {
record().stage(session.stage(), Some(path));
}
pub fn issue_branch(branch: Option<&str>) {
record().issue_branch(branch);
}
pub fn finished(account: &crate::run_ending::Account<'_>) {
record().finished(account);
}
pub fn stop_requested() -> Option<stop::Request> {
if !crate::interrupt::requested() {
return None;
}
record().stop_request()
}
pub fn dispatch_ended(ended: &crate::run::Ended) {
record().dispatch_ended(&crate::run_ending::Account::of_run(ended));
}
pub fn dispatching(work: Work, harness: &Choice) {
record().dispatching(work, harness);
}
pub fn show_held() {
record().show_held();
}
pub fn root(repo: &Repo) -> PathBuf {
record().root(repo)
}
pub fn command_kind() -> CommandKind {
record().command_kind()
}
pub fn session_dir(repo: &Repo) -> PathBuf {
let record = record();
record
.root(repo)
.join("commands")
.join(record.command_kind().folder())
}
pub fn stamp() -> String {
record().stamp()
}
pub fn command_log_path() -> Option<PathBuf> {
record().command_log_path()
}
pub(crate) fn captured_owner() -> Result<owner::Owner, String> {
record().captured_owner()
}
pub fn eprint(line: &str) {
record().eprint(line);
}
pub fn print(line: &str) {
record().print(line);
}
fn root_under(logs_dir: &Path, repo: &Repo) -> PathBuf {
logs_dir.join(&repo.owner).join(&repo.name)
}
impl Work<'_> {
fn initial_stage(self) -> Option<Stage> {
match self {
Work::Run(_) | Work::BaseFix(_) => Some(Stage::Implement),
Work::SpecRun(_) => Some(Stage::Tickets),
Work::ArchitectRun(_) => Some(Stage::ArchitectureReview),
Work::PickupRun(_) | Work::SecurityRun(_) => None,
}
}
fn repo(self) -> Repo {
match self {
Work::Run(issue)
| Work::BaseFix(issue)
| Work::SpecRun(issue)
| Work::PickupRun(issue) => issue.repo(),
Work::ArchitectRun(repo) | Work::SecurityRun(repo) => repo.clone(),
}
}
fn kind(self) -> Kind {
match self {
Work::Run(_) | Work::BaseFix(_) => Kind::Run,
Work::SpecRun(_) => Kind::SpecRun,
Work::PickupRun(_) => Kind::Pass(Pass::Pickup),
Work::ArchitectRun(_) => Kind::Pass(Pass::Architect),
Work::SecurityRun(_) => Kind::Pass(Pass::Security),
}
}
fn issue(self) -> Option<u64> {
match self {
Work::Run(issue)
| Work::BaseFix(issue)
| Work::SpecRun(issue)
| Work::PickupRun(issue) => Some(issue.number),
Work::ArchitectRun(_) | Work::SecurityRun(_) => None,
}
}
}
fn record() -> MutexGuard<'static, Record<OnMachine>> {
RECORD.lock().unwrap_or_else(PoisonError::into_inner)
}