revset: resolve "@" symbol's workspace id earlier (while parsing)

We resolve file paths into repo-relative paths while parsing the
revset expression, so I think it's consistent to also resolve which
workspace "@" refers to while parsing it. That means we won't need the
workspace context both while parsing and while resolving symbols.

In order to break things like `author("martinvonz@")` (thanks to @yuja
for catching this), I also changed the parsing of working-copy
expressions so they are not allowed to be
quoted. `author(martinvonz@)` will therefore be an error now. That
seems like a small improvement anyway, since we have recently talked
about making `root` and `[workspace]@` not parsed as other symbols.
This commit is contained in:
Martin von Zweigbergk 2023-04-09 16:18:32 -07:00 committed by Martin von Zweigbergk
parent f9b3211d58
commit 5f3df4aaea
8 changed files with 271 additions and 232 deletions

View file

@ -39,6 +39,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Older binaries may not warn user when attempting to `git push` commits
with such signatures.
* In revsets, the working-copy symbols (such as `@` and `workspace_id@`) can
no longer be quoted and will not be resolved as a revset aliases or revset
function parameter. For example, `author(foo@)` is now an error, and
a revset alias `'revset-aliases.foo@' = '@'` will be ignored.
### New features

View file

@ -328,6 +328,7 @@ impl From<RevsetResolutionError> for CommandError {
candidates,
} => format_similarity_hint(candidates),
RevsetResolutionError::EmptyString
| RevsetResolutionError::WorkspaceMissingWorkingCopy { .. }
| RevsetResolutionError::AmbiguousCommitIdPrefix(_)
| RevsetResolutionError::AmbiguousChangeIdPrefix(_)
| RevsetResolutionError::StoreError(_) => None,
@ -1142,7 +1143,7 @@ impl WorkspaceCommandHelper {
Box::new(|repo, prefix| id_prefix_context.resolve_commit_prefix(repo, prefix));
let change_id_resolver: revset::PrefixResolver<Vec<CommitId>> =
Box::new(|repo, prefix| id_prefix_context.resolve_change_prefix(repo, prefix));
DefaultSymbolResolver::new(self.repo().as_ref(), Some(self.workspace_id()))
DefaultSymbolResolver::new(self.repo().as_ref())
.with_commit_id_resolver(commit_id_resolver)
.with_change_id_resolver(change_id_resolver)
}
@ -1157,8 +1158,7 @@ impl WorkspaceCommandHelper {
.unwrap_or_else(|_| self.settings.default_revset());
if !revset_string.is_empty() {
let disambiguation_revset = self.parse_revset(&revset_string, None).unwrap();
context = context
.disambiguate_within(disambiguation_revset, Some(self.workspace_id().clone()));
context = context.disambiguate_within(disambiguation_revset);
}
context
})

View file

