mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-25 05:29:39 +00:00
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:
parent
f9b3211d58
commit
5f3df4aaea
8 changed files with 271 additions and 232 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
@ -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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()]
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue