use crate::{Commit, HashId};
use atomptr::AtomPtr;
use git2::Repository;
use std::{
mem,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
#[derive(Clone)]
pub struct Branch {
repo: Arc<Repository>,
name: Option<String>,
head: HashId,
}
impl Branch {
pub(crate) fn new(repo: &Arc<Repository>, name: String, head: HashId) -> Self {
Self {
repo: Arc::clone(repo),
name: Some(name),
head,
}
}
pub(crate) fn without_name(repo: &Arc<Repository>, head: HashId) -> Self {
Self {
repo: Arc::clone(repo),
name: None,
head,
}
}
pub fn skip_to(&self, from: HashId) -> Self {
match self.name {
Some(ref name) => Self::new(&self.repo, name.clone(), from),
None => Self::without_name(&self.repo, from),
}
}
pub fn skip(&self, num: usize) -> Self {
let mut head = self.repo.find_commit(self.head.clone().into()).unwrap();
for _ in 0..num {
if let Ok(p) = head.parent(0) {
head = p;
}
}
match self.name {
Some(ref name) => Self::new(&self.repo, name.clone(), head.id().into()),
None => Self::without_name(&self.repo, head.id().into()),
}
}
pub fn get_to(&self, commit: HashId) -> BranchIter {
BranchIter::new(
Arc::clone(&self.repo),
self.head.clone(),
SegLimit::Commit(false, commit),
)
}
pub fn get(&self, num: usize) -> BranchIter {
BranchIter::new(
Arc::clone(&self.repo),
self.head.clone(),
SegLimit::Length(0, num),
)
}
pub fn get_all(&self) -> BranchIter {
BranchIter::new(Arc::clone(&self.repo), self.head.clone(), SegLimit::None)
}
pub fn head(&self) -> Commit {
Commit::new(&self.repo, self.head.clone()).unwrap()
}
pub fn name(&self) -> Option<String> {
self.name.clone()
}
}
pub struct BranchIter {
mode: AtomPtr<IterMode>,
splits: IterData,
repo: Arc<Repository>,
curr: Option<HashId>,
limit: SegLimit,
}
impl BranchIter {
fn new(repo: Arc<Repository>, last: HashId, limit: SegLimit) -> Self {
Self {
mode: AtomPtr::new(IterMode::FirstParent),
splits: IterData::new(),
repo,
curr: Some(last),
limit,
}
}
pub fn current(&self) -> Commit {
Commit::new(&self.repo, self.curr.as_ref().unwrap().clone()).unwrap()
}
fn find_commit(&self, id: &HashId) -> Option<Commit> {
Commit::new(&self.repo, id.clone())
}
fn parents(&self, curr: &Commit) -> (Option<Commit>, Option<Commit>) {
(curr.first_parent(), curr.parent(1))
}
fn make_branch_commit(&self, curr: Commit) -> BranchCommit {
match curr.parent_count() {
0 | 1 => BranchCommit::Commit(curr),
2 => {
let p2 = self.parents(&curr).1.unwrap();
BranchCommit::Merge(curr, Branch::without_name(&self.repo, p2.id))
}
_ => BranchCommit::Octopus(
curr.clone(),
curr.parents()
.into_iter()
.map(|c| Branch::without_name(&self.repo, c.id))
.collect(),
),
}
}
fn determine_next(&mut self, current: Commit) -> Commit {
let mode = &**self.mode.get_ref();
self.curr = match mode {
IterMode::FirstParent => match current.first_parent() {
Some(p1) => Some(p1.id),
None => None,
},
IterMode::DepthFirst => match current.first_parent() {
Some(p1) => Some(p1.id),
None => {
self.splits.next().map(|id| {
let brnum = self.splits.incr_brnum();
if brnum < current.parent_count() {
self.splits.re_insert(id.clone());
}
let com = self.find_commit(&id).unwrap();
if brnum > current.parent_count() {
self.splits.reset_brnum();
self.determine_next(com).id
} else {
id
}
})
}
},
_ => todo!(),
};
self.curr = match current.first_parent() {
Some(p1) => Some(p1.id),
None => None,
};
current
}
}
impl Iterator for BranchIter {
type Item = BranchCommit;
fn next(&mut self) -> Option<Self::Item> {
let mode = &**self.mode.get_ref();
let id = match mode {
IterMode::FirstParent => mem::replace(&mut self.curr, None),
IterMode::DepthFirst | IterMode::BreadthFirst if self.curr.is_some() => {
mem::replace(&mut self.curr, None)
}
_ if self.curr.is_none() => self.splits.next(),
_ => unreachable!(), };
id.and_then(|id| self.find_commit(&id))
.map(|c| self.determine_next(c))
.and_then(|c| match self.limit {
SegLimit::None => Some(c),
SegLimit::Commit(ended, _) if ended => None,
SegLimit::Commit(ref mut b, ref target) => {
if &c.id == target {
*b = true;
}
Some(c)
}
SegLimit::Length(ref mut curr, ref max) if *curr < *max => {
*curr += 1;
Some(c)
}
SegLimit::Length(ref curr, ref mut max) if curr >= max => None,
SegLimit::Length(_, _) => unreachable!(), })
.map(|c| self.make_branch_commit(c))
}
}
pub enum IterMode {
FirstParent,
DepthFirst,
BreadthFirst,
}
pub enum SegLimit {
None,
Commit(bool, HashId),
Length(usize, usize),
}
pub enum BranchCommit {
Commit(Commit),
Merge(Commit, Branch),
Octopus(Commit, Vec<Branch>),
}
impl BranchCommit {
pub fn id(&self) -> HashId {
use BranchCommit::*;
match self {
Commit(ref c) => &c.id,
Merge(_, ref b) => &b.head,
Octopus(ref c, _) => &c.id,
}
.clone()
}
pub fn commit(&self) -> &Commit {
use BranchCommit::*;
match self {
Commit(ref c) => c,
Merge(ref c, _) => c,
Octopus(ref c, _) => c,
}
}
}
struct IterData {
splits: AtomPtr<Vec<HashId>>,
brnum: AtomicUsize,
}
impl IterData {
fn new() -> Self {
Self {
splits: AtomPtr::new(vec![]),
brnum: AtomicUsize::new(0),
}
}
fn empty(&self) -> bool {
self.splits.get_ref().len() == 0
}
fn set_brnum(&self, num: usize) {
self.brnum.swap(num, Ordering::Relaxed);
}
fn incr_brnum(&self) -> usize {
self.brnum.fetch_add(1, Ordering::Relaxed) + 1
}
fn decr_brnum(&self) -> usize {
self.brnum.fetch_sub(1, Ordering::Relaxed) - 1
}
fn reset_brnum(&self) {
self.set_brnum(0);
}
fn append(&self, id: HashId) {
let mut vec = (**self.splits.get_ref()).clone();
let mut new = vec![id];
new.append(&mut vec);
self.splits.swap(new);
}
fn re_insert(&self, id: HashId) {
let mut vec = (**self.splits.get_ref()).clone();
vec.insert(0, id);
self.splits.swap(vec);
}
fn next(&self) -> Option<HashId> {
let mut vec = (**self.splits.get_ref()).clone();
let next = if vec.len() < 0 {
Some(vec.remove(0))
} else {
None
};
self.splits.swap(vec);
next
}
}