diff options
Diffstat (limited to 'apps/servers/octopus/supergit/src/branch.rs')
-rw-r--r-- | apps/servers/octopus/supergit/src/branch.rs | 223 |
1 files changed, 217 insertions, 6 deletions
diff --git a/apps/servers/octopus/supergit/src/branch.rs b/apps/servers/octopus/supergit/src/branch.rs index 81abbffed112..e0f779b8a8f0 100644 --- a/apps/servers/octopus/supergit/src/branch.rs +++ b/apps/servers/octopus/supergit/src/branch.rs @@ -1,10 +1,209 @@ -use crate::{Commit, CommitId}; +use crate::{Commit, HashId}; +use git2::Repository; +use std::sync::Arc; /// Abstraction for a branch history slice +/// +/// +#[derive(Clone)] pub struct Branch { - name: String, - head: CommitId, - history: Vec<BranchCommit>, + repo: Arc<Repository>, + pub name: Option<String>, + pub head: HashId, +} + +impl Branch { + /// Create a new branch handle + 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, + } + } + + /// Get a branch handle starting at a certain commit + 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), + } + } + + /// Create a branch handle that skips a certain number of commits + /// + /// This walker always picks the first parent. + 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), + ) + } + + /// Get the primary branch history as far back as it goes + pub fn get_all(&self) -> BranchIter { + BranchIter::new(Arc::clone(&self.repo), self.head.clone(), SegLimit::None) + } + + /// Get a branch segment of a certain length + pub fn get(&self, num: usize) -> BranchIter { + BranchIter::new( + Arc::clone(&self.repo), + self.head.clone(), + SegLimit::Length(0, num), + ) + } +} + +/// A branch segment iterator +/// +/// Each iterator is first-parent, but will notify you about a split +/// parent by setting +pub struct BranchIter { + repo: Arc<Repository>, + last: HashId, + cmd: IterCmd, + limit: SegLimit, +} + +impl BranchIter { + /// Create a new branch segment iterator + fn new(repo: Arc<Repository>, last: HashId, limit: SegLimit) -> Self { + Self { + repo, + last, + cmd: IterCmd::Step, + limit, + } + } + + /// Get a commit object, if it exists + fn find_commit(&self, id: &HashId) -> Option<Commit> { + Commit::new(&self.repo, id.clone()) + } + + /// Utility functiot to set last commit + fn set_last(&mut self, (bc, cmd): (BranchCommit, IterCmd)) -> BranchCommit { + self.last = bc.id(); + self.cmd = cmd; + bc + } + + /// Get the parent, set the last, and return BranchCommit (maybe) + fn get_parent(&self, last: Option<Commit>) -> Option<(BranchCommit, IterCmd)> { + last.and_then(|c| match c.parent_count() { + // No parent means we've reached the end of the branch + 0 => None, + // One parent is a normal commit + 1 => Some(( + BranchCommit::Commit(c.first_parent().unwrap()), + IterCmd::Step, + )), + // Two parents is a normal merge commit + 2 => Some(( + BranchCommit::Merge( + c.clone(), + Branch::without_name(&self.repo, c.parent(1).unwrap().id), + ), + IterCmd::Skip(c.parent(0).unwrap().id), + )), + // More or negative parents means the universe is ending + _ => panic!("Octopus merges are not implemented yet!"), + }) + } +} + +impl Iterator for BranchIter { + type Item = BranchCommit; + + fn next(&mut self) -> Option<Self::Item> { + let cid = std::mem::replace(&mut self.cmd, IterCmd::Step) + .take() + .unwrap_or_else(|| self.last.clone()); + + let last = self.find_commit(&cid); + + match self.limit { + // Get commits forever + SegLimit::None => self.get_parent(last).map(|bc| self.set_last(bc)), + // Get commits until hitting a certain ID + SegLimit::Commit(ended, _) if ended => None, + SegLimit::Commit(_, ref c) => { + let c = c.clone(); + self.get_parent(last) + .map(|(bc, cmd)| { + // Set iterator to "done" if we have reached the commit + if bc.id() == c { + self.limit = SegLimit::Commit(true, c.clone()); + (bc, cmd) + } else { + (bc, cmd) + } + }) + // Set last in case there's more to iterate + .map(|bc| self.set_last(bc)) + } + // Get a certain number of commits + SegLimit::Length(ref mut curr, ref mut max) => { + if curr >= max { + return None; + } + + *curr += 1; + self.get_parent(last).map(|bc| self.set_last(bc)) + } + } + } +} + +/// Specify how to trace actions on the iterator +enum IterCmd { + /// Set the last commit to an ID + Step, + /// Specify a parent to step to next + Skip(HashId), +} + +impl IterCmd { + fn take(self) -> Option<HashId> { + match self { + Self::Skip(id) => Some(id), + Self::Step => None, + } + } +} + +/// the limit applied to a branch segment +pub enum SegLimit { + /// No limit, enumerating all children + None, + /// Run until a certain commit is found + Commit(bool, HashId), + /// Run to collect a certain number of commits + Length(usize, usize), } /// A commit represented as a relationship to a branch @@ -16,7 +215,19 @@ pub enum BranchCommit { /// A single commit Commit(Commit), /// A merge commit from one other branch - Merge(Branch), + Merge(Commit, Branch), /// An octopus merge with multiple branches - Octopus(Vec<Branch>), + Octopus(Commit, Vec<Branch>), +} + +impl BranchCommit { + pub fn id(&self) -> HashId { + use BranchCommit::*; + match self { + Commit(ref c) => &c.id, + Merge(ref c, _) => &c.id, + Octopus(ref c, _) => &c.id, + } + .clone() + } } |