@ -1728,7 +1728,7 @@ fn cmd_log(ui: &mut Ui, command: &CommandHelper, args: &LogArgs) -> Result<(), C
only_path,
&RevsetAliasesMap::new(),
&command.settings().user_email(),
None,
Some(&workspace_command.revset_context()),
)
.is_ok()
{
@ -2523,7 +2523,7 @@ from the source will be moved into the parent.
only_path,
&RevsetAliasesMap::new(),
&command.settings().user_email(),
None,
Some(&tx.base_workspace_helper().revset_context()),
)
.is_ok()
{

View file

@ -323,7 +323,7 @@ fn test_workspaces_forget() {
// Revision "@" cannot be used
let stderr = test_env.jj_cmd_failure(&main_path, &["log", "-r", "@"]);
insta::assert_snapshot!(stderr, @r###"
Error: Revision "@" doesn't exist
Error: Workspace "default" doesn't have a working copy
"###);
// Try to add back the workspace

View file

@ -23,7 +23,6 @@ use once_cell::unsync::OnceCell;
use crate::backend::{self, ChangeId, CommitId, ObjectId};
use crate::index::{HexPrefix, PrefixResolution};
use crate::op_store::WorkspaceId;
use crate::repo::Repo;
use crate::revset::{DefaultSymbolResolver, RevsetExpression};
@ -31,7 +30,6 @@ struct PrefixDisambiguationError;
struct DisambiguationData {
expression: Rc<RevsetExpression>,
workspace_id: Option<WorkspaceId>,
indexes: OnceCell<Indexes>,
}
@ -44,7 +42,7 @@ struct Indexes {
impl DisambiguationData {
fn indexes(&self, repo: &dyn Repo) -> Result<&Indexes, PrefixDisambiguationError> {
self.indexes.get_or_try_init(|| {
let symbol_resolver = DefaultSymbolResolver::new(repo, self.workspace_id.as_ref());
let symbol_resolver = DefaultSymbolResolver::new(repo);
let resolved_expression = self
.expression
.clone()
@ -99,13 +97,8 @@ pub struct IdPrefixContext {
}
impl IdPrefixContext {
pub fn disambiguate_within(
mut self,
expression: Rc<RevsetExpression>,
workspace_id: Option<WorkspaceId>,
) -> Self {
pub fn disambiguate_within(mut self, expression: Rc<RevsetExpression>) -> Self {
self.disambiguation = Some(DisambiguationData {
workspace_id,
expression,
indexes: OnceCell::new(),
});

View file

@ -51,6 +51,8 @@ pub enum RevsetResolutionError {
name: String,
candidates: Vec<String>,
},
#[error("Workspace \"{name}\" doesn't have a working copy")]
WorkspaceMissingWorkingCopy { name: String },
#[error("An empty string is not a valid revision")]
EmptyString,
#[error("Commit ID prefix \"{0}\" is ambiguous")]
@ -108,6 +110,8 @@ pub enum RevsetParseErrorKind {
FsPathParseError(#[source] FsPathParseError),
#[error("Cannot resolve file pattern without workspace")]
FsPathWithoutWorkspace,
#[error(r#"Cannot resolve "@" without workspace"#)]
WorkingCopyWithoutWorkspace,
#[error("Redefinition of function parameter")]
RedefinedFunctionParameter,
#[error(r#"Alias "{0}" cannot be expanded"#)]
@ -245,6 +249,7 @@ impl StringPattern {
/// Symbol or function to be resolved to `CommitId`s.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RevsetCommitRef {
WorkingCopy(WorkspaceId),
Symbol(String),
VisibleHeads,
Branches(StringPattern),
@ -327,6 +332,12 @@ impl RevsetExpression {
Rc::new(RevsetExpression::All)
}
pub fn working_copy(workspace_id: WorkspaceId) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::WorkingCopy(
workspace_id,
)))
}
pub fn symbol(value: String) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::Symbol(value)))
}
@ -883,7 +894,20 @@ fn parse_symbol_rule(
match first.as_rule() {
Rule::identifier => {
let name = first.as_str();
if let Some(expr) = state.locals.get(name) {
if name.ends_with('@') {
let workspace_id = if name == "@" {
if let Some(ctx) = state.workspace_ctx {
ctx.workspace_id.clone()
} else {
return Err(RevsetParseError::new(
RevsetParseErrorKind::WorkingCopyWithoutWorkspace,
));
}
} else {
WorkspaceId::new(name.strip_suffix('@').unwrap().to_string())
};
Ok(RevsetExpression::working_copy(workspace_id))
} else if let Some(expr) = state.locals.get(name) {
Ok(expr.clone())
} else if let Some((id, defn)) = state.aliases_map.get_symbol(name) {
let locals = HashMap::new(); // Don't spill out the current scope
@ -1903,20 +1927,18 @@ impl SymbolResolver for FailingSymbolResolver {
pub type PrefixResolver<'a, T> = Box<dyn Fn(&dyn Repo, &HexPrefix) -> PrefixResolution<T> + 'a>;
/// Resolves the "root" and "@" symbols, branches, remote branches, tags, git
/// Resolves the "root" symbol, branches, remote branches, tags, git
/// refs, and full and abbreviated commit and change ids.
pub struct DefaultSymbolResolver<'a> {
repo: &'a dyn Repo,
workspace_id: Option<&'a WorkspaceId>,
commit_id_resolver: PrefixResolver<'a, CommitId>,
change_id_resolver: PrefixResolver<'a, Vec<CommitId>>,
}
impl<'a> DefaultSymbolResolver<'a> {
pub fn new(repo: &'a dyn Repo, workspace_id: Option<&'a WorkspaceId>) -> Self {
pub fn new(repo: &'a dyn Repo) -> Self {
DefaultSymbolResolver {
repo,
workspace_id,
commit_id_resolver: Box::new(|repo, prefix| repo.index().resolve_prefix(prefix)),
change_id_resolver: Box::new(|repo, prefix| repo.resolve_change_id_prefix(prefix)),
}
@ -1941,28 +1963,7 @@ impl<'a> DefaultSymbolResolver<'a> {
impl SymbolResolver for DefaultSymbolResolver<'_> {
fn resolve_symbol(&self, symbol: &str) -> Result<Vec<CommitId>, RevsetResolutionError> {
if symbol.ends_with('@') {
let target_workspace = if symbol == "@" {
if let Some(workspace_id) = self.workspace_id {
workspace_id.clone()
} else {
return Err(RevsetResolutionError::NoSuchRevision {
name: symbol.to_owned(),
candidates: Default::default(),
});
}
} else {
WorkspaceId::new(symbol.strip_suffix('@').unwrap().to_string())
};
if let Some(commit_id) = self.repo.view().get_wc_commit_id(&target_workspace) {
Ok(vec![commit_id.clone()])
} else {
Err(RevsetResolutionError::NoSuchRevision {
name: symbol.to_owned(),
candidates: Default::default(),
})
}
} else if symbol == "root" {
if symbol == "root" {
Ok(vec![self.repo.store().root_commit_id().clone()])
} else if symbol.is_empty() {
Err(RevsetResolutionError::EmptyString)
@ -2042,6 +2043,15 @@ fn resolve_commit_ref(
) -> Result<Vec<CommitId>, RevsetResolutionError> {
match commit_ref {
RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(symbol),
RevsetCommitRef::WorkingCopy(workspace_id) => {
if let Some(commit_id) = repo.view().get_wc_commit_id(workspace_id) {
Ok(vec![commit_id.clone()])
} else {
Err(RevsetResolutionError::WorkspaceMissingWorkingCopy {
name: workspace_id.as_str().to_string(),
})
}
}
RevsetCommitRef::VisibleHeads => Ok(repo.view().heads().iter().cloned().collect_vec()),
RevsetCommitRef::Branches(pattern) => {
let view = repo.view();
@ -2098,7 +2108,8 @@ fn resolve_symbols(
RevsetResolutionError::NoSuchRevision { .. } => {
Ok(RevsetExpression::none())
}
RevsetResolutionError::EmptyString
RevsetResolutionError::WorkspaceMissingWorkingCopy { .. }
| RevsetResolutionError::EmptyString
| RevsetResolutionError::AmbiguousCommitIdPrefix(_)
| RevsetResolutionError::AmbiguousChangeIdPrefix(_)
| RevsetResolutionError::StoreError(_) => Err(err),
@ -2379,7 +2390,7 @@ impl Iterator for ReverseRevsetIterator {
}
}
/// Workspace information needed to evaluate revset expression.
/// Workspace information needed to parse revset expression.
#[derive(Clone, Debug)]
pub struct RevsetWorkspaceContext<'a> {
pub cwd: &'a Path,
@ -2395,6 +2406,13 @@ mod tests {
parse_with_aliases(revset_str, [] as [(&str, &str); 0])
}
fn parse_with_workspace(
revset_str: &str,
workspace_id: &WorkspaceId,
) -> Result<Rc<RevsetExpression>, RevsetParseErrorKind> {
parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_id)
}
fn parse_with_aliases(
revset_str: &str,
aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
@ -2403,12 +2421,25 @@ mod tests {
for (decl, defn) in aliases {
aliases_map.insert(decl, defn).unwrap();
}
// Set up pseudo context to resolve file(path)
// Map error to comparable object
super::parse(revset_str, &aliases_map, "test.user@example.com", None).map_err(|e| e.kind)
}
fn parse_with_aliases_and_workspace(
revset_str: &str,
aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
workspace_id: &WorkspaceId,
) -> Result<Rc<RevsetExpression>, RevsetParseErrorKind> {
// Set up pseudo context to resolve `workspace_id@` and `file(path)`
let workspace_ctx = RevsetWorkspaceContext {
cwd: Path::new("/"),
workspace_id: &WorkspaceId::default(),
workspace_id,
workspace_root: Path::new("/"),
};
let mut aliases_map = RevsetAliasesMap::new();
for (decl, defn) in aliases {
aliases_map.insert(decl, defn).unwrap();
}
// Map error to comparable object
super::parse(
revset_str,
@ -2422,36 +2453,36 @@ mod tests {
#[test]
#[allow(clippy::redundant_clone)] // allow symbol.clone()
fn test_revset_expression_building() {
let wc_symbol = RevsetExpression::symbol("@".to_string());
let current_wc = RevsetExpression::working_copy(WorkspaceId::default());
let foo_symbol = RevsetExpression::symbol("foo".to_string());
let bar_symbol = RevsetExpression::symbol("bar".to_string());
let baz_symbol = RevsetExpression::symbol("baz".to_string());
assert_eq!(
wc_symbol,
Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::Symbol(
"@".to_string()
current_wc,
Rc::new(RevsetExpression::CommitRef(RevsetCommitRef::WorkingCopy(
WorkspaceId::default()
))),
);
assert_eq!(
wc_symbol.heads(),
Rc::new(RevsetExpression::Heads(wc_symbol.clone()))
current_wc.heads(),
Rc::new(RevsetExpression::Heads(current_wc.clone()))
);
assert_eq!(
wc_symbol.roots(),
Rc::new(RevsetExpression::Roots(wc_symbol.clone()))
current_wc.roots(),
Rc::new(RevsetExpression::Roots(current_wc.clone()))
);
assert_eq!(
wc_symbol.parents(),
current_wc.parents(),
Rc::new(RevsetExpression::Ancestors {
heads: wc_symbol.clone(),
heads: current_wc.clone(),
generation: 1..2,
is_legacy: false,
})
);
assert_eq!(
wc_symbol.ancestors(),
current_wc.ancestors(),
Rc::new(RevsetExpression::Ancestors {
heads: wc_symbol.clone(),
heads: current_wc.clone(),
generation: GENERATION_RANGE_FULL,
is_legacy: false,
})
@ -2473,10 +2504,10 @@ mod tests {
})
);
assert_eq!(
foo_symbol.dag_range_to(&wc_symbol),
foo_symbol.dag_range_to(&current_wc),
Rc::new(RevsetExpression::DagRange {
roots: foo_symbol.clone(),
heads: wc_symbol.clone(),
heads: current_wc.clone(),
is_legacy: false,
})
);
@ -2489,10 +2520,10 @@ mod tests {
})
);
assert_eq!(
foo_symbol.range(&wc_symbol),
foo_symbol.range(&current_wc),
Rc::new(RevsetExpression::Range {
roots: foo_symbol.clone(),
heads: wc_symbol.clone(),
heads: current_wc.clone(),
generation: GENERATION_RANGE_FULL,
})
);
@ -2501,32 +2532,35 @@ mod tests {
Rc::new(RevsetExpression::NotIn(foo_symbol.clone()))
);
assert_eq!(
foo_symbol.union(&wc_symbol),
foo_symbol.union(&current_wc),
Rc::new(RevsetExpression::Union(
foo_symbol.clone(),
wc_symbol.clone()
current_wc.clone()
))
);
assert_eq!(
RevsetExpression::union_all(&[]),
Rc::new(RevsetExpression::None)
);
assert_eq!(RevsetExpression::union_all(&[wc_symbol.clone()]), wc_symbol);
assert_eq!(
RevsetExpression::union_all(&[wc_symbol.clone(), foo_symbol.clone()]),
RevsetExpression::union_all(&[current_wc.clone()]),
current_wc
);
assert_eq!(
RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
Rc::new(RevsetExpression::Union(
wc_symbol.clone(),
current_wc.clone(),
foo_symbol.clone(),
))
);
assert_eq!(
RevsetExpression::union_all(&[
wc_symbol.clone(),
current_wc.clone(),
foo_symbol.clone(),
bar_symbol.clone(),
]),
Rc::new(RevsetExpression::Union(
wc_symbol.clone(),
current_wc.clone(),
Rc::new(RevsetExpression::Union(
foo_symbol.clone(),
bar_symbol.clone(),
@ -2535,14 +2569,14 @@ mod tests {
);
assert_eq!(
RevsetExpression::union_all(&[
wc_symbol.clone(),
current_wc.clone(),
foo_symbol.clone(),
bar_symbol.clone(),
baz_symbol.clone(),
]),
Rc::new(RevsetExpression::Union(
Rc::new(RevsetExpression::Union(
wc_symbol.clone(),
current_wc.clone(),
foo_symbol.clone(),
)),
Rc::new(RevsetExpression::Union(
@ -2552,25 +2586,58 @@ mod tests {
))
);
assert_eq!(
foo_symbol.intersection(&wc_symbol),
foo_symbol.intersection(&current_wc),
Rc::new(RevsetExpression::Intersection(
foo_symbol.clone(),
wc_symbol.clone()
current_wc.clone()
))
);
assert_eq!(
foo_symbol.minus(&wc_symbol),
Rc::new(RevsetExpression::Difference(foo_symbol, wc_symbol.clone()))
foo_symbol.minus(&current_wc),
Rc::new(RevsetExpression::Difference(foo_symbol, current_wc.clone()))
);
}
#[test]
fn test_parse_revset() {
let wc_symbol = RevsetExpression::symbol("@".to_string());
let main_workspace_id = WorkspaceId::new("main".to_string());
let other_workspace_id = WorkspaceId::new("other".to_string());
let main_wc = RevsetExpression::working_copy(main_workspace_id.clone());
let foo_symbol = RevsetExpression::symbol("foo".to_string());
let bar_symbol = RevsetExpression::symbol("bar".to_string());
// Parse a single symbol (specifically the "checkout" symbol)
assert_eq!(parse("@"), Ok(wc_symbol.clone()));
// Parse "@" (the current working copy)
assert_eq!(
parse("@"),
Err(RevsetParseErrorKind::WorkingCopyWithoutWorkspace)
);
assert_eq!(parse("main@"), Ok(main_wc.clone()));
assert_eq!(
parse_with_workspace("@", &main_workspace_id),
Ok(main_wc.clone())
);
assert_eq!(
parse_with_workspace("main@", &other_workspace_id),
Ok(main_wc)
);
// Quoted "@" is not interpreted as a working copy
assert_eq!(
parse(r#""@""#),
Ok(RevsetExpression::symbol("@".to_string()))
);
// "@" in function argument must be quoted
assert_eq!(
parse("author(foo@)"),
Err(RevsetParseErrorKind::InvalidFunctionArguments {
name: "author".to_string(),
message: "Expected function argument of string pattern".to_string(),
})
);
assert_eq!(
parse(r#"author("foo@")"#),
Ok(RevsetExpression::filter(RevsetFilterPredicate::Author(
StringPattern::Substring("foo@".to_string()),
)))
);
// Parse a single symbol
assert_eq!(parse("foo"), Ok(foo_symbol.clone()));
// Internal '.', '-', and '+' are allowed
@ -2601,20 +2668,20 @@ mod tests {
// Parse a quoted symbol
assert_eq!(parse("\"foo\""), Ok(foo_symbol.clone()));
// Parse the "parents" operator
assert_eq!(parse("@-"), Ok(wc_symbol.parents()));
assert_eq!(parse("foo-"), Ok(foo_symbol.parents()));
// Parse the "children" operator
assert_eq!(parse("@+"), Ok(wc_symbol.children()));
assert_eq!(parse("foo+"), Ok(foo_symbol.children()));
// Parse the "ancestors" operator
assert_eq!(parse("::@"), Ok(wc_symbol.ancestors()));
assert_eq!(parse("::foo"), Ok(foo_symbol.ancestors()));
// Parse the "descendants" operator
assert_eq!(parse("@::"), Ok(wc_symbol.descendants()));
assert_eq!(parse("foo::"), Ok(foo_symbol.descendants()));
// Parse the "dag range" operator
assert_eq!(parse("foo::bar"), Ok(foo_symbol.dag_range_to(&bar_symbol)));
// Parse the "range" prefix operator
assert_eq!(parse("..@"), Ok(wc_symbol.ancestors()));
assert_eq!(parse("..foo"), Ok(foo_symbol.ancestors()));
assert_eq!(
parse("@.."),
Ok(wc_symbol.range(&RevsetExpression::visible_heads()))
parse("foo.."),
Ok(foo_symbol.range(&RevsetExpression::visible_heads()))
);
assert_eq!(parse("foo..bar"), Ok(foo_symbol.range(&bar_symbol)));
// Parse the "negate" operator
@ -2630,17 +2697,20 @@ mod tests {
// Parse the "difference" operator
assert_eq!(parse("foo ~ bar"), Ok(foo_symbol.minus(&bar_symbol)));
// Parentheses are allowed before suffix operators
assert_eq!(parse("(@)-"), Ok(wc_symbol.parents()));
assert_eq!(parse("(foo)-"), Ok(foo_symbol.parents()));
// Space is allowed around expressions
assert_eq!(parse(" ::@ "), Ok(wc_symbol.ancestors()));
assert_eq!(parse("( ::@ )"), Ok(wc_symbol.ancestors()));
assert_eq!(parse(" ::foo "), Ok(foo_symbol.ancestors()));
assert_eq!(parse("( ::foo )"), Ok(foo_symbol.ancestors()));
// Space is not allowed around prefix operators
assert_eq!(parse(" :: @ "), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse(" :: foo "), Err(RevsetParseErrorKind::SyntaxError));
// Incomplete parse
assert_eq!(parse("foo | -"), Err(RevsetParseErrorKind::SyntaxError));
// Space is allowed around infix operators and function arguments
assert_eq!(
parse(" description( arg1 ) ~ file( arg1 , arg2 ) ~ visible_heads( ) "),
parse_with_workspace(
" description( arg1 ) ~ file( arg1 , arg2 ) ~ visible_heads( ) ",
&main_workspace_id
),
Ok(RevsetExpression::filter(RevsetFilterPredicate::Description(
StringPattern::Substring("arg1".to_string())
))
@ -2666,8 +2736,8 @@ mod tests {
assert!(parse("branches(,a)").is_err());
assert!(parse("branches(a,,)").is_err());
assert!(parse("branches(a , , )").is_err());
assert!(parse("file(a,b,)").is_ok());
assert!(parse("file(a,,b)").is_err());
assert!(parse_with_workspace("file(a,b,)", &main_workspace_id).is_ok());
assert!(parse_with_workspace("file(a,,b)", &main_workspace_id).is_err());
assert!(parse("remote_branches(a,remote=b , )").is_ok());
assert!(parse("remote_branches(a,,remote=b)").is_err());
}
@ -2815,17 +2885,17 @@ mod tests {
#[test]
fn test_parse_revset_function() {
let wc_symbol = RevsetExpression::symbol("@".to_string());
assert_eq!(parse("parents(@)"), Ok(wc_symbol.parents()));
assert_eq!(parse("parents((@))"), Ok(wc_symbol.parents()));
assert_eq!(parse("parents(\"@\")"), Ok(wc_symbol.parents()));
let foo_symbol = RevsetExpression::symbol("foo".to_string());
assert_eq!(parse("parents(foo)"), Ok(foo_symbol.parents()));
assert_eq!(parse("parents((foo))"), Ok(foo_symbol.parents()));
assert_eq!(parse("parents(\"foo\")"), Ok(foo_symbol.parents()));
assert_eq!(
parse("ancestors(parents(@))"),
Ok(wc_symbol.parents().ancestors())
parse("ancestors(parents(foo))"),
Ok(foo_symbol.parents().ancestors())
);
assert_eq!(parse("parents(@"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("parents(foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(
parse("parents(@,@)"),
parse("parents(foo,foo)"),
Err(RevsetParseErrorKind::InvalidFunctionArguments {
name: "parents".to_string(),
message: "Expected 1 arguments".to_string()
@ -2870,19 +2940,19 @@ mod tests {
)))
);
assert_eq!(
parse("empty()"),
parse_with_workspace("empty()", &WorkspaceId::default()),
Ok(RevsetExpression::filter(RevsetFilterPredicate::File(None)).negated())
);
assert!(parse("empty(foo)").is_err());
assert!(parse("file()").is_err());
assert!(parse_with_workspace("empty(foo)", &WorkspaceId::default()).is_err());
assert!(parse_with_workspace("file()", &WorkspaceId::default()).is_err());
assert_eq!(
parse("file(foo)"),
parse_with_workspace("file(foo)", &WorkspaceId::default()),
Ok(RevsetExpression::filter(RevsetFilterPredicate::File(Some(
vec![RepoPath::from_internal_string("foo")]
))))
);
assert_eq!(
parse("file(foo, bar, baz)"),
parse_with_workspace("file(foo, bar, baz)", &WorkspaceId::default()),
Ok(RevsetExpression::filter(RevsetFilterPredicate::File(Some(
vec![
RepoPath::from_internal_string("foo"),
@ -2959,11 +3029,18 @@ mod tests {
parse_with_aliases(r#"A|"A""#, [("A", "a")]).unwrap(),
parse("a|A").unwrap()
);
// Working copy symbol should not be substituted with alias.
// TODO: Make it an error instead of being ignored
assert_eq!(
parse_with_aliases(r#"a@"#, [("a@", "a")]).unwrap(),
parse("a@").unwrap()
);
// Alias can be substituted to string literal.
assert_eq!(
parse_with_aliases("file(A)", [("A", "a")]).unwrap(),
parse("file(a)").unwrap()
parse_with_aliases_and_workspace("file(A)", [("A", "a")], &WorkspaceId::default())
.unwrap(),
parse_with_workspace("file(a)", &WorkspaceId::default()).unwrap()
);
// Alias can be substituted to string pattern.
@ -3034,6 +3111,13 @@ mod tests {
parse("(x|(x|a))&y").unwrap()
);
// Working copy symbol cannot be used as parameter name
// TODO: Make it an error instead of being ignored
assert_eq!(
parse_with_aliases("F(x)", [("F(a@)", "a@|y")]).unwrap(),
parse("a@|y").unwrap()
);
// Function parameter should precede the symbol alias.
assert_eq!(
parse_with_aliases("F(a)|X", [("F(X)", "X"), ("X", "x")]).unwrap(),
@ -3690,7 +3774,7 @@ mod tests {
)
"###);
insta::assert_debug_snapshot!(
optimize(parse("committer(foo) & file(bar) & baz").unwrap()), @r###"
optimize(parse_with_workspace("committer(foo) & file(bar) & baz", &WorkspaceId::default()).unwrap()), @r###"
Intersection(
Intersection(
CommitRef(
@ -3718,7 +3802,7 @@ mod tests {
)
"###);
insta::assert_debug_snapshot!(
optimize(parse("committer(foo) & file(bar) & author(baz)").unwrap()), @r###"
optimize(parse_with_workspace("committer(foo) & file(bar) & author(baz)", &WorkspaceId::default()).unwrap()), @r###"
Intersection(
Intersection(
Filter(
@ -3747,7 +3831,7 @@ mod tests {
),
)
"###);
insta::assert_debug_snapshot!(optimize(parse("foo & file(bar) & baz").unwrap()), @r###"
insta::assert_debug_snapshot!(optimize(parse_with_workspace("foo & file(bar) & baz", &WorkspaceId::default()).unwrap()), @r###"
Intersection(
Intersection(
CommitRef(

View file

@ -184,7 +184,7 @@ fn test_id_prefix() {
// ---------------------------------------------------------------------------------------------
let expression =
RevsetExpression::commits(vec![commits[0].id().clone(), commits[2].id().clone()]);
let c = c.disambiguate_within(expression, None);
let c = c.disambiguate_within(expression);
// The prefix is now shorter
assert_eq!(
c.shortest_commit_prefix_len(repo.as_ref(), commits[2].id()),
@ -213,7 +213,7 @@ fn test_id_prefix() {
// needed.
// ---------------------------------------------------------------------------------------------
let expression = RevsetExpression::commit(root_commit_id.clone());
let c = c.disambiguate_within(expression, None);
let c = c.disambiguate_within(expression);
assert_eq!(
c.shortest_commit_prefix_len(repo.as_ref(), root_commit_id),
1
@ -243,7 +243,7 @@ fn test_id_prefix() {
// ---------------------------------------------------------------------------------------------
// TODO: Should be an error
let expression = RevsetExpression::symbol("nonexistent".to_string());
let context = c.disambiguate_within(expression, None);
let context = c.disambiguate_within(expression);
assert_eq!(
context.shortest_commit_prefix_len(repo.as_ref(), commits[2].id()),
2

View file

@ -42,19 +42,15 @@ use testutils::{
create_random_commit, write_random_commit, CommitGraphBuilder, TestRepo, TestWorkspace,
};
fn resolve_symbol(
repo: &dyn Repo,
symbol: &str,
workspace_id: Option<&WorkspaceId>,
) -> Result<Vec<CommitId>, RevsetResolutionError> {
DefaultSymbolResolver::new(repo, workspace_id).resolve_symbol(symbol)
fn resolve_symbol(repo: &dyn Repo, symbol: &str) -> Result<Vec<CommitId>, RevsetResolutionError> {
DefaultSymbolResolver::new(repo).resolve_symbol(symbol)
}
fn revset_for_commits<'index>(
repo: &'index dyn Repo,
commits: &[&Commit],
) -> Box<dyn Revset<'index> + 'index> {
let symbol_resolver = DefaultSymbolResolver::new(repo, None);
let symbol_resolver = DefaultSymbolResolver::new(repo);
RevsetExpression::commits(commits.iter().map(|commit| commit.id().clone()).collect())
.resolve_user_expression(repo, &symbol_resolver)
.unwrap()
@ -69,7 +65,7 @@ fn test_resolve_symbol_root(use_git: bool) {
let repo = &test_repo.repo;
assert_matches!(
resolve_symbol(repo.as_ref(), "root", None),
resolve_symbol(repo.as_ref(), "root"),
Ok(v) if v == vec![repo.store().root_commit_id().clone()]
);
}
@ -80,7 +76,7 @@ fn test_resolve_symbol_empty_string() {
let repo = &test_repo.repo;
assert_matches!(
resolve_symbol(repo.as_ref(), "", None),
resolve_symbol(repo.as_ref(), ""),
Err(RevsetResolutionError::EmptyString)
);
}
@ -141,56 +137,41 @@ fn test_resolve_symbol_commit_id() {
// Test lookup by full commit id
assert_eq!(
resolve_symbol(
repo.as_ref(),
"0454de3cae04c46cda37ba2e8873b4c17ff51dcb",
None
)
.unwrap(),
resolve_symbol(repo.as_ref(), "0454de3cae04c46cda37ba2e8873b4c17ff51dcb",).unwrap(),
vec![commits[0].id().clone()]
);
assert_eq!(
resolve_symbol(
repo.as_ref(),
"045f56cd1b17e8abde86771e2705395dcde6a957",
None
)
.unwrap(),
resolve_symbol(repo.as_ref(), "045f56cd1b17e8abde86771e2705395dcde6a957",).unwrap(),
vec![commits[1].id().clone()]
);
assert_eq!(
resolve_symbol(
repo.as_ref(),
"0468f7da8de2ce442f512aacf83411d26cd2e0cf",
None
)
.unwrap(),
resolve_symbol(repo.as_ref(), "0468f7da8de2ce442f512aacf83411d26cd2e0cf",).unwrap(),
vec![commits[2].id().clone()]
);
// Test commit id prefix
assert_eq!(
resolve_symbol(repo.as_ref(), "046", None).unwrap(),
resolve_symbol(repo.as_ref(), "046").unwrap(),
vec![commits[2].id().clone()]
);
assert_matches!(
resolve_symbol(repo.as_ref(), "04", None),
resolve_symbol(repo.as_ref(), "04"),
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "04"
);
assert_matches!(
resolve_symbol(repo.as_ref(), "040", None),
resolve_symbol(repo.as_ref(), "040"),
Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "040" && candidates.is_empty()
);
// Test non-hex string
assert_matches!(
resolve_symbol(repo.as_ref(), "foo", None),
resolve_symbol(repo.as_ref(), "foo"),
Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "foo" && candidates.is_empty()
);
// Test present() suppresses only NoSuchRevision error
assert_eq!(resolve_commit_ids(repo.as_ref(), "present(foo)"), []);
let symbol_resolver = DefaultSymbolResolver::new(repo.as_ref(), None);
let symbol_resolver = DefaultSymbolResolver::new(repo.as_ref());
assert_matches!(
optimize(parse("present(04)", &RevsetAliasesMap::new(), &settings.user_email(), None).unwrap()).resolve_user_expression(repo.as_ref(), &symbol_resolver),
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "04"
@ -281,19 +262,19 @@ fn test_resolve_symbol_change_id(readonly: bool) {
// Test lookup by full change id
assert_eq!(
resolve_symbol(repo, "zvlyxpuvtsoopsqzlkorrpqrszrqvlnx", None).unwrap(),
resolve_symbol(repo, "zvlyxpuvtsoopsqzlkorrpqrszrqvlnx").unwrap(),
vec![CommitId::from_hex(
"8fd68d104372910e19511df709e5dde62a548720"
)]
);
assert_eq!(
resolve_symbol(repo, "zvzowopwpuymrlmonvnuruunomzqmlsy", None).unwrap(),
resolve_symbol(repo, "zvzowopwpuymrlmonvnuruunomzqmlsy").unwrap(),
vec![CommitId::from_hex(
"5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"
)]
);
assert_eq!(
resolve_symbol(repo, "zvlynszrxlvlwvkwkwsymrpypvtsszor", None).unwrap(),
resolve_symbol(repo, "zvlynszrxlvlwvkwkwsymrpypvtsszor").unwrap(),
vec![CommitId::from_hex(
"e2ad9d861d0ee625851b8ecfcf2c727410e38720"
)]
@ -301,36 +282,36 @@ fn test_resolve_symbol_change_id(readonly: bool) {
// Test change id prefix
assert_eq!(
resolve_symbol(repo, "zvlyx", None).unwrap(),
resolve_symbol(repo, "zvlyx").unwrap(),
vec![CommitId::from_hex(
"8fd68d104372910e19511df709e5dde62a548720"
)]
);
assert_eq!(
resolve_symbol(repo, "zvlyn", None).unwrap(),
resolve_symbol(repo, "zvlyn").unwrap(),
vec![CommitId::from_hex(
"e2ad9d861d0ee625851b8ecfcf2c727410e38720"
)]
);
assert_matches!(
resolve_symbol(repo, "zvly", None),
resolve_symbol(repo, "zvly"),
Err(RevsetResolutionError::AmbiguousChangeIdPrefix(s)) if s == "zvly"
);
assert_matches!(
resolve_symbol(repo, "zvlyw", None),
resolve_symbol(repo, "zvlyw"),
Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "zvlyw" && candidates.is_empty()
);
// Test that commit and changed id don't conflict ("040" and "zvz" are the
// same).
assert_eq!(
resolve_symbol(repo, "040", None).unwrap(),
resolve_symbol(repo, "040").unwrap(),
vec![CommitId::from_hex(
"040031cb4ad0cbc3287914f1d205dabf4a7eb889"
)]
);
assert_eq!(
resolve_symbol(repo, "zvz", None).unwrap(),
resolve_symbol(repo, "zvz").unwrap(),
vec![CommitId::from_hex(
"5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"
)]
@ -338,7 +319,7 @@ fn test_resolve_symbol_change_id(readonly: bool) {
// Test non-hex string
assert_matches!(
resolve_symbol(repo, "foo", None),
resolve_symbol(repo, "foo"),
Err(RevsetResolutionError::NoSuchRevision{
name,
candidates
@ -348,7 +329,7 @@ fn test_resolve_symbol_change_id(readonly: bool) {
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_resolve_symbol_checkout(use_git: bool) {
fn test_resolve_working_copy(use_git: bool) {
let settings = testutils::user_settings();
let test_repo = TestRepo::init(use_git);
let repo = &test_repo.repo;
@ -362,52 +343,33 @@ fn test_resolve_symbol_checkout(use_git: bool) {
let ws1 = WorkspaceId::new("ws1".to_string());
let ws2 = WorkspaceId::new("ws2".to_string());
// With no workspaces, no variation can be resolved
// Cannot resolve a working-copy commit for an unknown workspace
assert_matches!(
resolve_symbol(mut_repo, "@", None),
Err(RevsetResolutionError::NoSuchRevision{
name,
candidates,
}) if name == "@" && candidates.is_empty()
);
assert_matches!(
resolve_symbol(mut_repo, "@", Some(&ws1)),
Err(RevsetResolutionError::NoSuchRevision{
name,
candidates,
}) if name == "@" && candidates.is_empty()
);
assert_matches!(
resolve_symbol(mut_repo, "ws1@", Some(&ws1)),
Err(RevsetResolutionError::NoSuchRevision{
name,
candidates,
}) if name == "ws1@" && candidates.is_empty()
RevsetExpression::working_copy(ws1.clone()).resolve(mut_repo),
Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name }) if name == "ws1"
);
// Add some workspaces
mut_repo
.set_wc_commit(ws1.clone(), commit1.id().clone())
.unwrap();
mut_repo.set_wc_commit(ws2, commit2.id().clone()).unwrap();
// @ cannot be resolved without a default workspace ID
assert_matches!(
resolve_symbol(mut_repo, "@", None),
Err(RevsetResolutionError::NoSuchRevision{
name,
candidates,
}) if name == "@" && candidates.is_empty()
);
mut_repo
.set_wc_commit(ws2.clone(), commit2.id().clone())
.unwrap();
let resolve = |ws_id: WorkspaceId| -> Vec<CommitId> {
RevsetExpression::working_copy(ws_id)
.resolve(mut_repo)
.unwrap()
.evaluate(mut_repo)
.unwrap()
.iter()
.collect()
};
// Can resolve "@" shorthand with a default workspace ID
assert_eq!(
resolve_symbol(mut_repo, "@", Some(&ws1)).unwrap(),
vec![commit1.id().clone()]
);
assert_eq!(resolve(ws1), vec![commit1.id().clone()]);
// Can resolve an explicit checkout
assert_eq!(
resolve_symbol(mut_repo, "ws2@", Some(&ws1)).unwrap(),
vec![commit2.id().clone()]
);
assert_eq!(resolve(ws2), vec![commit2.id().clone()]);
}
#[test]
@ -461,11 +423,11 @@ fn test_resolve_symbol_branches() {
// Local only
assert_eq!(
resolve_symbol(mut_repo, "local", None).unwrap(),
resolve_symbol(mut_repo, "local").unwrap(),
vec![commit1.id().clone()],
);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local@origin", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "local@origin").unwrap_err(), @r###"
NoSuchRevision {
name: "local@origin",
candidates: [
@ -479,7 +441,7 @@ fn test_resolve_symbol_branches() {
// Remote only (or locally deleted)
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "remote", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "remote").unwrap_err(), @r###"
NoSuchRevision {
name: "remote",
candidates: [
@ -490,35 +452,35 @@ fn test_resolve_symbol_branches() {
}
"###);
assert_eq!(
resolve_symbol(mut_repo, "remote@origin", None).unwrap(),
resolve_symbol(mut_repo, "remote@origin").unwrap(),
vec![commit2.id().clone()],
);
// Local/remote/git
assert_eq!(
resolve_symbol(mut_repo, "local-remote", None).unwrap(),
resolve_symbol(mut_repo, "local-remote").unwrap(),
vec![commit3.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "local-remote@origin", None).unwrap(),
resolve_symbol(mut_repo, "local-remote@origin").unwrap(),
vec![commit4.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "local-remote@mirror", None).unwrap(),
resolve_symbol(mut_repo, "local-remote@mirror").unwrap(),
vec![commit3.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "local-remote@git", None).unwrap(),
resolve_symbol(mut_repo, "local-remote@git").unwrap(),
vec![commit3.id().clone()],
);
// Conflicted
assert_eq!(
resolve_symbol(mut_repo, "local-conflicted", None).unwrap(),
resolve_symbol(mut_repo, "local-conflicted").unwrap(),
vec![commit3.id().clone(), commit2.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "remote-conflicted@origin", None).unwrap(),
resolve_symbol(mut_repo, "remote-conflicted@origin").unwrap(),
vec![commit5.id().clone(), commit4.id().clone()],
);
@ -526,7 +488,7 @@ fn test_resolve_symbol_branches() {
// For "local-emote" (without @remote part), "local-remote@mirror"/"@git" aren't
// suggested since they point to the same target as "local-remote".
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local-emote", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "local-emote").unwrap_err(), @r###"
NoSuchRevision {
name: "local-emote",
candidates: [
@ -538,7 +500,7 @@ fn test_resolve_symbol_branches() {
}
"###);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local-emote@origin", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "local-emote@origin").unwrap_err(), @r###"
NoSuchRevision {
name: "local-emote@origin",
candidates: [
@ -553,7 +515,7 @@ fn test_resolve_symbol_branches() {
}
"###);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local-remote@origine", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "local-remote@origine").unwrap_err(), @r###"
NoSuchRevision {
name: "local-remote@origine",
candidates: [
@ -570,7 +532,7 @@ fn test_resolve_symbol_branches() {
// "local-remote@mirror" shouldn't be omitted just because it points to the same
// target as "local-remote".
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "remote@mirror", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "remote@mirror").unwrap_err(), @r###"
NoSuchRevision {
name: "remote@mirror",
candidates: [
@ -582,7 +544,7 @@ fn test_resolve_symbol_branches() {
// Typo of remote-only branch name
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "emote", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "emote").unwrap_err(), @r###"
NoSuchRevision {
name: "emote",
candidates: [
@ -592,7 +554,7 @@ fn test_resolve_symbol_branches() {
}
"###);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "emote@origin", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "emote@origin").unwrap_err(), @r###"
NoSuchRevision {
name: "emote@origin",
candidates: [
@ -602,7 +564,7 @@ fn test_resolve_symbol_branches() {
}
"###);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "remote@origine", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "remote@origine").unwrap_err(), @r###"
NoSuchRevision {
name: "remote@origine",
candidates: [
@ -627,14 +589,14 @@ fn test_resolve_symbol_git_head() {
// Without HEAD@git
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "HEAD", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "HEAD").unwrap_err(), @r###"
NoSuchRevision {
name: "HEAD",
candidates: [],
}
"###);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "HEAD@git", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "HEAD@git").unwrap_err(), @r###"
NoSuchRevision {
name: "HEAD@git",
candidates: [],
@ -644,7 +606,7 @@ fn test_resolve_symbol_git_head() {
// With HEAD@git
mut_repo.set_git_head_target(RefTarget::normal(commit1.id().clone()));
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "HEAD", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "HEAD").unwrap_err(), @r###"
NoSuchRevision {
name: "HEAD",
candidates: [
@ -653,7 +615,7 @@ fn test_resolve_symbol_git_head() {
}
"###);
assert_eq!(
resolve_symbol(mut_repo, "HEAD@git", None).unwrap(),
resolve_symbol(mut_repo, "HEAD@git").unwrap(),
vec![commit1.id().clone()],
);
}
@ -696,7 +658,7 @@ fn test_resolve_symbol_git_refs() {
// Nonexistent ref
assert_matches!(
resolve_symbol(mut_repo, "nonexistent", None),
resolve_symbol(mut_repo, "nonexistent"),
Err(RevsetResolutionError::NoSuchRevision{name, candidates})
if name == "nonexistent" && candidates.is_empty()
);
@ -704,7 +666,7 @@ fn test_resolve_symbol_git_refs() {
// Full ref
mut_repo.set_git_ref_target("refs/heads/branch", RefTarget::normal(commit4.id().clone()));
assert_eq!(
resolve_symbol(mut_repo, "refs/heads/branch", None).unwrap(),
resolve_symbol(mut_repo, "refs/heads/branch").unwrap(),
vec![commit4.id().clone()]
);
@ -712,7 +674,7 @@ fn test_resolve_symbol_git_refs() {
mut_repo.set_git_ref_target("refs/heads/branch", RefTarget::normal(commit5.id().clone()));
// branch alone is not recognized
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "branch", None).unwrap_err(), @r###"
resolve_symbol(mut_repo, "branch").unwrap_err(), @r###"
NoSuchRevision {
name: "branch",
candidates: [
@ -725,19 +687,19 @@ fn test_resolve_symbol_git_refs() {
mut_repo.set_git_ref_target("refs/tags/branch", RefTarget::normal(commit4.id().clone()));
// The *tag* branch is recognized
assert_eq!(
resolve_symbol(mut_repo, "branch", None).unwrap(),
resolve_symbol(mut_repo, "branch").unwrap(),
vec![commit4.id().clone()]
);
// heads/branch does get resolved to the git ref refs/heads/branch
assert_eq!(
resolve_symbol(mut_repo, "heads/branch", None).unwrap(),
resolve_symbol(mut_repo, "heads/branch").unwrap(),
vec![commit5.id().clone()]
);
// Unqualified tag name
mut_repo.set_git_ref_target("refs/tags/tag", RefTarget::normal(commit4.id().clone()));
assert_eq!(
resolve_symbol(mut_repo, "tag", None).unwrap(),
resolve_symbol(mut_repo, "tag").unwrap(),
vec![commit4.id().clone()]
);
@ -747,11 +709,11 @@ fn test_resolve_symbol_git_refs() {
RefTarget::normal(commit2.id().clone()),
);
assert_eq!(
resolve_symbol(mut_repo, "origin/remote-branch", None).unwrap(),
resolve_symbol(mut_repo, "origin/remote-branch").unwrap(),
vec![commit2.id().clone()]
);
// Cannot shadow checkout ("@") or root symbols
// Cannot shadow root symbols
let ws_id = WorkspaceId::default();
mut_repo
.set_wc_commit(ws_id.clone(), commit1.id().clone())
@ -759,17 +721,13 @@ fn test_resolve_symbol_git_refs() {
mut_repo.set_git_ref_target("@", RefTarget::normal(commit2.id().clone()));
mut_repo.set_git_ref_target("root", RefTarget::normal(commit3.id().clone()));
assert_eq!(
resolve_symbol(mut_repo, "@", Some(&ws_id)).unwrap(),
vec![mut_repo.view().get_wc_commit_id(&ws_id).unwrap().clone()]
);
assert_eq!(
resolve_symbol(mut_repo, "root", None).unwrap(),
resolve_symbol(mut_repo, "root").unwrap(),
vec![mut_repo.store().root_commit().id().clone()]
);
// Conflicted ref resolves to its "adds"
assert_eq!(
resolve_symbol(mut_repo, "refs/heads/conflicted", None).unwrap(),
resolve_symbol(mut_repo, "refs/heads/conflicted").unwrap(),
vec![commit1.id().clone(), commit3.id().clone()]
);
}
@ -785,7 +743,7 @@ fn resolve_commit_ids(repo: &dyn Repo, revset_str: &str) -> Vec<CommitId> {
)
.unwrap(),
);
let symbol_resolver = DefaultSymbolResolver::new(repo, None);
let symbol_resolver = DefaultSymbolResolver::new(repo);
let expression = expression
.resolve_user_expression(repo, &symbol_resolver)
.unwrap();
@ -813,7 +771,7 @@ fn resolve_commit_ids_in_workspace(
)
.unwrap(),
);
let symbol_resolver = DefaultSymbolResolver::new(repo, Some(workspace_ctx.workspace_id));
let symbol_resolver = DefaultSymbolResolver::new(repo);
let expression = expression
.resolve_user_expression(repo, &symbol_resolver)
.unwrap();
@ -2860,9 +2818,9 @@ fn test_no_such_revision_suggestion() {
);
}
assert_matches!(resolve_symbol(mut_repo, "bar", None), Ok(_));
assert_matches!(resolve_symbol(mut_repo, "bar"), Ok(_));
assert_matches!(
resolve_symbol(mut_repo, "bax", None),
resolve_symbol(mut_repo, "bax"),
Err(RevsetResolutionError::NoSuchRevision { name, candidates })
if name == "bax" && candidates == vec!["bar".to_string(), "baz".to_string()]
);