From 6ef94bb64d42b9b73ad2f9f74efb99012c8e6d1c Mon Sep 17 00:00:00 2001 From: Mx Kookie Date: Tue, 3 Nov 2020 23:37:50 +0100 Subject: octopus: implement full recursive branch parsing This code implements a parsing strategy that uses lazy iterators to traverse a branch graph. An iterator is constructed for a starting point on a branch, and new iterators are spawned for every merge that is encountered. To get all commits in a repository, simply do as test.rs: queue new work to a channel, that you poll from until no more branches have been discovered. This code is somewhat suboptimal. For one, get_parent() is way too complex, and could use some refactoring. Secondly, the semantics of `BranchCommit::Branch(...)` are unclear from the outside, and the fact that simple merge commits will be returned via `BranchCommit::Commit`, while subsequent merge commits need to use `BranchCommit::Merge(...)` is inconsistent and should be fixed before doing any sort of public release! --- apps/servers/octopus/supergit/src/bin/test.rs | 11 +++- apps/servers/octopus/supergit/src/branch.rs | 84 +++++++++++++++++++-------- 2 files changed, 70 insertions(+), 25 deletions(-) (limited to 'apps/servers/octopus/supergit') diff --git a/apps/servers/octopus/supergit/src/bin/test.rs b/apps/servers/octopus/supergit/src/bin/test.rs index 505580a860ce..5398d7539490 100644 --- a/apps/servers/octopus/supergit/src/bin/test.rs +++ b/apps/servers/octopus/supergit/src/bin/test.rs @@ -25,12 +25,19 @@ fn main() { // Iterate over all branch iterators we get while let Some(biter) = rx.recv().ok() { use BranchCommit::*; + println!( + "{}: {}", + biter.current().id_str(), + biter.current().summary() + ); + biter.for_each(|bc| match bc { Commit(c) => println!("{}: {}", c.id_str(), c.summary()), - Merge(c, b) => { - // println!("[MERGE] {}: {}", c.id_str(), c.summary()); + Merge(Some(c), b) => { + println!("{}: {}", c.id_str(), c.summary()); // tx.send(b.get_all()).unwrap(); } + Merge(_, b) => {} //tx.send(b.get_all()).unwrap(), _ => todo!(), }); } diff --git a/apps/servers/octopus/supergit/src/branch.rs b/apps/servers/octopus/supergit/src/branch.rs index e0f779b8a8f0..1dd3fffab370 100644 --- a/apps/servers/octopus/supergit/src/branch.rs +++ b/apps/servers/octopus/supergit/src/branch.rs @@ -1,6 +1,6 @@ use crate::{Commit, HashId}; use git2::Repository; -use std::sync::Arc; +use std::{mem, sync::Arc}; /// Abstraction for a branch history slice /// @@ -100,6 +100,10 @@ impl BranchIter { } } + pub fn current(&self) -> Commit { + Commit::new(&self.repo, self.last.clone()).unwrap() + } + /// Get a commit object, if it exists fn find_commit(&self, id: &HashId) -> Option { Commit::new(&self.repo, id.clone()) @@ -113,23 +117,59 @@ impl BranchIter { } /// Get the parent, set the last, and return BranchCommit (maybe) - fn get_parent(&self, last: Option) -> Option<(BranchCommit, IterCmd)> { + fn get_parent(&self, last: Option, cmd: IterCmd) -> Option<(BranchCommit, IterCmd)> { + if let Some(id) = cmd.take() { + let commit = Commit::new(&self.repo, id).unwrap(); + + return match commit.parent_count() { + // Special case: if the previous commit was a merge, + // but this was the first commit in the history, we + // need to return it here or else it will be forgotten + // about! + 0 | 1 => Some((BranchCommit::Commit(commit), IterCmd::Step)), + 2 => { + let p1 = commit.first_parent().unwrap(); + let p2 = commit.parent(1).unwrap(); + + Some(( + BranchCommit::Merge( + // Here we return a commit via the merge + // field, because otherwise it will be + // dropped! Because we just skipped + // because of a merge (because merges are + // normal commit parents). + Some(commit.clone()), + Branch::without_name(&self.repo, p2.id), + ), + IterCmd::Skip(p1.id), + )) + } + _ => todo!(), + }; + } + + // This code is only entered when we are checking for the parents 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, - )), + 1 => { + let parent = c.first_parent().unwrap(); + Some((BranchCommit::Commit(parent), 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), - )), + 2 => { + let p1 = c.first_parent().unwrap(); + let p2 = c.parent(1).unwrap(); + + Some(( + // Set the Merge commit field to None because it's + // used to communicate special states (like a + // merge after another merge). + BranchCommit::Merge(None, Branch::without_name(&self.repo, p2.id)), + IterCmd::Skip(p1.id), + )) + } // More or negative parents means the universe is ending _ => panic!("Octopus merges are not implemented yet!"), }) @@ -140,20 +180,17 @@ impl Iterator for BranchIter { type Item = BranchCommit; fn next(&mut self) -> Option { - let cid = std::mem::replace(&mut self.cmd, IterCmd::Step) - .take() - .unwrap_or_else(|| self.last.clone()); + let cmd = mem::replace(&mut self.cmd, IterCmd::Step); + let last = self.find_commit(&self.last); - let last = self.find_commit(&cid); - match self.limit { // Get commits forever - SegLimit::None => self.get_parent(last).map(|bc| self.set_last(bc)), + SegLimit::None => self.get_parent(last, cmd).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) + self.get_parent(last, cmd) .map(|(bc, cmd)| { // Set iterator to "done" if we have reached the commit if bc.id() == c { @@ -173,13 +210,14 @@ impl Iterator for BranchIter { } *curr += 1; - self.get_parent(last).map(|bc| self.set_last(bc)) + self.get_parent(last, cmd).map(|bc| self.set_last(bc)) } } } } /// Specify how to trace actions on the iterator +#[derive(Debug)] enum IterCmd { /// Set the last commit to an ID Step, @@ -215,7 +253,7 @@ pub enum BranchCommit { /// A single commit Commit(Commit), /// A merge commit from one other branch - Merge(Commit, Branch), + Merge(Option, Branch), /// An octopus merge with multiple branches Octopus(Commit, Vec), } @@ -225,7 +263,7 @@ impl BranchCommit { use BranchCommit::*; match self { Commit(ref c) => &c.id, - Merge(ref c, _) => &c.id, + Merge(_, ref b) => &b.head, Octopus(ref c, _) => &c.id, } .clone() -- cgit v1.2.3