aboutsummaryrefslogtreecommitdiff
path: root/apps/servers/octopus/supergit/src/branch.rs
diff options
context:
space:
mode:
Diffstat (limited to 'apps/servers/octopus/supergit/src/branch.rs')
-rw-r--r--apps/servers/octopus/supergit/src/branch.rs223
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()
+ }
}