use std::io::Write;
use std::path::{Path, PathBuf};
use clap::{Args, ValueEnum};
use pointbreak::documents::revision_list_document;
use pointbreak::session::{
OrphanVisibility, QueryDiagnosticCode, QuerySurface, RefFilterMode, RevisionListOptions,
RevisionOverviewsOptions, RevisionRecordInputs, SupersessionView, build_revision_search_record,
list_revisions, matches_query, parse_search_query_for, read_events_for_display,
revision_supersession_classification, show_revision_overviews,
};
use crate::cli::output;
#[derive(Debug, Args)]
pub(super) struct RevisionListArgs {
#[arg(long, default_value = ".")]
repo: PathBuf,
#[arg(long)]
object: Option<String>,
#[arg(long)]
filter: Option<String>,
#[arg(long = "ref", alias = "branch")]
ref_name: Option<String>,
#[arg(long, value_enum, default_value = "label")]
by: RefFilterByArg,
#[arg(long = "all")]
_all: bool,
#[arg(long)]
orphans: bool,
#[arg(long = "integration-ref")]
integration_ref: Option<String>,
#[arg(long)]
worktree: Option<PathBuf>,
#[command(flatten)]
format_args: output::FormatArgs,
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
#[value(rename_all = "kebab-case")]
enum RefFilterByArg {
#[default]
Label,
Liveness,
}
impl From<RefFilterByArg> for RefFilterMode {
fn from(by: RefFilterByArg) -> Self {
match by {
RefFilterByArg::Label => RefFilterMode::Label,
RefFilterByArg::Liveness => RefFilterMode::Liveness,
}
}
}
pub(super) fn run(
args: RevisionListArgs,
stdout: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
let span = tracing::info_span!("shore.revision.list");
let _entered = span.enter();
tracing::debug!(command = "revision.list", "command_start");
let ids = crate::cli::id_resolver::IdResolver::new(&args.repo);
let object = match &args.object {
Some(object) => Some(ids.object(object)?),
None => None,
};
let mut options = RevisionListOptions::new(&args.repo).with_read_for_display(true);
if let Some(ref_name) = args.ref_name {
options = options.with_ref_filter(ref_name, args.by.into());
}
let visibility = if args.orphans {
OrphanVisibility::OrphansOnly
} else {
OrphanVisibility::All
};
options = options.with_orphan_visibility(visibility);
if let Some(integration_ref) = args.integration_ref {
options = options.with_integration_ref(integration_ref);
}
if let Some(worktree) = args.worktree {
options = options.with_worktree_scope(worktree);
}
let mut result = list_revisions(options)?;
if let Some(object) = object.as_deref() {
result
.entries
.retain(|entry| entry.object_id.as_str() == object);
result.revision_count = result.entries.len();
}
apply_revision_filter(&args.repo, args.filter.as_deref(), &mut result)?;
let document = revision_list_document(result);
let format = output::resolve_format(args.format_args.explicit(), output::OutputFormat::Json)?;
output::write_document_json_fallback(stdout, format, &document)
}
fn apply_revision_filter(
repo: &Path,
filter: Option<&str>,
result: &mut pointbreak::session::RevisionListResult,
) -> Result<(), Box<dyn std::error::Error>> {
let Some(filter) = filter else {
return Ok(());
};
let overviews = show_revision_overviews(
RevisionOverviewsOptions::new(repo)
.with_revisions(result.entries.iter().map(|e| e.revision_id.clone()))
.with_trust_set(crate::cli::common::discover_trust_set(repo))
.with_read_for_display(true),
)?;
let (events, _) = read_events_for_display(repo)?;
let classification =
revision_supersession_classification(&SupersessionView::from_events(&events)?);
let parsed = parse_search_query_for(filter, QuerySurface::Revision);
for diagnostic in &parsed.diagnostics {
match diagnostic.code {
QueryDiagnosticCode::UnsupportedQualifier | QueryDiagnosticCode::UnsupportedValue => {
return Err(diagnostic.message.clone().into());
}
QueryDiagnosticCode::DeprecatedQualifier => eprintln!("hint: {}", diagnostic.message),
}
}
result.entries.retain(|entry| {
let Some(overview) = overviews.get(&entry.revision_id) else {
return false;
};
let facet = classification.get(&entry.revision_id);
let record = build_revision_search_record(RevisionRecordInputs {
entry,
overview,
classification_state: facet.map(|f| f.state).unwrap_or("isolated"),
competing: facet.is_some_and(|f| f.competing),
});
matches_query(&record.0, &parsed.clauses)
});
result.revision_count = result.entries.len();
Ok(())
}