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!
wip/yesman
Katharina Fey 4 years ago
parent 70fe187f1e
commit 6ef94bb64d
  1. 11
      apps/servers/octopus/supergit/src/bin/test.rs
  2. 84
      apps/servers/octopus/supergit/src/branch.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!(),
});
}

@ -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> {
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<Commit>) -> Option<(BranchCommit, IterCmd)> {
fn get_parent(&self, last: Option<Commit>, 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<Self::Item> {
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<Commit>, Branch),
/// An octopus merge with multiple branches
Octopus(Commit, Vec<Branch>),
}
@ -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()

Loading…
Cancel
Save