mirror of
https://github.com/awfixers-stuff/src.git
synced 2026-03-24 03:25:59 +00:00
create src
This commit is contained in:
65
src-revision/tests/revision/spec/display.rs
Normal file
65
src-revision/tests/revision/spec/display.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use crate::hex_to_id;
|
||||
|
||||
fn oid() -> gix_hash::ObjectId {
|
||||
hex_to_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
}
|
||||
|
||||
fn oid2() -> gix_hash::ObjectId {
|
||||
hex_to_id("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn include() {
|
||||
assert_eq!(
|
||||
gix_revision::Spec::Include(oid()).to_string(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude() {
|
||||
assert_eq!(
|
||||
gix_revision::Spec::Exclude(oid()).to_string(),
|
||||
"^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range() {
|
||||
assert_eq!(
|
||||
gix_revision::Spec::Range {
|
||||
from: oid(),
|
||||
to: oid2()
|
||||
}
|
||||
.to_string(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
assert_eq!(
|
||||
gix_revision::Spec::Merge {
|
||||
theirs: oid(),
|
||||
ours: oid2()
|
||||
}
|
||||
.to_string(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn include_parents() {
|
||||
assert_eq!(
|
||||
gix_revision::Spec::IncludeOnlyParents(oid()).to_string(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^@"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_parents() {
|
||||
assert_eq!(
|
||||
gix_revision::Spec::ExcludeParents(oid()).to_string(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^!"
|
||||
);
|
||||
}
|
||||
2
src-revision/tests/revision/spec/mod.rs
Normal file
2
src-revision/tests/revision/spec/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod display;
|
||||
mod parse;
|
||||
226
src-revision/tests/revision/spec/parse/anchor/at_symbol.rs
Normal file
226
src-revision/tests/revision/spec/parse/anchor/at_symbol.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
use crate::spec::parse::{parse, try_parse};
|
||||
|
||||
#[test]
|
||||
fn braces_must_be_closed() {
|
||||
for unclosed_spec in ["@{something", "@{", "@{..@"] {
|
||||
let err = try_parse(unclosed_spec).unwrap_err();
|
||||
assert_eq!(
|
||||
err.input.as_ref().map(std::convert::AsRef::as_ref),
|
||||
Some(&unclosed_spec.as_bytes()[1..])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")] // Only works this way on 64-bit systems.
|
||||
fn fuzzed() {
|
||||
let rec = parse("@{-9223372036854775808}");
|
||||
assert_eq!(rec.nth_checked_out_branch, [Some(9223372036854775808), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_entry_for_current_branch() {
|
||||
for (spec, expected_entry) in [("@{0}", 0), ("@{42}", 42), ("@{00100}", 100)] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(
|
||||
rec.prefix[0], None,
|
||||
"neither ref nor prefixes are set, straight to navigation"
|
||||
);
|
||||
assert_eq!(rec.current_branch_reflog_entry[0], Some(expected_entry.to_string()));
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_date_for_current_branch() {
|
||||
let rec = parse("@{42 +0030}");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(
|
||||
rec.prefix[0], None,
|
||||
"neither ref nor prefixes are set, straight to navigation"
|
||||
);
|
||||
assert_eq!(rec.current_branch_reflog_entry[0], Some("42 +0030".to_string()));
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_unix_timestamp_for_current_branch() {
|
||||
let rec = parse("@{100000000}");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(
|
||||
rec.prefix[0], None,
|
||||
"neither ref nor prefixes are set, straight to navigation"
|
||||
);
|
||||
assert_eq!(
|
||||
rec.current_branch_reflog_entry[0],
|
||||
Some("100000000 +0000".to_string()),
|
||||
"This number is the first to count as date"
|
||||
);
|
||||
assert_eq!(rec.calls, 1);
|
||||
|
||||
let rec = parse("@{99999999}");
|
||||
assert_eq!(
|
||||
rec.current_branch_reflog_entry[0],
|
||||
Some("99999999".to_string()),
|
||||
"one less is an offset though"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_date_with_date_parse_failure() {
|
||||
let err = try_parse("@{foo}").unwrap_err();
|
||||
insta::assert_snapshot!(err, @"could not parse time for reflog lookup: foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_date_for_hash_is_invalid() {
|
||||
for (spec, full_name) in [
|
||||
("1234@{42 +0030}", "1234"),
|
||||
("abcd-dirty@{42 +0030}", "abcd-dirty"),
|
||||
("v1.2.3-0-g1234@{42 +0030}", "v1.2.3-0-g1234"),
|
||||
] {
|
||||
let err = try_parse(spec).unwrap_err();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(full_name.as_bytes()));
|
||||
assert!(err.message.contains("reflog entries require a ref name"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_date_for_given_ref_name() {
|
||||
for (spec, expected_ref) in [
|
||||
("main@{42 +0030}", "main"),
|
||||
("refs/heads/other@{42 +0030}", "refs/heads/other"),
|
||||
("refs/worktree/feature/a@{42 +0030}", "refs/worktree/feature/a"),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), expected_ref);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.current_branch_reflog_entry[0], Some("42 +0030".to_string()));
|
||||
assert_eq!(rec.calls, 2, "first the ref, then the reflog entry");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_entry_for_given_ref_name() {
|
||||
for (spec, expected_ref, expected_entry) in [
|
||||
("main@{0}", "main", 0),
|
||||
("refs/heads/other@{42}", "refs/heads/other", 42),
|
||||
("refs/worktree/feature/a@{00100}", "refs/worktree/feature/a", 100),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), expected_ref);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.current_branch_reflog_entry[0], Some(expected_entry.to_string()));
|
||||
assert_eq!(rec.calls, 2, "first the ref, then the reflog entry");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflog_by_entry_for_hash_is_invalid() {
|
||||
for (spec, full_name) in [
|
||||
("1234@{0}", "1234"),
|
||||
("abcd-dirty@{1}", "abcd-dirty"),
|
||||
("v1.2.3-0-g1234@{2}", "v1.2.3-0-g1234"),
|
||||
] {
|
||||
let err = try_parse(spec).unwrap_err();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(full_name.as_bytes()));
|
||||
assert!(err.message.contains("reflog entries require a ref name"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sibling_branch_current_branch() {
|
||||
for (spec, kind_name) in [("@{u}", "Upstream"), ("@{push}", "Push"), ("@{UPSTREAM}", "Upstream")] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], None, "neither ref nor prefix are explicitly set");
|
||||
assert_eq!(rec.sibling_branch[0].as_deref(), Some(kind_name));
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sibling_branch_for_branch_name() {
|
||||
for (spec, ref_name, kind_name) in [
|
||||
("r1@{U}", "r1", "Upstream"),
|
||||
("refs/heads/main@{Push}", "refs/heads/main", "Push"),
|
||||
("refs/worktree/private@{UpStreaM}", "refs/worktree/private", "Upstream"),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), ref_name);
|
||||
assert_eq!(rec.prefix[0], None, "neither ref nor prefix are explicitly set");
|
||||
assert_eq!(
|
||||
rec.sibling_branch[0].as_deref(),
|
||||
Some(kind_name),
|
||||
"note that we do not know if something is a branch or not and make the call even if it would not be allowed. Configuration decides"
|
||||
);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sibling_branch_for_hash_is_invalid() {
|
||||
for (spec, full_name) in [
|
||||
("1234@{u}", "1234"),
|
||||
("abcd-dirty@{push}", "abcd-dirty"),
|
||||
("v1.2.3-0-g1234@{upstream}", "v1.2.3-0-g1234"),
|
||||
] {
|
||||
let err = try_parse(spec).unwrap_err();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(full_name.as_bytes()));
|
||||
assert!(err.message.contains("sibling branches"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nth_checked_out_branch_for_refname_is_invalid() {
|
||||
let err = try_parse("r1@{-1}").unwrap_err();
|
||||
// its undefined how to handle negative numbers and specified ref names
|
||||
insta::assert_snapshot!(err, @"reference name must be followed by positive numbers in @{n}: -1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nth_checked_out_branch() {
|
||||
for (spec, expected_branch) in [("@{-1}", 1), ("@{-42}", 42), ("@{-00100}", 100)] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(
|
||||
rec.prefix[0], None,
|
||||
"neither ref nor prefixes are set, straight to navigation"
|
||||
);
|
||||
assert_eq!(rec.nth_checked_out_branch[0], Some(expected_branch));
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numbers_within_braces_cannot_be_negative_zero() {
|
||||
let err = try_parse("@{-0}").unwrap_err();
|
||||
// negative zero is not accepted, even though it could easily be defaulted to 0 which is a valid value
|
||||
insta::assert_snapshot!(err, @"negative zero is invalid - remove the minus sign: -0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numbers_within_braces_can_be_positive_zero() {
|
||||
assert_eq!(
|
||||
parse("@{+0}"),
|
||||
parse("@{0}"),
|
||||
"+ prefixes are allowed though and the same as without it"
|
||||
);
|
||||
}
|
||||
140
src-revision/tests/revision/spec/parse/anchor/colon_symbol.rs
Normal file
140
src-revision/tests/revision/spec/parse/anchor/colon_symbol.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use crate::spec::parse::{parse, try_parse};
|
||||
|
||||
#[test]
|
||||
fn regex_parsing_ignores_ranges_as_opposed_to_git() {
|
||||
for spec in [":/a..b", ":/a...b"] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(
|
||||
rec.patterns,
|
||||
vec![(spec[2..].into(), false)],
|
||||
"git parses ranges but I think it's merely coincidental rather than intended, not doing so allows to use '.' more liberally"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_lookups_ignores_ranges_as_opposed_to_git() {
|
||||
for spec in [":a..b", ":a...b"] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(
|
||||
rec.index_lookups,
|
||||
vec![(spec[1..].into(), 0)],
|
||||
"git parses ranges but it's never useful as these specs only ever produce blob ids"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn various_forms_of_regex() {
|
||||
for (spec, (regex, negated)) in [
|
||||
(":/simple", ("simple", false)),
|
||||
(":/!-negated", ("negated", true)),
|
||||
(":/^from start", ("^from start", false)),
|
||||
(":/!!leading exclamation mark", ("!leading exclamation mark", false)),
|
||||
(":/with count{1}", ("with count{1}", false)),
|
||||
(
|
||||
":/all-consuming makes navigation impossible^5~10",
|
||||
("all-consuming makes navigation impossible^5~10", false),
|
||||
),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.patterns, vec![(regex.into(), negated)]);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_do_not_get_any_backslash_processing() {
|
||||
for (spec, regex) in [(r#":/{"#, "{"), (r":/\{\}", r"\{\}"), (r":/\\\\\}", r"\\\\\}")] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert_eq!(rec.patterns, vec![(regex.into(), false)]);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn various_valid_index_lookups_by_path() {
|
||||
for spec in [
|
||||
":path",
|
||||
":dir/path",
|
||||
":./relative-to.cwd",
|
||||
":../relative-to-cwd-too",
|
||||
":navigation/is/ignored~10^^^",
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.index_lookups, vec![(spec[1..].into(), 0)]);
|
||||
assert_eq!(rec.peel_to, vec![], "peeling only works for anchors");
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn various_valid_index_lookups_by_path_and_stage() {
|
||||
for (spec, path, stage) in [
|
||||
(":0:path", "path", 0),
|
||||
(":1:dir/path", "dir/path", 1),
|
||||
(":2:dir/path@{part-of-path}", "dir/path@{part-of-path}", 2),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.index_lookups, vec![(path.into(), stage)]);
|
||||
assert_eq!(rec.peel_to, vec![], "peeling only works for anchors");
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_top_level_regex_are_invalid() {
|
||||
let err = try_parse(":/").unwrap_err();
|
||||
// git also can't do it, finds nothing instead. It could be the youngest commit in theory, but isn't
|
||||
insta::assert_snapshot!(err, @"':/' must be followed by a regular expression");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_with_empty_exclamation_mark_prefix_is_invalid() {
|
||||
let err = try_parse(r#":/!hello"#).unwrap_err();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(b"!hello".as_ref()));
|
||||
insta::assert_snapshot!(err, @"need one character after /!, typically -: !hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn needs_suffix() {
|
||||
let err = try_parse(":").unwrap_err();
|
||||
// git also can't do it, finds nothing instead. It could be the youngest commit in theory, but isn't
|
||||
insta::assert_snapshot!(err, @"':' must be followed by either slash and regex or path to lookup in HEAD tree");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_index_stage_is_part_of_path() {
|
||||
for spec in [":4:file", ":5:file", ":01:file", ":10:file"] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.patterns, vec![]);
|
||||
assert_eq!(
|
||||
rec.index_lookups,
|
||||
vec![(spec[1..].into(), 0)],
|
||||
"these count as stage 0 lookups"
|
||||
);
|
||||
assert_eq!(rec.peel_to, vec![]);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
132
src-revision/tests/revision/spec/parse/anchor/describe.rs
Normal file
132
src-revision/tests/revision/spec/parse/anchor/describe.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use crate::spec::parse::{parse, try_parse_opts, Options, PrefixHintOwned};
|
||||
|
||||
fn anchor_hint() -> Option<PrefixHintOwned> {
|
||||
Some(PrefixHintOwned::DescribeAnchor {
|
||||
ref_name: "cargo-smart-release".into(),
|
||||
generation: 679,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_format_parses_hash_portion_as_prefix() {
|
||||
let rec = parse("cargo-smart-release-679-g3bee7fb");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None, "references are not resolved in describe output");
|
||||
assert_eq!(rec.prefix[0], Some(gix_hash::Prefix::from_hex("3bee7fb").unwrap()));
|
||||
assert_eq!(rec.prefix_hint[0], anchor_hint());
|
||||
assert_eq!(rec.calls, 1);
|
||||
|
||||
let rec = parse("v1.0-0-g3bee7fb");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None, "references are not resolved in describe output");
|
||||
assert_eq!(rec.prefix[0], Some(gix_hash::Prefix::from_hex("3bee7fb").unwrap()));
|
||||
assert_eq!(
|
||||
rec.prefix_hint[0],
|
||||
Some(PrefixHintOwned::DescribeAnchor {
|
||||
ref_name: "v1.0".into(),
|
||||
generation: 0,
|
||||
})
|
||||
);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_format_lookalikes_fallback_to_ref() {
|
||||
let spec = "cargo-smart-release-679-g3bee7fb";
|
||||
let rec = try_parse_opts(
|
||||
spec,
|
||||
Options {
|
||||
reject_prefix: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), spec);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.prefix_hint[0], None);
|
||||
assert_eq!(rec.calls, 2, "call prefix, then call ref");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_hash_without_suffix_and_prefix_g_is_assumed_to_be_describe_output() {
|
||||
let spec = "foo--bar-gabcdef1";
|
||||
let rec = parse(spec);
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(
|
||||
rec.prefix[0],
|
||||
Some(gix_hash::Prefix::from_hex("abcdef1").unwrap()),
|
||||
"git does not parse very precisely here"
|
||||
);
|
||||
assert_eq!(rec.prefix_hint[0], Some(PrefixHintOwned::MustBeCommit));
|
||||
assert_eq!(rec.calls, 1);
|
||||
|
||||
for invalid_describe in ["-gabcdef1", "gabcdef1"] {
|
||||
let rec = parse(invalid_describe);
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(
|
||||
rec.get_ref(0),
|
||||
invalid_describe,
|
||||
"we don't consider this a prefix from a describe block"
|
||||
);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.prefix_hint[0], None);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_format_with_dirty_suffix_is_recognized() {
|
||||
let rec = parse("cargo-smart-release-679-g3bee7fb-dirty");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None, "git does not see this as prefix, we do");
|
||||
assert_eq!(rec.prefix[0], Some(gix_hash::Prefix::from_hex("3bee7fb").unwrap()));
|
||||
assert_eq!(rec.prefix_hint[0], anchor_hint());
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_format_with_dirty_suffix_is_recognized() {
|
||||
let spec = "abcdef1-dirty";
|
||||
let rec = parse(spec);
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(
|
||||
rec.prefix[0],
|
||||
Some(gix_hash::Prefix::from_hex("abcdef1").unwrap()),
|
||||
"git does not see this as prefix anymore, we do"
|
||||
);
|
||||
assert_eq!(
|
||||
rec.prefix_hint[0], None,
|
||||
"This leaves room for improvement as we could assume that -dirty belongs to a revision, so this could be PrefixHint::MustBeCommit"
|
||||
);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_format_lookalikes_are_never_considered() {
|
||||
let spec = "abcdef1-dirty-laundry";
|
||||
let rec = parse(spec);
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), spec);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.calls, 1, "we don't even try the prefix");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_format_with_dirty_suffix_lookalikes_are_treated_as_refs() {
|
||||
let spec = "abcdef1-dirty";
|
||||
let rec = try_parse_opts(
|
||||
spec,
|
||||
Options {
|
||||
reject_prefix: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), spec);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
66
src-revision/tests/revision/spec/parse/anchor/hash.rs
Normal file
66
src-revision/tests/revision/spec/parse/anchor/hash.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::spec::parse::{parse, try_parse_opts, Options};
|
||||
|
||||
#[test]
|
||||
fn short_hex_literals_are_considered_prefixes() {
|
||||
let rec = parse("abCD");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(
|
||||
rec.find_ref[0], None,
|
||||
"references are not resolved if prefix lookups succeed"
|
||||
);
|
||||
assert_eq!(rec.prefix[0], Some(gix_hash::Prefix::from_hex("abcd").unwrap()));
|
||||
assert_eq!(rec.prefix_hint[0], None);
|
||||
assert_eq!(rec.calls, 1);
|
||||
|
||||
let rec = parse("gabcd123");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(
|
||||
rec.get_ref(0),
|
||||
"gabcd123",
|
||||
"ref lookups are performed if it doesn't look like a hex sha"
|
||||
);
|
||||
assert_eq!(
|
||||
rec.prefix[0], None,
|
||||
"prefix lookups are not attempted at all (and they are impossible even)"
|
||||
);
|
||||
assert_eq!(rec.prefix_hint[0], None);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unresolvable_hex_literals_are_resolved_as_refs() {
|
||||
let rec = try_parse_opts(
|
||||
"abCD",
|
||||
Options {
|
||||
reject_prefix: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "abCD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.prefix_hint[0], None);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_literals_that_are_too_long_are_resolved_as_refs() {
|
||||
let spec = "abcd123456789abcd123456789abcd123456789abcd123456789abcd123456789abcd123456789abcd123456789";
|
||||
let rec = try_parse_opts(
|
||||
spec,
|
||||
Options {
|
||||
reject_prefix: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), spec);
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.prefix_hint[0], None);
|
||||
assert_eq!(
|
||||
rec.calls, 1,
|
||||
"we can't create a prefix from it, hence only ref resolution is attempted"
|
||||
);
|
||||
}
|
||||
5
src-revision/tests/revision/spec/parse/anchor/mod.rs
Normal file
5
src-revision/tests/revision/spec/parse/anchor/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod at_symbol;
|
||||
mod colon_symbol;
|
||||
mod describe;
|
||||
mod hash;
|
||||
mod refnames;
|
||||
68
src-revision/tests/revision/spec/parse/anchor/refnames.rs
Normal file
68
src-revision/tests/revision/spec/parse/anchor/refnames.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use crate::spec::parse::{parse, try_parse};
|
||||
|
||||
#[test]
|
||||
fn at_by_itself_is_shortcut_for_head() {
|
||||
let rec = parse("@");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_is_allowed() {
|
||||
for name in ["a@b", "@branch", "branch@", "@@", "@inner@"] {
|
||||
let rec = parse(name);
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), name);
|
||||
assert_eq!(rec.find_ref[1], None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_in_ranges_is_allowed() {
|
||||
let input = "@@@..";
|
||||
let rec = parse(input);
|
||||
assert_eq!(rec.kind, Some(gix_revision::spec::Kind::RangeBetween));
|
||||
assert_eq!(rec.get_ref(0), "@@@");
|
||||
assert_eq!(rec.get_ref(1), "HEAD");
|
||||
|
||||
let input = "@@...@@";
|
||||
let rec = parse(input);
|
||||
assert_eq!(rec.kind, Some(gix_revision::spec::Kind::ReachableToMergeBase));
|
||||
assert_eq!(rec.get_ref(0), "@@");
|
||||
assert_eq!(rec.get_ref(1), "@@");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strange_revspecs_do_not_panic() {
|
||||
let err = try_parse(".@.").unwrap_err();
|
||||
insta::assert_snapshot!(err, @"@ character must be standalone or followed by {<content>}: @.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refname_head() {
|
||||
let rec = parse("HEAD");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refname_tag() {
|
||||
let spec = "v1.2.3.4-beta.1";
|
||||
let rec = parse(spec);
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), spec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refname_with_head_prefix() {
|
||||
let rec = parse("HEADfake");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEADfake");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_head_ref_name() {
|
||||
let rec = parse("refs/heads/main");
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "refs/heads/main");
|
||||
}
|
||||
425
src-revision/tests/revision/spec/parse/kind.rs
Normal file
425
src-revision/tests/revision/spec/parse/kind.rs
Normal file
@@ -0,0 +1,425 @@
|
||||
use crate::spec::parse::{try_parse, try_parse_opts, Options};
|
||||
|
||||
#[test]
|
||||
fn cannot_declare_ranges_multiple_times() {
|
||||
for invalid_spec in ["^HEAD..", "^HEAD..."] {
|
||||
let err = try_parse(invalid_spec).unwrap_err().into_inner();
|
||||
assert!(err.message.contains("cannot set spec kind more than once"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegate_can_refuse_spec_kinds() {
|
||||
let err = try_parse_opts(
|
||||
"^HEAD",
|
||||
Options {
|
||||
reject_kind: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap_err()
|
||||
.into_inner();
|
||||
// Delegates can refuse spec kind changes to abort parsing early in case they want single-specs only
|
||||
insta::assert_snapshot!(err, @"delegate.kind(ExcludeReachable) failed");
|
||||
}
|
||||
|
||||
mod include_parents {
|
||||
use gix_revision::spec;
|
||||
|
||||
use crate::spec::parse::{kind::prefix, parse, try_parse, Call};
|
||||
|
||||
#[test]
|
||||
fn trailing_caret_at_symbol() {
|
||||
let rec = parse("HEAD^@");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::IncludeReachableFromParents);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Kind]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("abcd^@");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::IncludeReachableFromParents);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.order, [Call::DisambiguatePrefix, Call::Kind]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("r1^@");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::IncludeReachableFromParents);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Kind]);
|
||||
assert!(rec.done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_caret_exclamation_mark_must_end_the_input() {
|
||||
let err = try_parse("r1^@~1").unwrap_err().into_inner();
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
}
|
||||
}
|
||||
|
||||
mod exclude_parents {
|
||||
use gix_revision::spec;
|
||||
|
||||
use crate::spec::parse::{kind::prefix, parse, try_parse, Call};
|
||||
|
||||
#[test]
|
||||
fn freestanding() {
|
||||
let rec = parse("^!");
|
||||
assert_eq!(
|
||||
rec.kind,
|
||||
Some(gix_revision::spec::Kind::ExcludeReachable),
|
||||
"the delegate has to be able to deal with this"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_caret_exclamation_mark() {
|
||||
let rec = parse("HEAD^!");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ExcludeReachableFromParents);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Kind]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("abcd^!");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ExcludeReachableFromParents);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.order, [Call::DisambiguatePrefix, Call::Kind]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("r1^!");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ExcludeReachableFromParents);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Kind]);
|
||||
assert!(rec.done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_caret_exclamation_mark_must_end_the_input() {
|
||||
let err = try_parse("r1^!~1").unwrap_err().into_inner();
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
}
|
||||
}
|
||||
|
||||
mod exclusive {
|
||||
use gix_revision::spec;
|
||||
|
||||
use crate::spec::parse::{kind::prefix, parse};
|
||||
|
||||
#[test]
|
||||
fn freestanding() {
|
||||
let rec = parse("^");
|
||||
assert_eq!(
|
||||
rec.kind,
|
||||
Some(gix_revision::spec::Kind::ExcludeReachable),
|
||||
"the delegate has to be able to deal with this"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_caret() {
|
||||
let rec = parse("^HEAD");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ExcludeReachable);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.calls, 2);
|
||||
|
||||
let rec = parse("^abcd");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ExcludeReachable);
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.calls, 2);
|
||||
|
||||
let rec = parse("^r1");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ExcludeReachable);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.calls, 2);
|
||||
|
||||
let rec = parse("^hello-0-gabcd-dirty");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ExcludeReachable);
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
}
|
||||
|
||||
mod range {
|
||||
use gix_revision::{spec, spec::parse::delegate::Traversal};
|
||||
|
||||
use crate::spec::parse::{kind::prefix, parse, try_parse, Call};
|
||||
|
||||
#[test]
|
||||
fn minus_with_n_omitted() {
|
||||
let rec = parse("r1^-");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.traversal, [Traversal::NthParent(1)], "default is 1");
|
||||
assert_eq!(rec.get_ref(1), "r1");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Traverse, Call::Kind, Call::FindRef]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("@^-");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.traversal, [Traversal::NthParent(1)], "default is 1");
|
||||
assert_eq!(rec.get_ref(1), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Traverse, Call::Kind, Call::FindRef]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("abcd^-");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.traversal, [Traversal::NthParent(1)], "default is 1");
|
||||
assert_eq!(rec.prefix[1], prefix("abcd").into());
|
||||
assert_eq!(
|
||||
rec.order,
|
||||
[
|
||||
Call::DisambiguatePrefix,
|
||||
Call::Traverse,
|
||||
Call::Kind,
|
||||
Call::DisambiguatePrefix
|
||||
]
|
||||
);
|
||||
assert!(rec.done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_with_n() {
|
||||
let rec = parse("r1^-42");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.traversal, [Traversal::NthParent(42)]);
|
||||
assert_eq!(rec.get_ref(1), "r1");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Traverse, Call::Kind, Call::FindRef]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("@^-42");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.traversal, [Traversal::NthParent(42)]);
|
||||
assert_eq!(rec.get_ref(1), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Traverse, Call::Kind, Call::FindRef]);
|
||||
assert!(rec.done);
|
||||
|
||||
let rec = parse("abcd^-42");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.traversal, [Traversal::NthParent(42)]);
|
||||
assert_eq!(rec.prefix[1], prefix("abcd").into());
|
||||
assert_eq!(
|
||||
rec.order,
|
||||
[
|
||||
Call::DisambiguatePrefix,
|
||||
Call::Traverse,
|
||||
Call::Kind,
|
||||
Call::DisambiguatePrefix
|
||||
]
|
||||
);
|
||||
assert!(rec.done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_with_n_omitted_has_to_end_there() {
|
||||
let err = try_parse("r1^-^").unwrap_err().into_inner();
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_with_n_has_to_end_there() {
|
||||
let err = try_parse("r1^-42^").unwrap_err().into_inner();
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_with_n_has_to_end_there_and_handle_range_suffix() {
|
||||
let err = try_parse("r1^-42..").unwrap_err().into_inner();
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minus_with_n_omitted_has_to_end_there_and_handle_range_suffix() {
|
||||
let err = try_parse("r1^-..").unwrap_err().into_inner();
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freestanding_dot_dot() {
|
||||
let rec = parse("..");
|
||||
assert_eq!(
|
||||
rec.kind,
|
||||
Some(gix_revision::spec::Kind::RangeBetween),
|
||||
"the delegate has to be able to deal with this"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_dot_dot() {
|
||||
let rec = parse("r1..");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.get_ref(1), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Kind, Call::FindRef]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_dot_dot() {
|
||||
let rec = parse("..r2");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.get_ref(1), "r2");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.order, [Call::FindRef, Call::Kind, Call::FindRef]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn middle_dot_dot() {
|
||||
let rec = parse("@..r2");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.get_ref(1), "r2");
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("r1..r2");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.get_ref(1), "r2");
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("abcd..1234");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.prefix[1], prefix("1234").into());
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("r1..abcd");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("abcd-dirty..v1.2-42-g1234");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.prefix[1], prefix("1234").into());
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("v1.2-42-g1234..abcd-dirty");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], prefix("1234").into());
|
||||
assert_eq!(rec.prefix[1], prefix("abcd").into());
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("v1.2.4@{1}~~^10..r1@{2}~10^2");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::RangeBetween);
|
||||
assert_eq!(rec.get_ref(0), "v1.2.4");
|
||||
assert_eq!(rec.get_ref(1), "r1");
|
||||
assert_eq!(&rec.prefix, &[None, None]);
|
||||
assert_eq!(
|
||||
rec.traversal,
|
||||
[
|
||||
Traversal::NthAncestor(1),
|
||||
Traversal::NthAncestor(1),
|
||||
Traversal::NthParent(10),
|
||||
Traversal::NthAncestor(10),
|
||||
Traversal::NthParent(2)
|
||||
]
|
||||
);
|
||||
assert_eq!(rec.calls, 10);
|
||||
assert!(rec.done);
|
||||
}
|
||||
}
|
||||
|
||||
mod mergebase {
|
||||
use gix_revision::{spec, spec::parse::delegate::Traversal};
|
||||
|
||||
use crate::spec::parse::{kind::prefix, parse};
|
||||
|
||||
#[test]
|
||||
fn freestanding_dot_dot_dot() {
|
||||
let rec = parse("...");
|
||||
assert_eq!(
|
||||
rec.kind,
|
||||
Some(gix_revision::spec::Kind::ReachableToMergeBase),
|
||||
"the delegate has to be able to deal with this"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_dot_dot_dot() {
|
||||
let rec = parse("HEAD...");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ReachableToMergeBase);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.calls, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_dot_dot_dot() {
|
||||
let rec = parse("...r2");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ReachableToMergeBase);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.get_ref(1), "r2");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.calls, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn middle_dot_dot_dot() {
|
||||
let rec = parse("HEAD...@");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ReachableToMergeBase);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.get_ref(1), "HEAD");
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("@...HEAD");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ReachableToMergeBase);
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.get_ref(1), "HEAD");
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("r1...abcd");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ReachableToMergeBase);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.prefix[0], prefix("abcd").into());
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("v1.2.3-beta.1-42-g1234-dirty...abcd-dirty");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ReachableToMergeBase);
|
||||
assert_eq!(rec.find_ref[0], None);
|
||||
assert_eq!(rec.prefix[0], prefix("1234").into());
|
||||
assert_eq!(rec.prefix[1], prefix("abcd").into());
|
||||
assert_eq!(rec.calls, 3);
|
||||
|
||||
let rec = parse("r1@{1}~~^10...@{2}~10^2");
|
||||
assert_eq!(rec.kind.unwrap(), spec::Kind::ReachableToMergeBase);
|
||||
assert_eq!(rec.get_ref(0), "r1");
|
||||
assert_eq!(rec.find_ref[1], None, "HEAD is implied");
|
||||
assert_eq!(&rec.prefix, &[None, None]);
|
||||
assert_eq!(
|
||||
rec.traversal,
|
||||
[
|
||||
Traversal::NthAncestor(1),
|
||||
Traversal::NthAncestor(1),
|
||||
Traversal::NthParent(10),
|
||||
Traversal::NthAncestor(10),
|
||||
Traversal::NthParent(2)
|
||||
]
|
||||
);
|
||||
assert_eq!(rec.calls, 9);
|
||||
assert!(rec.done);
|
||||
}
|
||||
}
|
||||
|
||||
fn prefix(hex: &str) -> gix_hash::Prefix {
|
||||
gix_hash::Prefix::from_hex(hex).unwrap()
|
||||
}
|
||||
273
src-revision/tests/revision/spec/parse/mod.rs
Normal file
273
src-revision/tests/revision/spec/parse/mod.rs
Normal file
@@ -0,0 +1,273 @@
|
||||
use gix_error::{bail, message, ErrorExt, Exn};
|
||||
use gix_object::bstr::{BStr, BString};
|
||||
use gix_revision::{
|
||||
spec,
|
||||
spec::parse::{delegate, Delegate},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct Options {
|
||||
reject_kind: bool,
|
||||
reject_prefix: bool,
|
||||
no_internal_assertions: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct Recorder {
|
||||
// anchors
|
||||
find_ref: [Option<BString>; 2],
|
||||
prefix: [Option<gix_hash::Prefix>; 2],
|
||||
prefix_hint: [Option<PrefixHintOwned>; 2],
|
||||
current_branch_reflog_entry: [Option<String>; 2],
|
||||
nth_checked_out_branch: [Option<usize>; 2],
|
||||
sibling_branch: [Option<String>; 2],
|
||||
index_lookups: Vec<(BString, u8)>,
|
||||
|
||||
// navigation
|
||||
traversal: Vec<delegate::Traversal>,
|
||||
peel_to: Vec<PeelToOwned>,
|
||||
patterns: Vec<(BString, bool)>,
|
||||
|
||||
// range
|
||||
kind: Option<spec::Kind>,
|
||||
|
||||
order: Vec<Call>,
|
||||
calls: usize,
|
||||
opts: Options,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Call {
|
||||
FindRef,
|
||||
DisambiguatePrefix,
|
||||
Reflog,
|
||||
NthCheckedOutBranch,
|
||||
SiblingBranch,
|
||||
Traverse,
|
||||
PeelUntil,
|
||||
Find,
|
||||
IndexLookup,
|
||||
Kind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum PeelToOwned {
|
||||
ObjectKind(gix_object::Kind),
|
||||
ExistingObject,
|
||||
RecursiveTagObject,
|
||||
Path(BString),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum PrefixHintOwned {
|
||||
MustBeCommit,
|
||||
DescribeAnchor { ref_name: BString, generation: usize },
|
||||
}
|
||||
|
||||
impl Recorder {
|
||||
fn with(options: Options) -> Self {
|
||||
Recorder {
|
||||
opts: options,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ref(&self, idx: usize) -> &BStr {
|
||||
self.find_ref[idx].as_ref().map(AsRef::as_ref).unwrap()
|
||||
}
|
||||
|
||||
fn called(&mut self, f: Call) {
|
||||
self.calls += 1;
|
||||
self.order.push(f);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_val<T: std::fmt::Debug>(fn_name: &str, store: &mut [Option<T>; 2], val: T) -> Result<(), Exn> {
|
||||
for entry in store.iter_mut() {
|
||||
if entry.is_none() {
|
||||
*entry = Some(val);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
panic!("called {fn_name}() more than twice with '{val:?}'");
|
||||
}
|
||||
|
||||
impl delegate::Revision for Recorder {
|
||||
fn find_ref(&mut self, input: &BStr) -> Result<(), Exn> {
|
||||
self.called(Call::FindRef);
|
||||
set_val("find_ref", &mut self.find_ref, input.into())
|
||||
}
|
||||
|
||||
fn disambiguate_prefix(
|
||||
&mut self,
|
||||
input: gix_hash::Prefix,
|
||||
hint: Option<delegate::PrefixHint<'_>>,
|
||||
) -> Result<(), Exn> {
|
||||
self.called(Call::DisambiguatePrefix);
|
||||
if self.opts.reject_prefix {
|
||||
bail!(message!("disambiguate_prefix rejected").raise_erased());
|
||||
}
|
||||
set_val("disambiguate_prefix", &mut self.prefix, input)?;
|
||||
if let Some(hint) = hint {
|
||||
set_val(
|
||||
"disambiguate_prefix",
|
||||
&mut self.prefix_hint,
|
||||
match hint {
|
||||
delegate::PrefixHint::DescribeAnchor { ref_name, generation } => PrefixHintOwned::DescribeAnchor {
|
||||
ref_name: ref_name.into(),
|
||||
generation,
|
||||
},
|
||||
delegate::PrefixHint::MustBeCommit => PrefixHintOwned::MustBeCommit,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reflog(&mut self, entry: delegate::ReflogLookup) -> Result<(), Exn> {
|
||||
self.called(Call::Reflog);
|
||||
set_val(
|
||||
"current_branch_reflog",
|
||||
&mut self.current_branch_reflog_entry,
|
||||
match entry {
|
||||
delegate::ReflogLookup::Entry(no) => no.to_string(),
|
||||
delegate::ReflogLookup::Date(time) => {
|
||||
let mut buf = Vec::new();
|
||||
time.write_to(&mut buf).unwrap();
|
||||
BString::from(buf).to_string()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn nth_checked_out_branch(&mut self, branch: usize) -> Result<(), Exn> {
|
||||
assert_ne!(branch, 0);
|
||||
self.called(Call::NthCheckedOutBranch);
|
||||
set_val("nth_checked_out_branch", &mut self.nth_checked_out_branch, branch)
|
||||
}
|
||||
|
||||
fn sibling_branch(&mut self, kind: delegate::SiblingBranch) -> Result<(), Exn> {
|
||||
self.called(Call::SiblingBranch);
|
||||
set_val("sibling_branch", &mut self.sibling_branch, format!("{kind:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl delegate::Navigate for Recorder {
|
||||
fn traverse(&mut self, kind: delegate::Traversal) -> Result<(), Exn> {
|
||||
self.called(Call::Traverse);
|
||||
self.traversal.push(kind);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn peel_until(&mut self, kind: delegate::PeelTo) -> Result<(), Exn> {
|
||||
self.called(Call::PeelUntil);
|
||||
self.peel_to.push(match kind {
|
||||
delegate::PeelTo::ObjectKind(kind) => PeelToOwned::ObjectKind(kind),
|
||||
delegate::PeelTo::ValidObject => PeelToOwned::ExistingObject,
|
||||
delegate::PeelTo::Path(path) => PeelToOwned::Path(path.into()),
|
||||
delegate::PeelTo::RecursiveTagObject => PeelToOwned::RecursiveTagObject,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find(&mut self, regex: &BStr, negated: bool) -> Result<(), Exn> {
|
||||
self.called(Call::Find);
|
||||
self.patterns.push((regex.into(), negated));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn index_lookup(&mut self, path: &BStr, stage: u8) -> Result<(), Exn> {
|
||||
self.called(Call::IndexLookup);
|
||||
self.index_lookups.push((path.into(), stage));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl delegate::Kind for Recorder {
|
||||
fn kind(&mut self, kind: spec::Kind) -> Result<(), Exn> {
|
||||
self.called(Call::Kind);
|
||||
if self.opts.reject_kind {
|
||||
bail!(message!("kind() was rejected").raise_erased());
|
||||
}
|
||||
if self.kind.is_none() {
|
||||
self.kind = Some(kind);
|
||||
} else if !self.opts.no_internal_assertions {
|
||||
panic!("called kind more than once with '{kind:?}'");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Delegate for Recorder {
|
||||
fn done(&mut self) -> Result<(), Exn> {
|
||||
self.done = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(spec: &str) -> Recorder {
|
||||
try_parse_opts(spec, Options::default()).unwrap()
|
||||
}
|
||||
|
||||
fn try_parse(spec: &str) -> Result<Recorder, Exn<spec::parse::Error>> {
|
||||
try_parse_opts(spec, Default::default())
|
||||
}
|
||||
|
||||
fn try_parse_opts(spec: &str, options: Options) -> Result<Recorder, Exn<spec::parse::Error>> {
|
||||
let mut rec = Recorder::with(options);
|
||||
spec::parse(spec.into(), &mut rec)?;
|
||||
Ok(rec)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_specs_are_valid() {
|
||||
// they should of course be invalid for the delegate. CLIs may pre-process the input as well if they wish
|
||||
// but git itself doesn't do that.
|
||||
for spec in [" ", "\n\t"] {
|
||||
let rec = parse(spec);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
let rec = parse("");
|
||||
assert_eq!(rec.calls, 0, "but we do not bother to call the delegate with nothing");
|
||||
assert!(rec.done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_characters_are_taken_verbatim_which_includes_whitespace() {
|
||||
let spec = " HEAD \n";
|
||||
let rec = parse(spec);
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), spec);
|
||||
}
|
||||
|
||||
mod fuzz {
|
||||
use crate::spec::parse::{try_parse_opts, Options};
|
||||
|
||||
#[test]
|
||||
fn failures() {
|
||||
for spec in [
|
||||
"@{6255520 day ago}: ",
|
||||
"|^--",
|
||||
"^^-^",
|
||||
"^^-",
|
||||
":/!-",
|
||||
"A6a^-09223372036854775808",
|
||||
"^^^^^^-(",
|
||||
] {
|
||||
drop(
|
||||
try_parse_opts(
|
||||
spec,
|
||||
Options {
|
||||
no_internal_assertions: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap_err(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
mod anchor;
|
||||
mod kind;
|
||||
mod navigate;
|
||||
255
src-revision/tests/revision/spec/parse/navigate/caret_symbol.rs
Normal file
255
src-revision/tests/revision/spec/parse/navigate/caret_symbol.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
use gix_revision::spec::parse::delegate::Traversal;
|
||||
|
||||
use crate::spec::parse::{parse, try_parse, PeelToOwned as PeelTo};
|
||||
|
||||
#[test]
|
||||
fn single_is_first_parent() {
|
||||
let rec = parse("@^");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.traversal[0], Traversal::NthParent(1));
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_calls_stack() {
|
||||
let rec = parse("@^^^10^0^{tag}^020");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(
|
||||
rec.traversal,
|
||||
vec![
|
||||
Traversal::NthParent(1),
|
||||
Traversal::NthParent(1),
|
||||
Traversal::NthParent(10),
|
||||
Traversal::NthParent(20),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
rec.peel_to,
|
||||
vec![
|
||||
PeelTo::ObjectKind(gix_object::Kind::Commit),
|
||||
PeelTo::ObjectKind(gix_object::Kind::Tag)
|
||||
]
|
||||
);
|
||||
assert_eq!(rec.calls, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn followed_by_zero_is_peeling_to_commit() {
|
||||
let rec = parse("@^0");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.traversal.len(), 0, "traversals by parent are never zero");
|
||||
assert_eq!(
|
||||
rec.peel_to,
|
||||
vec![PeelTo::ObjectKind(gix_object::Kind::Commit)],
|
||||
"instead 0 serves as shortcut"
|
||||
);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicitly_positive_numbers_are_invalid() {
|
||||
let err = try_parse("@^+1").unwrap_err().into_inner();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(b"+1".as_ref()));
|
||||
assert!(err.message.contains("positive numbers are invalid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_parent_number() {
|
||||
for (spec, expected) in [
|
||||
("HEAD^1", 1),
|
||||
("abcd^10", 10),
|
||||
("v1.3.4^123", 123),
|
||||
("v1.3.4-12-g1234^1000", 1000),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert!(rec.find_ref[0].as_ref().is_some() || rec.prefix[0].is_some());
|
||||
assert_eq!(rec.traversal, vec![Traversal::NthParent(expected)]);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peel_to_object_type() {
|
||||
for (spec, expected) in [
|
||||
("HEAD^{commit}", PeelTo::ObjectKind(gix_object::Kind::Commit)),
|
||||
("abcd^{tree}", PeelTo::ObjectKind(gix_object::Kind::Tree)),
|
||||
("v1.3.4^{blob}", PeelTo::ObjectKind(gix_object::Kind::Blob)),
|
||||
("v1.3.4-12-g1234^{tag}", PeelTo::ObjectKind(gix_object::Kind::Tag)),
|
||||
("v1.3.4-12-g1234^{object}", PeelTo::ExistingObject),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert!(rec.find_ref[0].as_ref().is_some() || rec.prefix[0].is_some());
|
||||
assert_eq!(rec.peel_to, vec![expected]);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_backslash_rules() {
|
||||
for (spec, regex, msg) in [
|
||||
(
|
||||
r#"@^{/with count{1}}"#,
|
||||
r#"with count{1}"#,
|
||||
"matching inner parens do not need escaping",
|
||||
),
|
||||
(
|
||||
r"@^{/with count\{1\}}",
|
||||
r#"with count{1}"#,
|
||||
"escaped parens are entirely ignored",
|
||||
),
|
||||
(r"@^{/1\}}", r#"1}"#, "unmatched closing parens need to be escaped"),
|
||||
(r"@^{/2\{}", r#"2{"#, "unmatched opening parens need to be escaped"),
|
||||
(
|
||||
r"@^{/3{\{}}",
|
||||
r#"3{{}"#,
|
||||
"unmatched nested opening parens need to be escaped",
|
||||
),
|
||||
(
|
||||
r"@^{/4{\}}}",
|
||||
r#"4{}}"#,
|
||||
"unmatched nested closing parens need to be escaped",
|
||||
),
|
||||
(r"@^{/a\b\c}", r"a\b\c", "single backslashes do not need to be escaped"),
|
||||
(
|
||||
r"@^{/a\b\c\\}",
|
||||
r"a\b\c\",
|
||||
"single backslashes do not need to be escaped, trailing",
|
||||
),
|
||||
(
|
||||
r"@^{/a\\b\\c\\}",
|
||||
r"a\b\c\",
|
||||
"backslashes can be escaped nonetheless, trailing",
|
||||
),
|
||||
(
|
||||
r"@^{/5\\{}}",
|
||||
r"5\{}",
|
||||
"backslashes in front of parens must be escaped or they would unbalance the brace pair",
|
||||
),
|
||||
] {
|
||||
let rec = try_parse(spec).expect(msg);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert!(rec.find_ref[0].as_ref().is_some() || rec.prefix[0].is_some());
|
||||
assert_eq!(rec.patterns, vec![(regex.into(), false)], "{msg}");
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_with_revision_starting_point_and_negation() {
|
||||
for (spec, (regex, negated)) in [
|
||||
("HEAD^{/simple}", ("simple", false)),
|
||||
("abcd^{/!-negated}", ("negated", true)),
|
||||
("v1.3.4^{/^from start}", ("^from start", false)),
|
||||
(
|
||||
"v1.3.4-12-g1234^{/!!leading exclamation mark}",
|
||||
("!leading exclamation mark", false),
|
||||
),
|
||||
("v1.3.4-12-g1234^{/with count{1}}", ("with count{1}", false)),
|
||||
] {
|
||||
let rec = parse(spec);
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert!(rec.find_ref[0].as_ref().is_some() || rec.prefix[0].is_some());
|
||||
assert_eq!(rec.patterns, vec![(regex.into(), negated)]);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_braces_deref_a_tag() {
|
||||
let rec = parse("v1.2^{}");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "v1.2");
|
||||
assert_eq!(rec.peel_to, vec![PeelTo::RecursiveTagObject]);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_object_type() {
|
||||
let err = try_parse("@^{invalid}").unwrap_err().into_inner();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(b"invalid".as_ref()));
|
||||
assert!(err.message.contains("cannot peel"));
|
||||
|
||||
let err = try_parse("@^{Commit}").unwrap_err().into_inner();
|
||||
assert!(
|
||||
err.input.as_ref().map(AsRef::as_ref) == Some(b"Commit".as_ref()) && err.message.contains("cannot peel"),
|
||||
"these types are case sensitive"
|
||||
);
|
||||
}
|
||||
|
||||
#[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().into_inner();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(b"HEAD".as_ref()));
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_escaped_braces_in_regex_are_invalid() {
|
||||
let err = try_parse(r"@^{/a\{1}}").unwrap_err().into_inner();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(b"}".as_ref()));
|
||||
assert!(err.message.contains("unconsumed input"));
|
||||
|
||||
let err = try_parse(r"@^{/a{1\}}").unwrap_err().into_inner();
|
||||
assert!(
|
||||
err.input.as_ref().map(AsRef::as_ref) == Some(br"{/a{1\}}".as_ref()) && err.message.contains("unclosed brace")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_with_empty_exclamation_mark_prefix_is_invalid() {
|
||||
let err = try_parse(r#"@^{/!hello}"#).unwrap_err().into_inner();
|
||||
assert_eq!(err.input.as_ref().map(AsRef::as_ref), Some(b"!hello".as_ref()));
|
||||
assert!(err.message.contains("need one character after"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_escapes_can_cause_brace_mismatch() {
|
||||
let err = try_parse(r"@^{\}").unwrap_err().into_inner();
|
||||
assert!(err.input.as_ref().map(AsRef::as_ref) == Some(br"{\}".as_ref()) && err.message.contains("unclosed brace"));
|
||||
|
||||
let err = try_parse(r"@^{{\}}").unwrap_err().into_inner();
|
||||
// The raw string r"{{\}}" contains actual backslashes, so the input would be r"{{\}}"
|
||||
assert!(
|
||||
err.input.as_ref().map(AsRef::as_ref) == Some(br"{{\}}".as_ref()) && err.message.contains("unclosed brace")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_top_revision_regex_are_skipped_as_they_match_everything() {
|
||||
let rec = parse("@^{/}");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert!(
|
||||
rec.patterns.is_empty(),
|
||||
"The delegate won't be called with empty regexes"
|
||||
);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use gix_revision::spec::parse::delegate::Traversal;
|
||||
|
||||
use crate::spec::parse::{parse, PeelToOwned as PeelTo};
|
||||
|
||||
#[test]
|
||||
fn paths_consume_all_remaining_input_as_they_refer_to_blobs() {
|
||||
let rec = parse("@:../relative/path...@^^~~");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.traversal.len(), 0);
|
||||
assert_eq!(rec.peel_to, vec![PeelTo::Path("../relative/path...@^^~~".into())]);
|
||||
assert_eq!(rec.calls, 2);
|
||||
|
||||
let rec = parse("@:absolute/path^{object}");
|
||||
assert_eq!(
|
||||
rec.peel_to,
|
||||
vec![PeelTo::Path("absolute/path^{object}".into())],
|
||||
"this includes useful navigation like object-existence, a shortcoming git shares, proper implementation needs escaping as well."
|
||||
);
|
||||
|
||||
let rec = parse("@:absolute/path^{tree}");
|
||||
assert_eq!(
|
||||
rec.peel_to,
|
||||
vec![PeelTo::Path("absolute/path^{tree}".into())],
|
||||
"this includes useful navigation like assertion of trees/blobs, we may make this possible in future but for now are as open as git"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_paths_refer_to_the_root_tree() {
|
||||
let rec = parse("@:");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.peel_to, vec![PeelTo::Path("".into())]);
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paths_have_to_be_last_but_stack_with_other_navigation() {
|
||||
let rec = parse("HEAD@{1}~10^2^{commit}:README.md");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.current_branch_reflog_entry[0], Some("1".to_string()));
|
||||
assert_eq!(rec.traversal, vec![Traversal::NthAncestor(10), Traversal::NthParent(2)]);
|
||||
assert_eq!(
|
||||
rec.peel_to,
|
||||
vec![
|
||||
PeelTo::ObjectKind(gix_object::Kind::Commit),
|
||||
PeelTo::Path("README.md".into())
|
||||
]
|
||||
);
|
||||
assert_eq!(rec.calls, 6);
|
||||
}
|
||||
3
src-revision/tests/revision/spec/parse/navigate/mod.rs
Normal file
3
src-revision/tests/revision/spec/parse/navigate/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod caret_symbol;
|
||||
mod colon_symbol;
|
||||
mod tilde_symbol;
|
||||
@@ -0,0 +1,49 @@
|
||||
use gix_revision::spec::parse::delegate::Traversal;
|
||||
|
||||
use crate::spec::parse::{parse, try_parse};
|
||||
|
||||
#[test]
|
||||
fn without_anchor_is_invalid() {
|
||||
let err = try_parse("~").unwrap_err().into_inner();
|
||||
assert!(err.message.contains("tilde needs to follow an anchor"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_is_first_ancestor() {
|
||||
let rec = parse("@~");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.traversal[0], Traversal::NthAncestor(1));
|
||||
assert_eq!(rec.calls, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn followed_by_zero_is_no_op() {
|
||||
let rec = parse("@~0");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(rec.calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_calls_stack() {
|
||||
let rec = parse("@~~~10~0~020");
|
||||
|
||||
assert!(rec.kind.is_none());
|
||||
assert_eq!(rec.get_ref(0), "HEAD");
|
||||
assert_eq!(rec.prefix[0], None);
|
||||
assert_eq!(
|
||||
rec.traversal,
|
||||
vec![
|
||||
Traversal::NthAncestor(1),
|
||||
Traversal::NthAncestor(1),
|
||||
Traversal::NthAncestor(10),
|
||||
Traversal::NthAncestor(20),
|
||||
]
|
||||
);
|
||||
assert_eq!(rec.calls, 5);
|
||||
}
|
||||
Reference in New Issue
Block a user