use std::collections::VecDeque;
use anyhow::{Result, ensure};
use dsi_progress_logger::{ProgressLog, concurrent_progress_logger};
use rayon::prelude::*;
use swh_graph::graph::*;
use swh_graph::labels::{EdgeLabel, LabelNameId};
use swh_graph::properties;
use swh_graph::views::Subgraph;
use swh_graph::{NodeConstraint, NodeType};
use crate::collections::{AdaptiveNodeSet, NodeSet, ReadNodeSet};
use crate::{iter_nodes, peel_rel};
pub const HEAD_REF_NAMES: [&str; 2] = ["refs/heads/main", "refs/heads/master"];
pub fn find_head_rev<G>(graph: &G, snp: NodeId) -> Result<Option<NodeId>>
where
G: SwhLabeledForwardGraph + SwhGraphWithProperties,
<G as SwhGraphWithProperties>::LabelNames: properties::LabelNames,
<G as SwhGraphWithProperties>::Maps: properties::Maps,
{
let props = graph.properties();
let head_ref_name_ids: Vec<LabelNameId> = HEAD_REF_NAMES
.into_iter()
.filter_map(|name| props.label_name_id(name.as_bytes()).ok())
.collect();
find_head_rev_by_refs(graph, snp, &head_ref_name_ids)
}
pub fn find_head_rev_by_refs<G>(
graph: &G,
snp: NodeId,
ref_name_ids: &[LabelNameId],
) -> Result<Option<NodeId>>
where
G: SwhLabeledForwardGraph + SwhGraphWithProperties,
<G as SwhGraphWithProperties>::Maps: properties::Maps,
<G as SwhGraphWithProperties>::LabelNames: properties::LabelNames,
{
let props = graph.properties();
let node_type = props.node_type(snp);
ensure!(
node_type == NodeType::Snapshot,
"Cannot find head revision of {}: not a snapshot",
graph.properties().swhid(snp),
);
for (succ, labels) in graph.labeled_successors(snp) {
let node_type = props.node_type(succ);
if node_type != NodeType::Revision && node_type != NodeType::Release {
continue;
}
for label in labels {
#[allow(clippy::collapsible_if)]
if let EdgeLabel::Branch(branch) = label {
if ref_name_ids.contains(&branch.label_name_id()) {
return Ok(Some(succ));
}
}
}
}
Ok(None)
}
pub fn list_root_revisions<G>(graph: &G) -> Result<Vec<NodeId>>
where
G: SwhForwardGraph + SwhGraphWithProperties<Maps: properties::Maps> + Sync,
{
let graph = Subgraph::with_node_constraint(graph, "rev".parse().unwrap());
let mut pl = concurrent_progress_logger!(
item_name = "rev",
display_memory = false,
expected_updates = graph.actual_num_nodes().ok(),
);
pl.start("Listing initial revs...");
Ok(graph
.par_iter_nodes(pl)
.filter(|&node| graph.successors(node).next().is_none())
.collect())
}
#[derive(Default)]
pub struct RevListOptions {
pub min_parents: Option<usize>,
pub max_parents: Option<usize>,
pub since: Option<i64>,
pub until: Option<i64>,
}
pub fn list_revisions<G>(graph: &G, rev: NodeId) -> Result<Vec<NodeId>>
where
G: SwhForwardGraph
+ SwhGraphWithProperties<Maps: properties::Maps, Timestamps: properties::Timestamps>,
{
list_revisions_with(graph, &[rev], &[], &Default::default())
}
pub fn list_revisions_with<G>(
graph: &G,
start: &[NodeId],
exclude: &[NodeId],
opts: &RevListOptions,
) -> Result<Vec<NodeId>>
where
G: SwhForwardGraph
+ SwhGraphWithProperties<Maps: properties::Maps, Timestamps: properties::Timestamps>,
{
let props = graph.properties();
let rev_constraint: NodeConstraint = "rev".parse().unwrap();
let rev_graph = Subgraph::with_node_constraint(graph, rev_constraint);
let peel_to_rev = |node: NodeId| -> Result<NodeId> {
peel_rel(graph, node)?
.filter(|&n| props.node_type(n) == NodeType::Revision)
.ok_or_else(|| anyhow::anyhow!("Node {node} cannot be peeled to a revision"))
};
let start_revs: Vec<NodeId> = start
.iter()
.map(|&n| peel_to_rev(n))
.collect::<Result<_>>()?;
let exclude_revs: Vec<NodeId> = exclude
.iter()
.map(|&n| peel_to_rev(n))
.collect::<Result<_>>()?;
let passes_traversal_filters = |node: NodeId| -> bool {
opts.since
.is_none_or(|since| props.committer_timestamp(node).is_none_or(|ts| ts >= since))
};
let count_parents_up_to =
|node: NodeId, limit: usize| -> usize { rev_graph.successors(node).take(limit).count() };
let passes_output_filters = |node: NodeId| -> bool {
opts.min_parents
.is_none_or(|min| count_parents_up_to(node, min) >= min)
&& opts
.max_parents
.is_none_or(|max| count_parents_up_to(node, max.saturating_add(1)) <= max)
&& opts
.until
.is_none_or(|until| props.committer_timestamp(node).is_none_or(|ts| ts <= until))
};
let excluded: AdaptiveNodeSet = {
let mut set = AdaptiveNodeSet::new(graph.num_nodes());
for node in iter_nodes(&rev_graph, &exclude_revs) {
set.insert(node);
}
set
};
let mut visited = AdaptiveNodeSet::new(graph.num_nodes());
let mut included = Vec::new();
let mut queue = VecDeque::new();
for &rev in &start_revs {
if !visited.contains(rev) && !excluded.contains(rev) {
visited.insert(rev);
queue.push_back(rev);
}
}
while let Some(node) = queue.pop_front() {
if !passes_traversal_filters(node) {
continue; }
if passes_output_filters(node) {
included.push(node);
}
for succ in rev_graph.successors(node) {
if !visited.contains(succ) && !excluded.contains(succ) {
visited.insert(succ);
queue.push_back(succ);
}
}
}
included.sort_unstable_by_key(|&rev| {
(
std::cmp::Reverse(props.committer_timestamp(rev).unwrap_or(i64::MIN)),
rev,
)
});
Ok(included)
}