aboutsummaryrefslogtreecommitdiff
path: root/apps/servers/octopus/supergit/src/branch.rs
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2020-11-03 21:08:38 +0100
committerMx Kookie <kookie@spacekookie.de>2020-12-21 05:19:28 +0100
commit70fe187f1e118b6e63e512b6718635561682dad4 (patch)
tree1ae04ed78cee3dee951400f7d99642f885ef4cd9 /apps/servers/octopus/supergit/src/branch.rs
parent8be6dc679e97053da8f173333578ed626466d3de (diff)
octopus: refactoring & typed branch parsing
This code is work-in-progress, and doesn't work on a repo that has a branched history. The issue here is that after handling a merge commit, keeping track of which commit to look at next is non-trivial. This solution tries to isuse a "skip" command on the walker, but this can accidentally skip commits, when two merges have happened in succession (maybe a bug with the impl, not the concept). But also, the actual merge commit seems to already be part of the norma history? So maybe we can ommit the merge commit explicitly, and simply return a new branch handle instead.
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()
+ }
}