Skip to content

Commit

Permalink
prepare range parsing (#427)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed May 25, 2022
1 parent 11bf081 commit caaf5da
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 0 deletions.
1 change: 1 addition & 0 deletions git-revision/Cargo.toml
Expand Up @@ -19,6 +19,7 @@ serde1 = [ "serde", "git-hash/serde1", "git-object/serde1" ]
[dependencies]
git-hash = { version = "^0.9.4", path = "../git-hash" }
git-object = { version = "^0.19.0", path = "../git-object" }

hash_hasher = "2.0.3"
thiserror = "1.0.26"
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
Expand Down
31 changes: 31 additions & 0 deletions git-revision/src/spec.rs
@@ -1,3 +1,21 @@
/// How to interpret a revision specification, or `revspec`.
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub enum Kind {
/// A single revision specification, pointing at one reference.
Single,
/// Two revision specifications `a` and `b` where we want all commits from `b` that are not also in `a`.
Range,
/// Everything in `a` and `b` but no commit from any of their merge bases.
MergeBase,
}

impl Default for Kind {
fn default() -> Self {
Kind::Single
}
}

pub mod parse {
#![allow(missing_docs)]
use git_object::bstr::BStr;
Expand All @@ -8,12 +26,25 @@ pub mod parse {
Delegate,
}

/// A delegate to be informed about parse events, with methods split into three categories.
///
/// - **Revisions** - which revision to use as starting point for…
/// - **Navigation** - where to go once from the initial revision.
/// - **range** - to learn if the specification is for a single or multiple references.
pub trait Delegate {
fn resolve_ref(&mut self, input: &BStr) -> Option<()>;
fn find_by_prefix(&mut self, input: &BStr) -> Option<()>;

fn nth_ancestor(&mut self, n: usize) -> Option<()>;
fn nth_parent(&mut self, n: usize) -> Option<()>;

/// Set the kind of the specification, which happens only once if it happens at all.
/// In case this method isn't called, assume `Single`.
///
/// Note that ranges don't necessarily assure that a second specification will be parsed.
/// If `^rev` is given, this method is called with [`spec::Kind::Range`][crate::spec::Kind::Range]
/// and no second specification is provided.
fn kind(&mut self, kind: crate::spec::Kind);
}

pub(crate) mod function {
Expand Down
16 changes: 16 additions & 0 deletions git-revision/tests/spec/mod.rs
@@ -1,9 +1,11 @@
mod parse {
use git_object::bstr::{BStr, BString};
use git_revision::spec;

#[derive(Default, Debug)]
struct Recorder {
resolve_ref_input: Option<BString>,
kind: Option<spec::Kind>,
}
impl spec::parse::Delegate for Recorder {
fn resolve_ref(&mut self, input: &BStr) -> Option<()> {
Expand All @@ -27,6 +29,10 @@ mod parse {
fn nth_parent(&mut self, _n: usize) -> Option<()> {
todo!()
}

fn kind(&mut self, kind: spec::Kind) {
self.kind = Some(kind);
}
}

fn parse(spec: &str) -> Recorder {
Expand All @@ -35,15 +41,25 @@ mod parse {
rec
}

#[test]
#[ignore]
fn leading_caret_is_range_kind() {
let rec = parse("^HEAD");
assert_eq!(rec.kind.unwrap(), spec::Kind::Range);
assert_eq!(rec.resolve_ref_input.unwrap(), "HEAD");
}

#[test]
fn at_by_iteself_is_shortcut_for_head() {
let rec = parse("@");
assert!(rec.kind.is_none());
assert_eq!(rec.resolve_ref_input.unwrap(), "HEAD");
}

#[test]
fn refname_head() {
let rec = parse("HEAD");
assert!(rec.kind.is_none());
assert_eq!(rec.resolve_ref_input.unwrap(), "HEAD");
}
}

0 comments on commit caaf5da

Please sign in to comment.