use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
use crate::error::{Error, Result};
use crate::objects::{parse_commit, ObjectId};
use crate::repo::Repository;
const COMMON: u8 = 1 << 2;
const ADVERTISED: u8 = 1 << 3;
const SEEN: u8 = 1 << 4;
const POPPED: u8 = 1 << 5;
fn committer_unix_seconds(committer: &str) -> Result<i64> {
let parts: Vec<&str> = committer.rsplitn(3, ' ').collect();
if parts.len() < 2 {
return Err(Error::CorruptObject(
"committer line missing date".to_owned(),
));
}
parts[1]
.parse::<i64>()
.map_err(|_| Error::CorruptObject("invalid committer timestamp".to_owned()))
}
fn commit_date(repo: &Repository, oid: ObjectId) -> Result<i64> {
let obj = repo.odb.read(&oid)?;
let c = parse_commit(&obj.data)?;
committer_unix_seconds(&c.committer)
}
fn read_parents(repo: &Repository, oid: ObjectId) -> Result<Vec<ObjectId>> {
let obj = repo.odb.read(&oid)?;
Ok(parse_commit(&obj.data)?.parents)
}
#[derive(Clone, Copy)]
struct HeapItem {
date: i64,
oid: ObjectId,
}
impl Eq for HeapItem {}
impl PartialEq for HeapItem {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Ord for HeapItem {
fn cmp(&self, other: &Self) -> Ordering {
self.date
.cmp(&other.date)
.then_with(|| self.oid.cmp(&other.oid))
}
}
impl PartialOrd for HeapItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
struct Entry {
original_ttl: u16,
ttl: u16,
}
pub struct SkippingNegotiator {
repo: Repository,
heap: BinaryHeap<HeapItem>,
entries: HashMap<ObjectId, Entry>,
flags: HashMap<ObjectId, u8>,
non_common_revs: usize,
}
impl SkippingNegotiator {
pub fn new(repo: Repository) -> Self {
Self {
repo,
heap: BinaryHeap::new(),
entries: HashMap::new(),
flags: HashMap::new(),
non_common_revs: 0,
}
}
pub fn repo(&self) -> &Repository {
&self.repo
}
fn f(&self, oid: ObjectId) -> u8 {
*self.flags.get(&oid).unwrap_or(&0)
}
fn set_f(&mut self, oid: ObjectId, bits: u8) {
*self.flags.entry(oid).or_insert(0) |= bits;
}
fn rev_list_push(&mut self, oid: ObjectId, mark: u8) -> Result<()> {
self.set_f(oid, mark | SEEN);
self.entries.insert(
oid,
Entry {
original_ttl: 0,
ttl: 0,
},
);
if mark & COMMON == 0 {
self.non_common_revs += 1;
}
let d = commit_date(&self.repo, oid)?;
self.heap.push(HeapItem { date: d, oid });
Ok(())
}
pub fn known_common(&mut self, oid: ObjectId) -> Result<()> {
if self.f(oid) & SEEN != 0 {
return Ok(());
}
self.rev_list_push(oid, ADVERTISED)?;
Ok(())
}
pub fn add_tip(&mut self, oid: ObjectId) -> Result<()> {
if self.f(oid) & SEEN != 0 {
return Ok(());
}
self.rev_list_push(oid, 0)?;
Ok(())
}
fn mark_common(&mut self, seen_oid: ObjectId) -> Result<()> {
if self.f(seen_oid) & COMMON != 0 {
return Ok(());
}
let mut stack = vec![seen_oid];
self.set_f(seen_oid, COMMON);
while let Some(c) = stack.pop() {
if self.f(c) & POPPED == 0 {
self.non_common_revs = self.non_common_revs.saturating_sub(1);
}
for p in read_parents(&self.repo, c)? {
if self.f(p) & SEEN == 0 || self.f(p) & COMMON != 0 {
continue;
}
self.set_f(p, COMMON);
stack.push(p);
}
}
Ok(())
}
fn push_parent(&mut self, entry_oid: ObjectId, to_push: ObjectId) -> Result<bool> {
let (entry_ttl, entry_orig) = {
let e = self
.entries
.get(&entry_oid)
.ok_or_else(|| Error::CorruptObject("missing queue entry".to_owned()))?;
(e.ttl, e.original_ttl)
};
let entry_common_or_adv = self.f(entry_oid) & (COMMON | ADVERTISED) != 0;
if self.f(to_push) & SEEN != 0 {
if self.f(to_push) & POPPED != 0 {
return Ok(false);
}
} else {
self.rev_list_push(to_push, 0)?;
}
let parent_entry = self
.entries
.get_mut(&to_push)
.ok_or_else(|| Error::CorruptObject("missing parent entry".to_owned()))?;
if entry_common_or_adv {
self.mark_common(to_push)?;
} else {
let new_original_ttl = if entry_ttl != 0 {
entry_orig
} else {
entry_orig * 3 / 2 + 1
};
let new_ttl = if entry_ttl != 0 {
entry_ttl - 1
} else {
new_original_ttl
};
if parent_entry.original_ttl < new_original_ttl {
parent_entry.original_ttl = new_original_ttl;
parent_entry.ttl = new_ttl;
}
}
Ok(true)
}
#[must_use]
pub fn have_phase_is_empty(&self) -> bool {
self.heap.is_empty() || self.non_common_revs == 0
}
pub fn next_have(&mut self) -> Result<Option<ObjectId>> {
loop {
if self.heap.is_empty() || self.non_common_revs == 0 {
return Ok(None);
}
let HeapItem {
oid: commit_oid, ..
} = self
.heap
.pop()
.ok_or_else(|| Error::CorruptObject("negotiator heap inconsistency".to_owned()))?;
let entry_ttl = self
.entries
.get(&commit_oid)
.map(|e| e.ttl)
.ok_or_else(|| Error::CorruptObject("popped missing entry".to_owned()))?;
self.set_f(commit_oid, POPPED);
if self.f(commit_oid) & COMMON == 0 {
self.non_common_revs = self.non_common_revs.saturating_sub(1);
}
let mut to_send: Option<ObjectId> = None;
if self.f(commit_oid) & COMMON == 0 && entry_ttl == 0 {
to_send = Some(commit_oid);
}
let pars = read_parents(&self.repo, commit_oid)?;
let mut parent_pushed = false;
for p in pars {
parent_pushed |= self.push_parent(commit_oid, p)?;
}
if self.f(commit_oid) & COMMON == 0 && !parent_pushed {
to_send = Some(commit_oid);
}
self.entries.remove(&commit_oid);
if let Some(oid) = to_send {
return Ok(Some(oid));
}
}
}
pub fn ack(&mut self, oid: ObjectId) -> Result<bool> {
if self.f(oid) & SEEN == 0 {
return Err(Error::CorruptObject(format!(
"ack for commit {} not sent as have",
oid.to_hex()
)));
}
let known = self.f(oid) & COMMON != 0;
self.mark_common(oid)?;
Ok(known)
}
}