mirror of
https://github.com/jj-vcs/jj.git
synced 2026-07-05 15:45:23 +08:00
revset: parameterize default string pattern kind, add config knob
Glob patterns will be enabled by default globally. Since this will be a big breaking change in revsets, this patch adds a config knob to turn the new default on/off.
This commit is contained in:
@@ -18,6 +18,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
are in revsets. and can be combined with logical operators: `jj bookmark
|
||||
list`, `jj tag list`
|
||||
|
||||
* The default string pattern syntax in revsets will be changed to `glob:` in a
|
||||
future release. You can opt in to the new default by setting
|
||||
`ui.revsets-use-glob-by-default=true`.
|
||||
|
||||
* Upgraded `scm-record` from v0.8.0 to v0.9.0. See release notes at
|
||||
<https://github.com/arxanas/scm-record/releases/tag/v0.9.0>.
|
||||
|
||||
|
||||
@@ -786,6 +786,7 @@ pub struct WorkspaceCommandEnvironment {
|
||||
revset_aliases_map: RevsetAliasesMap,
|
||||
template_aliases_map: TemplateAliasesMap,
|
||||
default_ignored_remote: Option<&'static RemoteName>,
|
||||
revsets_use_glob_by_default: bool,
|
||||
path_converter: RepoPathUiConverter,
|
||||
workspace_name: WorkspaceNameBuf,
|
||||
immutable_heads_expression: Arc<UserRevsetExpression>,
|
||||
@@ -810,6 +811,7 @@ impl WorkspaceCommandEnvironment {
|
||||
revset_aliases_map,
|
||||
template_aliases_map,
|
||||
default_ignored_remote,
|
||||
revsets_use_glob_by_default: settings.get("ui.revsets-use-glob-by-default")?,
|
||||
path_converter,
|
||||
workspace_name: workspace.workspace_name().to_owned(),
|
||||
immutable_heads_expression: RevsetExpression::root(),
|
||||
@@ -847,6 +849,7 @@ impl WorkspaceCommandEnvironment {
|
||||
user_email: self.settings.user_email(),
|
||||
date_pattern_context: now.into(),
|
||||
default_ignored_remote: self.default_ignored_remote,
|
||||
use_glob_by_default: self.revsets_use_glob_by_default,
|
||||
extensions: self.command.revset_extensions(),
|
||||
workspace: Some(workspace_context),
|
||||
}
|
||||
|
||||
@@ -2789,6 +2789,7 @@ mod tests {
|
||||
user_email: "test.user@example.com",
|
||||
date_pattern_context: chrono::DateTime::UNIX_EPOCH.fixed_offset().into(),
|
||||
default_ignored_remote: None,
|
||||
use_glob_by_default: false,
|
||||
extensions: &self.revset_extensions,
|
||||
workspace: Some(RevsetWorkspaceContext {
|
||||
path_converter: &self.path_converter,
|
||||
|
||||
@@ -237,6 +237,11 @@
|
||||
"conflict-marker-style": {
|
||||
"$ref": "#/properties/ui/definitions/conflict-marker-style"
|
||||
},
|
||||
"revsets-use-glob-by-default": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to use glob string patterns in revsets by default"
|
||||
},
|
||||
"show-cryptographic-signatures": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -39,6 +39,9 @@ conflict-marker-style = "diff"
|
||||
# signature verification is slow, disable by default
|
||||
show-cryptographic-signatures = false
|
||||
bookmark-list-sort-keys = ["name"]
|
||||
# TODO: enable glob by default in jj 0.37+ or so, and deprecate this option
|
||||
# https://github.com/jj-vcs/jj/issues/6971#issuecomment-3067038313
|
||||
revsets-use-glob-by-default = false
|
||||
|
||||
[ui.movement]
|
||||
edit = false
|
||||
|
||||
@@ -511,6 +511,44 @@ fn test_bad_symbol_or_argument_should_not_be_optimized_out() {
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_string_pattern() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||||
let work_dir = test_env.work_dir("repo");
|
||||
|
||||
// substring match by default as of jj 0.35
|
||||
let output = work_dir.run_jj(["log", "-rauthor('test.user')"]);
|
||||
insta::assert_snapshot!(output.normalize_backslash(), @r"
|
||||
@ qpvuntsm test.user@example.com 2001-02-03 08:05:07 e8849ae1
|
||||
│ (empty) (no description set)
|
||||
~
|
||||
[EOF]
|
||||
------- stderr -------
|
||||
Warning: In revset expression
|
||||
--> 1:8
|
||||
|
|
||||
1 | author('test.user')
|
||||
| ^---------^
|
||||
|
|
||||
= Default pattern syntax will be changed to `glob:` in a future release; use `substring:` prefix or set ui.revsets-use-glob-by-default=true to suppress this warning
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// with default flipped
|
||||
let output = work_dir.run_jj([
|
||||
"log",
|
||||
"-rauthor('*test.user*')",
|
||||
"--config=ui.revsets-use-glob-by-default=true",
|
||||
]);
|
||||
insta::assert_snapshot!(output.normalize_backslash(), @r"
|
||||
@ qpvuntsm test.user@example.com 2001-02-03 08:05:07 e8849ae1
|
||||
│ (empty) (no description set)
|
||||
~
|
||||
[EOF]
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alias() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
||||
@@ -476,14 +476,18 @@ revsets (expressions) as arguments.
|
||||
## String patterns
|
||||
|
||||
Functions that perform string matching support the following pattern syntax (the
|
||||
quotes are optional):
|
||||
quotes are optional).
|
||||
|
||||
By default, `"string"` is parsed as a `substring:` pattern in revsets. The
|
||||
default will be changed to `glob:` in a future release. The new behavior can be
|
||||
enabled by: `ui.revsets-use-glob-by-default=true`.
|
||||
|
||||
* `"string"` or `substring:"string"`: Matches strings that contain `string`.
|
||||
* `exact:"string"`: Matches strings exactly equal to `string`.
|
||||
* `glob:"pattern"`: Matches strings with Unix-style shell [wildcard
|
||||
`pattern`](https://docs.rs/globset/latest/globset/#syntax).
|
||||
* `regex:"pattern"`: Matches substrings with [regular
|
||||
expression `pattern`](https://docs.rs/regex/latest/regex/#syntax).
|
||||
* `substring:"string"`: Matches strings that contain `string`.
|
||||
|
||||
You can append `-i` after the kind to match case‐insensitively (e.g.
|
||||
`glob-i:"fix*jpeg*"`).
|
||||
|
||||
@@ -889,22 +889,17 @@ static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock:
|
||||
})?;
|
||||
Ok(RevsetExpression::commit_id_prefix(prefix))
|
||||
});
|
||||
map.insert("bookmarks", |diagnostics, function, _context| {
|
||||
map.insert("bookmarks", |diagnostics, function, context| {
|
||||
let ([], [opt_arg]) = function.expect_arguments()?;
|
||||
let expr = if let Some(arg) = opt_arg {
|
||||
expect_string_expression(diagnostics, arg)?
|
||||
expect_string_expression(diagnostics, arg, context)?
|
||||
} else {
|
||||
StringExpression::all()
|
||||
};
|
||||
Ok(RevsetExpression::bookmarks(expr))
|
||||
});
|
||||
map.insert("remote_bookmarks", |diagnostics, function, context| {
|
||||
parse_remote_bookmarks_arguments(
|
||||
diagnostics,
|
||||
function,
|
||||
None,
|
||||
context.default_ignored_remote,
|
||||
)
|
||||
parse_remote_bookmarks_arguments(diagnostics, function, None, context)
|
||||
});
|
||||
map.insert(
|
||||
"tracked_remote_bookmarks",
|
||||
@@ -913,7 +908,7 @@ static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock:
|
||||
diagnostics,
|
||||
function,
|
||||
Some(RemoteRefState::Tracked),
|
||||
context.default_ignored_remote,
|
||||
context,
|
||||
)
|
||||
},
|
||||
);
|
||||
@@ -924,14 +919,14 @@ static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock:
|
||||
diagnostics,
|
||||
function,
|
||||
Some(RemoteRefState::New),
|
||||
context.default_ignored_remote,
|
||||
context,
|
||||
)
|
||||
},
|
||||
);
|
||||
map.insert("tags", |diagnostics, function, _context| {
|
||||
map.insert("tags", |diagnostics, function, context| {
|
||||
let ([], [opt_arg]) = function.expect_arguments()?;
|
||||
let expr = if let Some(arg) = opt_arg {
|
||||
expect_string_expression(diagnostics, arg)?
|
||||
expect_string_expression(diagnostics, arg, context)?
|
||||
} else {
|
||||
StringExpression::all()
|
||||
};
|
||||
@@ -977,35 +972,35 @@ static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock:
|
||||
RevsetFilterPredicate::ParentCount(2..u32::MAX),
|
||||
))
|
||||
});
|
||||
map.insert("description", |diagnostics, function, _context| {
|
||||
map.insert("description", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let predicate = RevsetFilterPredicate::Description(expr);
|
||||
Ok(RevsetExpression::filter(predicate))
|
||||
});
|
||||
map.insert("subject", |diagnostics, function, _context| {
|
||||
map.insert("subject", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let predicate = RevsetFilterPredicate::Subject(expr);
|
||||
Ok(RevsetExpression::filter(predicate))
|
||||
});
|
||||
map.insert("author", |diagnostics, function, _context| {
|
||||
map.insert("author", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let name_predicate = RevsetFilterPredicate::AuthorName(expr.clone());
|
||||
let email_predicate = RevsetFilterPredicate::AuthorEmail(expr);
|
||||
Ok(RevsetExpression::filter(name_predicate)
|
||||
.union(&RevsetExpression::filter(email_predicate)))
|
||||
});
|
||||
map.insert("author_name", |diagnostics, function, _context| {
|
||||
map.insert("author_name", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let predicate = RevsetFilterPredicate::AuthorName(expr);
|
||||
Ok(RevsetExpression::filter(predicate))
|
||||
});
|
||||
map.insert("author_email", |diagnostics, function, _context| {
|
||||
map.insert("author_email", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let predicate = RevsetFilterPredicate::AuthorEmail(expr);
|
||||
Ok(RevsetExpression::filter(predicate))
|
||||
});
|
||||
@@ -1030,23 +1025,23 @@ static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock:
|
||||
let predicate = RevsetFilterPredicate::AuthorEmail(StringExpression::pattern(pattern));
|
||||
Ok(RevsetExpression::filter(predicate))
|
||||
});
|
||||
map.insert("committer", |diagnostics, function, _context| {
|
||||
map.insert("committer", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let name_predicate = RevsetFilterPredicate::CommitterName(expr.clone());
|
||||
let email_predicate = RevsetFilterPredicate::CommitterEmail(expr);
|
||||
Ok(RevsetExpression::filter(name_predicate)
|
||||
.union(&RevsetExpression::filter(email_predicate)))
|
||||
});
|
||||
map.insert("committer_name", |diagnostics, function, _context| {
|
||||
map.insert("committer_name", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let predicate = RevsetFilterPredicate::CommitterName(expr);
|
||||
Ok(RevsetExpression::filter(predicate))
|
||||
});
|
||||
map.insert("committer_email", |diagnostics, function, _context| {
|
||||
map.insert("committer_email", |diagnostics, function, context| {
|
||||
let [arg] = function.expect_exact_arguments()?;
|
||||
let expr = expect_string_expression(diagnostics, arg)?;
|
||||
let expr = expect_string_expression(diagnostics, arg, context)?;
|
||||
let predicate = RevsetFilterPredicate::CommitterEmail(expr);
|
||||
Ok(RevsetExpression::filter(predicate))
|
||||
});
|
||||
@@ -1074,7 +1069,7 @@ static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock:
|
||||
});
|
||||
map.insert("diff_contains", |diagnostics, function, context| {
|
||||
let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
|
||||
let text = expect_string_expression(diagnostics, text_arg)?;
|
||||
let text = expect_string_expression(diagnostics, text_arg, context)?;
|
||||
let files = if let Some(files_arg) = files_opt_arg {
|
||||
let ctx = context.workspace.as_ref().ok_or_else(|| {
|
||||
RevsetParseError::with_span(
|
||||
@@ -1150,8 +1145,13 @@ pub fn expect_fileset_expression(
|
||||
pub fn expect_string_expression(
|
||||
diagnostics: &mut RevsetDiagnostics,
|
||||
node: &ExpressionNode,
|
||||
context: &LoweringContext,
|
||||
) -> Result<StringExpression, RevsetParseError> {
|
||||
let default_kind = "substring";
|
||||
let default_kind = if context.use_glob_by_default {
|
||||
"glob"
|
||||
} else {
|
||||
"substring"
|
||||
};
|
||||
expect_string_expression_inner(diagnostics, node, default_kind)
|
||||
}
|
||||
|
||||
@@ -1164,14 +1164,22 @@ fn expect_string_expression_inner(
|
||||
revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| {
|
||||
let expr_error = || RevsetParseError::expression("Invalid string expression", node.span);
|
||||
let pattern_error = || RevsetParseError::expression("Invalid string pattern", node.span);
|
||||
let default_pattern = |value: &str| {
|
||||
let default_pattern = |diagnostics: &mut RevsetDiagnostics, value: &str| {
|
||||
if default_kind == "substring" {
|
||||
diagnostics.add_warning(RevsetParseError::expression(
|
||||
"Default pattern syntax will be changed to `glob:` in a future release; use \
|
||||
`substring:` prefix or set ui.revsets-use-glob-by-default=true to suppress \
|
||||
this warning",
|
||||
node.span,
|
||||
));
|
||||
}
|
||||
let pattern = StringPattern::from_str_kind(value, default_kind)
|
||||
.map_err(|err| pattern_error().with_source(err))?;
|
||||
Ok(StringExpression::pattern(pattern))
|
||||
};
|
||||
match &node.kind {
|
||||
ExpressionKind::Identifier(value) => default_pattern(value),
|
||||
ExpressionKind::String(value) => default_pattern(value),
|
||||
ExpressionKind::Identifier(value) => default_pattern(diagnostics, value),
|
||||
ExpressionKind::String(value) => default_pattern(diagnostics, value),
|
||||
ExpressionKind::StringPattern { kind, value } => {
|
||||
let pattern = StringPattern::from_str_kind(value, kind)
|
||||
.map_err(|err| pattern_error().with_source(err))?;
|
||||
@@ -1236,18 +1244,18 @@ fn parse_remote_bookmarks_arguments(
|
||||
diagnostics: &mut RevsetDiagnostics,
|
||||
function: &FunctionCallNode,
|
||||
remote_ref_state: Option<RemoteRefState>,
|
||||
default_ignored_remote: Option<&RemoteName>,
|
||||
context: &LoweringContext,
|
||||
) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
|
||||
let ([], [bookmark_opt_arg, remote_opt_arg]) =
|
||||
function.expect_named_arguments(&["", "remote"])?;
|
||||
let bookmark_expr = if let Some(bookmark_arg) = bookmark_opt_arg {
|
||||
expect_string_expression(diagnostics, bookmark_arg)?
|
||||
expect_string_expression(diagnostics, bookmark_arg, context)?
|
||||
} else {
|
||||
StringExpression::all()
|
||||
};
|
||||
let remote_expr = if let Some(remote_arg) = remote_opt_arg {
|
||||
expect_string_expression(diagnostics, remote_arg)?
|
||||
} else if let Some(remote) = default_ignored_remote {
|
||||
expect_string_expression(diagnostics, remote_arg, context)?
|
||||
} else if let Some(remote) = context.default_ignored_remote {
|
||||
StringExpression::exact(remote).negated()
|
||||
} else {
|
||||
StringExpression::all()
|
||||
@@ -3426,6 +3434,7 @@ pub struct RevsetParseContext<'a> {
|
||||
pub date_pattern_context: DatePatternContext,
|
||||
/// Special remote that should be ignored by default. (e.g. "git")
|
||||
pub default_ignored_remote: Option<&'a RemoteName>,
|
||||
pub use_glob_by_default: bool,
|
||||
pub extensions: &'a RevsetExtensions,
|
||||
pub workspace: Option<RevsetWorkspaceContext<'a>>,
|
||||
}
|
||||
@@ -3438,6 +3447,7 @@ impl<'a> RevsetParseContext<'a> {
|
||||
user_email,
|
||||
date_pattern_context,
|
||||
default_ignored_remote,
|
||||
use_glob_by_default,
|
||||
extensions,
|
||||
workspace,
|
||||
} = *self;
|
||||
@@ -3445,6 +3455,7 @@ impl<'a> RevsetParseContext<'a> {
|
||||
user_email,
|
||||
date_pattern_context,
|
||||
default_ignored_remote,
|
||||
use_glob_by_default,
|
||||
extensions,
|
||||
workspace,
|
||||
}
|
||||
@@ -3457,6 +3468,7 @@ pub struct LoweringContext<'a> {
|
||||
user_email: &'a str,
|
||||
date_pattern_context: DatePatternContext,
|
||||
default_ignored_remote: Option<&'a RemoteName>,
|
||||
use_glob_by_default: bool,
|
||||
extensions: &'a RevsetExtensions,
|
||||
workspace: Option<RevsetWorkspaceContext<'a>>,
|
||||
}
|
||||
@@ -3544,6 +3556,7 @@ mod tests {
|
||||
user_email: "test.user@example.com",
|
||||
date_pattern_context: chrono::Utc::now().fixed_offset().into(),
|
||||
default_ignored_remote: Some("ignored".as_ref()),
|
||||
use_glob_by_default: false,
|
||||
extensions: &RevsetExtensions::default(),
|
||||
workspace: None,
|
||||
};
|
||||
@@ -3574,6 +3587,7 @@ mod tests {
|
||||
user_email: "test.user@example.com",
|
||||
date_pattern_context: chrono::Utc::now().fixed_offset().into(),
|
||||
default_ignored_remote: Some("ignored".as_ref()),
|
||||
use_glob_by_default: false,
|
||||
extensions: &RevsetExtensions::default(),
|
||||
workspace: Some(workspace_ctx),
|
||||
};
|
||||
@@ -3600,6 +3614,7 @@ mod tests {
|
||||
user_email: "test.user@example.com",
|
||||
date_pattern_context: chrono::Utc::now().fixed_offset().into(),
|
||||
default_ignored_remote: Some("ignored".as_ref()),
|
||||
use_glob_by_default: false,
|
||||
extensions: &RevsetExtensions::default(),
|
||||
workspace: None,
|
||||
};
|
||||
|
||||
@@ -97,6 +97,7 @@ fn resolve_symbol(repo: &dyn Repo, symbol: &str) -> Result<Vec<CommitId>, Revset
|
||||
user_email: "",
|
||||
date_pattern_context: chrono::Local::now().into(),
|
||||
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
|
||||
use_glob_by_default: false,
|
||||
extensions: &RevsetExtensions::default(),
|
||||
workspace: None,
|
||||
};
|
||||
@@ -230,6 +231,7 @@ fn test_resolve_symbol_commit_id() {
|
||||
user_email: settings.user_email(),
|
||||
date_pattern_context: chrono::Utc::now().fixed_offset().into(),
|
||||
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
|
||||
use_glob_by_default: false,
|
||||
extensions: &RevsetExtensions::default(),
|
||||
workspace: None,
|
||||
};
|
||||
@@ -1020,6 +1022,7 @@ fn try_resolve_expression(
|
||||
user_email: settings.user_email(),
|
||||
date_pattern_context: chrono::Utc::now().fixed_offset().into(),
|
||||
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
|
||||
use_glob_by_default: false,
|
||||
extensions: &RevsetExtensions::default(),
|
||||
workspace: None,
|
||||
};
|
||||
@@ -1070,6 +1073,7 @@ fn resolve_commit_ids_in_workspace(
|
||||
user_email: settings.user_email(),
|
||||
date_pattern_context: chrono::Utc::now().fixed_offset().into(),
|
||||
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
|
||||
use_glob_by_default: false,
|
||||
extensions: &RevsetExtensions::default(),
|
||||
workspace: Some(workspace_ctx),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user