From caaf5da99fdc3baad6d2f7c31b7034984026b243 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 25 May 2022 17:32:33 +0800 Subject: [PATCH] prepare range parsing (#427) --- git-revision/Cargo.toml | 1 + git-revision/src/spec.rs | 31 +++++++++++++++++++++++++++++++ git-revision/tests/spec/mod.rs | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/git-revision/Cargo.toml b/git-revision/Cargo.toml index 92a70b5088..76d447bc4b 100644 --- a/git-revision/Cargo.toml +++ b/git-revision/Cargo.toml @@ -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"] } diff --git a/git-revision/src/spec.rs b/git-revision/src/spec.rs index 67645ce61c..5563f527d1 100644 --- a/git-revision/src/spec.rs +++ b/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; @@ -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 { diff --git a/git-revision/tests/spec/mod.rs b/git-revision/tests/spec/mod.rs index 67dc569d65..7d4cc60cf0 100644 --- a/git-revision/tests/spec/mod.rs +++ b/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, + kind: Option, } impl spec::parse::Delegate for Recorder { fn resolve_ref(&mut self, input: &BStr) -> Option<()> { @@ -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 { @@ -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"); } }