use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::AtomicBool;
use super::{ForcePolicy, PushCommand, PushFailure as Error, PushLimits};
use crate::packet::check_cancelled;
use crate::{Object, ObjectId, ObjectKind, Objects};
pub(super) struct Graph {
pub objects: HashMap<ObjectId, Object>,
parents: HashMap<ObjectId, Vec<ObjectId>>,
}
impl Graph {
pub fn select(
store: &Objects,
commands: &[PushCommand],
limits: PushLimits,
cancel: &AtomicBool,
) -> Result<Self, Error> {
let mut graph = Self {
objects: HashMap::new(),
parents: HashMap::new(),
};
let mut pending = VecDeque::new();
let mut expected = HashMap::new();
for command in commands {
if command.deletes() {
continue;
}
let kind = command
.name
.as_bytes()
.starts_with(b"refs/heads/")
.then_some(ObjectKind::Commit);
enqueue(command.new, kind, &mut expected, &mut pending, limits)?;
}
let mut bytes = limits.pack.max_input_bytes;
let mut edges = limits.max_edges;
while let Some(id) = pending.pop_front() {
check_cancelled(cancel)?;
let mut read = limits.read;
read.max_object_bytes = read.max_object_bytes.min(
usize::try_from(bytes.min(limits.pack.max_object_bytes)).unwrap_or(usize::MAX),
);
let object = store
.read(id, read)
.map_err(|source| Error::Read { id, source })?
.or_else(|| {
let empty = Object {
kind: ObjectKind::Tree,
format: id.format(),
data: Vec::new(),
};
(id == empty.id()).then_some(empty)
})
.ok_or(Error::Missing(id))?;
if expected[&id].is_some_and(|kind| kind != object.kind()) {
return Err(Error::Kind(id));
}
bytes = bytes
.checked_sub(object.data().len() as u64)
.ok_or(Error::Limit("selected bytes"))?;
let mut parents = Vec::new();
let edge = |target, kind| {
if object.kind() == ObjectKind::Commit && kind == ObjectKind::Commit {
parents.push(target);
}
check_cancelled(cancel)?;
edges = edges
.checked_sub(1)
.ok_or(Error::Limit("reachable edges"))?;
if graph.objects.get(&target).is_some_and(|o| o.kind() != kind) {
return Err(Error::Kind(target));
}
enqueue(target, Some(kind), &mut expected, &mut pending, limits)
};
crate::edges::visit(id, &object, edge)?;
if object.kind() == ObjectKind::Commit {
graph.parents.insert(id, parents);
}
if expected[&id].is_some_and(|kind| kind != object.kind()) {
return Err(Error::Kind(id));
}
graph.objects.insert(id, object);
}
let mut remaining = limits.max_ancestry_steps;
for command in commands {
check_cancelled(cancel)?;
if command.deletes() {
continue;
}
if command.force == ForcePolicy::Allow {
continue;
}
if let Some(old) = command.expected {
if old == command.new {
continue;
}
if command.name.as_bytes().starts_with(b"refs/heads/")
&& !graph.is_ancestor(old, command.new, &mut remaining, cancel)?
{
return Err(Error::WouldForce(command.name.clone()));
}
if command.name.as_bytes().starts_with(b"refs/tags/") {
return Err(Error::WouldForce(command.name.clone()));
}
}
}
Ok(graph)
}
pub fn exclude(
&mut self,
roots: &[ObjectId],
limits: PushLimits,
cancel: &AtomicBool,
) -> Result<Vec<ObjectId>, Error> {
if roots.len() > limits.max_refs {
return Err(Error::Limit("receiver roots"));
}
let mut proven = Vec::new();
let mut seen = HashSet::new();
let mut pending = VecDeque::new();
for &id in roots {
check_cancelled(cancel)?;
if self.objects.contains_key(&id) && seen.insert(id) {
proven.push(id);
pending.push_back(id);
}
}
let mut edges = limits.max_edges;
while let Some(id) = pending.pop_front() {
check_cancelled(cancel)?;
let object = &self.objects[&id];
let edge = |target, _kind| {
check_cancelled(cancel)?;
edges = edges
.checked_sub(1)
.ok_or(Error::Limit("exclusion edges"))?;
if seen.insert(target) {
pending.push_back(target);
}
Ok::<_, Error>(())
};
crate::edges::visit(id, object, edge)?;
}
for id in seen {
check_cancelled(cancel)?;
self.objects.remove(&id);
}
Ok(proven)
}
fn is_ancestor(
&self,
old: ObjectId,
new: ObjectId,
remaining: &mut usize,
cancel: &AtomicBool,
) -> Result<bool, Error> {
let mut pending = vec![new];
let mut seen = HashSet::from([new]);
while let Some(id) = pending.pop() {
check_cancelled(cancel)?;
*remaining = remaining
.checked_sub(1)
.ok_or(Error::Limit("ancestry steps"))?;
if id == old {
return Ok(true);
}
for &parent in &self.parents[&id] {
check_cancelled(cancel)?;
*remaining = remaining
.checked_sub(1)
.ok_or(Error::Limit("ancestry steps"))?;
if seen.insert(parent) {
pending.push(parent);
}
}
}
Ok(false)
}
}
fn enqueue(
id: ObjectId,
kind: Option<ObjectKind>,
expected: &mut HashMap<ObjectId, Option<ObjectKind>>,
pending: &mut VecDeque<ObjectId>,
limits: PushLimits,
) -> Result<(), Error> {
if let Some(previous) = expected.get_mut(&id) {
if previous.is_some() && kind.is_some() && *previous != kind {
return Err(Error::Kind(id));
}
if kind.is_some() {
*previous = kind;
}
} else {
if expected.len() as u64 >= u64::from(limits.pack.max_objects) {
return Err(Error::Limit("selected objects"));
}
expected.insert(id, kind);
pending.push_back(id);
}
Ok(())
}