use std::collections::VecDeque;
use std::collections::hash_map::Entry;
use std::sync::atomic::AtomicBool;
use super::ServeError;
use crate::packet::check_cancelled;
use crate::{IdMap, IdSet, ObjectId, Objects, ReadLimits};
#[derive(Debug, Default)]
pub(super) struct Deepen {
pub depth: Option<u32>,
pub relative: bool,
pub since: Option<i64>,
pub not: Vec<ObjectId>,
}
impl Deepen {
pub(super) fn requested(&self) -> bool {
self.depth.is_some() || self.since.is_some() || !self.not.is_empty()
}
}
#[derive(Debug, Default)]
pub(super) struct Boundaries {
pub shallow: Vec<ObjectId>,
pub unshallow: Vec<ObjectId>,
pub cut: IdSet,
pub tips: Vec<ObjectId>,
}
struct Node {
parents: Vec<ObjectId>,
date: i64,
}
struct Reader<'a> {
objects: &'a Objects,
read: ReadLimits,
cancel: &'a AtomicBool,
nodes: IdMap<Node>,
remaining: usize,
parents: usize,
}
impl Reader<'_> {
fn node(&mut self, id: ObjectId) -> Result<&Node, ServeError> {
check_cancelled(self.cancel)?;
match self.nodes.entry(id) {
Entry::Occupied(entry) => Ok(entry.into_mut()),
Entry::Vacant(entry) => {
self.remaining = self
.remaining
.checked_sub(1)
.ok_or(ServeError::Limit("shallow commits"))?;
let commit = self
.objects
.read_commit_with(id, self.read)
.map_err(|error| ServeError::Commit(Box::new(error)))?;
let date = commit
.committer()
.ok()
.flatten()
.and_then(|person| person.date().ok())
.map_or(0, |date| date.seconds);
self.parents = self
.parents
.checked_sub(commit.parents().len())
.ok_or(ServeError::Limit("shallow parents"))?;
Ok(entry.insert(Node {
parents: commit.parents().to_vec(),
date,
}))
}
}
}
}
const INFINITE_DEPTH: u32 = i32::MAX as u32;
pub(super) fn boundaries(
objects: &Objects,
wants: &[ObjectId],
client_order: &[ObjectId],
deepen: &Deepen,
history: crate::HistoryLimits,
cancel: &AtomicBool,
) -> Result<Boundaries, ServeError> {
let client: IdSet = client_order.iter().copied().collect();
let mut reader = Reader {
objects,
read: history.read,
cancel,
nodes: IdMap::default(),
remaining: history.max_commits,
parents: history.max_parents,
};
let mut kept = Vec::new();
let mut lifted = Vec::new();
match deepen.depth {
Some(depth) => {
let sources: Vec<(ObjectId, u32)> = if deepen.relative {
let reached = reached_boundaries(&mut reader, wants, &client)?;
client_order
.iter()
.filter(|id| reached.contains(id))
.map(|&id| (id, 0))
.collect()
} else {
wants.iter().map(|&id| (id, 1)).collect()
};
let mut seen = IdSet::default();
let mut queue: VecDeque<(ObjectId, u32)> = VecDeque::new();
for (id, level) in sources {
if seen.insert(id) {
queue.push_back((id, level));
}
}
while let Some((id, level)) = queue.pop_front() {
if level >= depth {
kept.push(id);
continue;
}
let parents = reader.node(id)?.parents.clone();
if client.contains(&id) {
lifted.push(id);
}
for parent in parents {
if seen.insert(parent) {
queue.push_back((parent, level + 1));
}
}
}
}
None => {
let excluded = reachable(&mut reader, &deepen.not)?;
let included = |reader: &mut Reader<'_>, id: ObjectId| -> Result<bool, ServeError> {
if excluded.contains(&id) {
return Ok(false);
}
Ok(match deepen.since {
Some(since) => reader.node(id)?.date >= since,
None => true,
})
};
let mut seen = IdSet::default();
let mut pending: Vec<ObjectId> = Vec::new();
for &id in wants {
if included(&mut reader, id)? && seen.insert(id) {
pending.push(id);
}
}
if pending.is_empty() {
return Err(ServeError::Protocol(
"no commits selected for shallow requests",
));
}
while let Some(id) = pending.pop() {
let parents = reader.node(id)?.parents.clone();
let mut cut = false;
for &parent in &parents {
if included(&mut reader, parent)? {
if seen.insert(parent) {
pending.push(parent);
}
} else {
cut = true;
}
}
if cut {
kept.push(id);
} else if client.contains(&id) {
lifted.push(id);
}
}
}
}
if deepen.depth == Some(INFINITE_DEPTH) {
let lifted_set: IdSet = lifted.iter().copied().collect();
for &id in client_order {
if !lifted_set.contains(&id) {
reader.node(id)?;
lifted.push(id);
}
}
}
let mut boundaries = Boundaries::default();
for id in kept {
boundaries.cut.insert(id);
if !client.contains(&id) {
boundaries.shallow.push(id);
}
}
for id in lifted {
if let Some(node) = reader.nodes.get(&id) {
boundaries.tips.extend(&node.parents);
}
boundaries.unshallow.push(id);
}
Ok(boundaries)
}
fn reached_boundaries(
reader: &mut Reader<'_>,
wants: &[ObjectId],
client: &IdSet,
) -> Result<IdSet, ServeError> {
let mut reached = IdSet::default();
let mut seen: IdSet = wants.iter().copied().collect();
let mut pending = wants.to_vec();
while let Some(id) = pending.pop() {
if client.contains(&id) {
reached.insert(id);
continue;
}
for parent in reader.node(id)?.parents.clone() {
if seen.insert(parent) {
pending.push(parent);
}
}
}
Ok(reached)
}
fn reachable(reader: &mut Reader<'_>, roots: &[ObjectId]) -> Result<IdSet, ServeError> {
let mut seen: IdSet = roots.iter().copied().collect();
let mut pending = roots.to_vec();
while let Some(id) = pending.pop() {
for parent in reader.node(id)?.parents.clone() {
if seen.insert(parent) {
pending.push(parent);
}
}
}
Ok(seen)
}