Skip to main content

git_zero_server/receive/
ancestry.rs

1//! Bounded commit-ancestry decisions over a host's external graph catalog.
2use git_zero_core::{Error, ObjectFormat, ObjectId, ObjectKind, Result};
3
4/// The host queues distinct commits, starting at the proposed tip, and follows
5/// only parent edges. Queue storage can be paged; no history lives in this value.
6/// One visit budget covers every branch update in the receive operation.
7pub struct Ancestry {
8    format: ObjectFormat,
9    target: Option<ObjectId>,
10    remaining: u64,
11}
12
13impl Ancestry {
14    pub const fn new(format: ObjectFormat, max_visits: u64) -> Self {
15        Self {
16            format,
17            target: None,
18            remaining: max_visits,
19        }
20    }
21
22    /// Start searching for the old tip without replenishing the request budget.
23    pub fn start(&mut self, old: ObjectId) -> Result<()> {
24        self.target = None;
25        if self.remaining == 0 {
26            return Err(Error::LimitExceeded);
27        }
28        if old.format() != self.format || old.is_zero() {
29            return Err(Error::InvalidObjectId);
30        }
31        self.target = Some(old);
32        Ok(())
33    }
34
35    /// Return true only when a visited commit is the old tip. Exhausting the
36    /// host's queue without a match means the update is not a fast-forward.
37    pub fn visit(&mut self, id: ObjectId, kind: ObjectKind) -> Result<bool> {
38        let target = self.target.ok_or(Error::InvalidRequest)?;
39        if self.remaining == 0 {
40            return Err(Error::LimitExceeded);
41        }
42        if id.format() != self.format || id.is_zero() {
43            return Err(Error::InvalidObjectId);
44        }
45        if kind != ObjectKind::Commit {
46            return Err(Error::InvalidObject);
47        }
48        self.remaining -= 1;
49        let found = target == id;
50        if found {
51            self.target = None;
52        }
53        Ok(found)
54    }
55}