diff --git a/gix-revision/tests/revision/spec/parse/navigate/caret_symbol.rs b/gix-revision/tests/revision/spec/parse/navigate/caret_symbol.rs index f1cf5236dfe..4a3fc214e85 100644 --- a/gix-revision/tests/revision/spec/parse/navigate/caret_symbol.rs +++ b/gix-revision/tests/revision/spec/parse/navigate/caret_symbol.rs @@ -1,6 +1,5 @@ -use gix_revision::{spec, spec::parse::delegate::Traversal}; - use crate::spec::parse::{parse, try_parse, PeelToOwned as PeelTo}; +use gix_revision::{spec, spec::parse::delegate::Traversal}; #[test] fn single_is_first_parent() { @@ -190,6 +189,23 @@ fn invalid_object_type() { ); } +#[test] +fn invalid_caret_without_previous_refname() { + let rec = parse(r"^^"); + assert_eq!(rec.calls, 2); + assert_eq!(rec.kind, Some(gix_revision::spec::Kind::ExcludeReachable)); + assert_eq!( + rec.traversal, + [Traversal::NthParent(1)], + "This can trip off an implementation as it's actually invalid, but looks valid" + ); + + for revspec in ["^^^HEAD", "^^HEAD"] { + let err = try_parse(revspec).unwrap_err(); + assert!(matches!(err, spec::parse::Error::UnconsumedInput {input} if input == "HEAD")); + } +} + #[test] fn incomplete_escaped_braces_in_regex_are_invalid() { let err = try_parse(r"@^{/a\{1}}").unwrap_err(); diff --git a/gix/src/revision/spec/parse/delegate/navigate.rs b/gix/src/revision/spec/parse/delegate/navigate.rs index efe86447746..ca32061acd7 100644 --- a/gix/src/revision/spec/parse/delegate/navigate.rs +++ b/gix/src/revision/spec/parse/delegate/navigate.rs @@ -25,7 +25,13 @@ impl delegate::Navigate for Delegate<'_> { let mut replacements = Replacements::default(); let mut errors = Vec::new(); - let objs = self.objs[self.idx].as_mut()?; + let objs = match self.objs[self.idx].as_mut() { + Some(objs) => objs, + None => { + self.err.push(Error::TraversalWithoutStartObject); + return None; + } + }; let repo = self.repo; for obj in objs.iter() { diff --git a/gix/src/revision/spec/parse/error.rs b/gix/src/revision/spec/parse/error.rs index 3af4697b0da..8a536210bb7 100644 --- a/gix/src/revision/spec/parse/error.rs +++ b/gix/src/revision/spec/parse/error.rs @@ -105,7 +105,6 @@ impl Error { } pub(crate) fn from_errors(errors: Vec) -> Self { - assert!(!errors.is_empty()); match errors.len() { 0 => unreachable!( "BUG: cannot create something from nothing, must have recorded some errors to call from_errors()" diff --git a/gix/src/revision/spec/parse/types.rs b/gix/src/revision/spec/parse/types.rs index 984071cce6a..1045364eb13 100644 --- a/gix/src/revision/spec/parse/types.rs +++ b/gix/src/revision/spec/parse/types.rs @@ -188,6 +188,8 @@ pub enum Error { }, #[error(transparent)] Traverse(#[from] crate::revision::walk::iter::Error), + #[error("Tried to navigate the commit-graph without providing an anchor first")] + TraversalWithoutStartObject, #[error(transparent)] Walk(#[from] crate::revision::walk::Error), #[error("Spec does not contain a single object id")] diff --git a/gix/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar b/gix/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar index 538e9943fc5..580318e4876 100644 Binary files a/gix/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar and b/gix/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar differ diff --git a/gix/tests/fixtures/make_rev_spec_parse_repos.sh b/gix/tests/fixtures/make_rev_spec_parse_repos.sh index 887f0f1d7ab..aceae3fb15e 100755 --- a/gix/tests/fixtures/make_rev_spec_parse_repos.sh +++ b/gix/tests/fixtures/make_rev_spec_parse_repos.sh @@ -356,6 +356,7 @@ EOF baseline "@^{/!-B}" # negation from branch baseline ":file" # index lookup, default stage 0 baseline ":1:file" # stage 1 + baseline ":5:file" # invalid stage baseline ":foo" # not found # parents baseline "a" @@ -381,6 +382,10 @@ EOF baseline "b^3^2" baseline "a^^3^2" + # invalid + baseline "^^" + baseline "^^HEAD" + baseline "@{-1}" baseline "@{-2}" baseline "@{-3}" diff --git a/gix/tests/gix/revision/spec/from_bytes/mod.rs b/gix/tests/gix/revision/spec/from_bytes/mod.rs index 78eb9f55dae..3c916cdb671 100644 --- a/gix/tests/gix/revision/spec/from_bytes/mod.rs +++ b/gix/tests/gix/revision/spec/from_bytes/mod.rs @@ -64,6 +64,12 @@ mod index { "Path \"file\" did not exist in index at stage 1. It does exist at stage 0. It exists on disk", ); + assert_eq!( + parse_spec(":5:file", &repo).unwrap_err().to_string(), + "Path \"5:file\" did not exist in index at stage 0. It does not exist on disk", + "invalid stage ids are interpreted as part of the filename" + ); + assert_eq!( parse_spec(":foo", &repo).unwrap_err().to_string(), "Path \"foo\" did not exist in index at stage 0. It does not exist on disk", diff --git a/gix/tests/gix/revision/spec/from_bytes/traverse.rs b/gix/tests/gix/revision/spec/from_bytes/traverse.rs index 345a72bbb6e..8a28b4da79e 100644 --- a/gix/tests/gix/revision/spec/from_bytes/traverse.rs +++ b/gix/tests/gix/revision/spec/from_bytes/traverse.rs @@ -24,8 +24,16 @@ fn complex() -> crate::Result { #[test] fn freestanding_negation_yields_descriptive_error() -> crate::Result { let repo = repo("complex_graph")?; - let expected = "The rev-spec is malformed and misses a ref name"; - assert_eq!(parse_spec("^", &repo).unwrap_err().to_string(), expected); + for revspec in ["^^", "^^HEAD"] { + assert_eq!( + parse_spec(revspec, &repo).unwrap_err().to_string(), + "Tried to navigate the commit-graph without providing an anchor first" + ); + } + assert_eq!( + parse_spec("^", &repo).unwrap_err().to_string(), + "The rev-spec is malformed and misses a ref name" + ); assert_eq!( parse_spec("^!", &repo).unwrap_err().to_string(), "The ref partially named \"!\" could not be found"