forked from mirrors/jj
revset: add author_date and committer_date revset functions
Author dates and committer dates can be filtered like so: committer_date(before:"1 hour ago") # more than 1 hour ago committer_date(after:"1 hour ago") # 1 hour ago or less A date range can be created by combining revsets. For example, to see any revisions committed yesterday: committer_date(after:"yesterday") & committer_date(before:"today")
This commit is contained in:
parent
ff9e739798
commit
6c41b1bef8
8 changed files with 377 additions and 3 deletions
|
@ -99,6 +99,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
This simplifies the use case of configuring code formatters for specific file
|
This simplifies the use case of configuring code formatters for specific file
|
||||||
types. See `jj help fix` for details.
|
types. See `jj help fix` for details.
|
||||||
|
|
||||||
|
* Added revset functions `author_date` and `committer_date`.
|
||||||
|
|
||||||
### Fixed bugs
|
### Fixed bugs
|
||||||
|
|
||||||
* `jj status` will show different messages in a conflicted tree, depending
|
* `jj status` will show different messages in a conflicted tree, depending
|
||||||
|
|
|
@ -28,6 +28,7 @@ use std::time::SystemTime;
|
||||||
use std::{fs, mem, str};
|
use std::{fs, mem, str};
|
||||||
|
|
||||||
use bstr::ByteVec as _;
|
use bstr::ByteVec as _;
|
||||||
|
use chrono::TimeZone;
|
||||||
use clap::builder::{
|
use clap::builder::{
|
||||||
MapValueParser, NonEmptyStringValueParser, TypedValueParser, ValueParserFactory,
|
MapValueParser, NonEmptyStringValueParser, TypedValueParser, ValueParserFactory,
|
||||||
};
|
};
|
||||||
|
@ -1010,9 +1011,17 @@ impl WorkspaceCommandHelper {
|
||||||
path_converter: &self.path_converter,
|
path_converter: &self.path_converter,
|
||||||
workspace_id: self.workspace_id(),
|
workspace_id: self.workspace_id(),
|
||||||
};
|
};
|
||||||
|
let now = if let Some(timestamp) = self.settings.commit_timestamp() {
|
||||||
|
chrono::Local
|
||||||
|
.timestamp_millis_opt(timestamp.timestamp.0)
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
chrono::Local::now()
|
||||||
|
};
|
||||||
RevsetParseContext::new(
|
RevsetParseContext::new(
|
||||||
&self.revset_aliases_map,
|
&self.revset_aliases_map,
|
||||||
self.settings.user_email(),
|
self.settings.user_email(),
|
||||||
|
now.into(),
|
||||||
&self.revset_extensions,
|
&self.revset_extensions,
|
||||||
Some(workspace_context),
|
Some(workspace_context),
|
||||||
)
|
)
|
||||||
|
|
|
@ -325,7 +325,7 @@ fn test_function_name_hint() {
|
||||||
| ^-----^
|
| ^-----^
|
||||||
|
|
|
|
||||||
= Function "author_" doesn't exist
|
= Function "author_" doesn't exist
|
||||||
Hint: Did you mean "author", "my_author"?
|
Hint: Did you mean "author", "author_date", "my_author"?
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
insta::assert_snapshot!(evaluate_err("my_branches"), @r###"
|
insta::assert_snapshot!(evaluate_err("my_branches"), @r###"
|
||||||
|
@ -629,3 +629,126 @@ fn test_all_modifier() {
|
||||||
For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
|
For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verifies that the committer_date revset honors the local time zone.
|
||||||
|
/// This test cannot run on Windows because The TZ env var does not control
|
||||||
|
/// chrono::Local on that platform.
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn test_revset_committer_date_with_time_zone() {
|
||||||
|
// Use these for the test instead of tzdb identifiers like America/New_York
|
||||||
|
// because the tz database may not be installed on some build servers
|
||||||
|
const NEW_YORK: &str = "EST+5EDT+4,M3.1.0,M11.1.0";
|
||||||
|
const CHICAGO: &str = "CST+6CDT+5,M3.1.0,M11.1.0";
|
||||||
|
const AUSTRALIA: &str = "AEST-10";
|
||||||
|
let mut test_env = TestEnvironment::default();
|
||||||
|
test_env.add_env_var("TZ", NEW_YORK);
|
||||||
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
|
||||||
|
let repo_path = test_env.env_root().join("repo");
|
||||||
|
|
||||||
|
test_env.jj_cmd_ok(
|
||||||
|
&repo_path,
|
||||||
|
&[
|
||||||
|
"--config-toml",
|
||||||
|
"debug.commit-timestamp='2023-01-25T11:30:00-05:00'",
|
||||||
|
"describe",
|
||||||
|
"-m",
|
||||||
|
"first",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
test_env.jj_cmd_ok(
|
||||||
|
&repo_path,
|
||||||
|
&[
|
||||||
|
"--config-toml",
|
||||||
|
"debug.commit-timestamp='2023-01-25T12:30:00-05:00'",
|
||||||
|
"new",
|
||||||
|
"-m",
|
||||||
|
"second",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
test_env.jj_cmd_ok(
|
||||||
|
&repo_path,
|
||||||
|
&[
|
||||||
|
"--config-toml",
|
||||||
|
"debug.commit-timestamp='2023-01-25T13:30:00-05:00'",
|
||||||
|
"new",
|
||||||
|
"-m",
|
||||||
|
"third",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut log_commits_before_and_after =
|
||||||
|
|committer_date: &str, now: &str, tz: &str| -> (String, String) {
|
||||||
|
test_env.add_env_var("TZ", tz);
|
||||||
|
let config = format!("debug.commit-timestamp='{now}'");
|
||||||
|
let before_log = test_env.jj_cmd_success(
|
||||||
|
&repo_path,
|
||||||
|
&[
|
||||||
|
"--config-toml",
|
||||||
|
config.as_str(),
|
||||||
|
"log",
|
||||||
|
"--no-graph",
|
||||||
|
"-T",
|
||||||
|
"description.first_line() ++ ' ' ++ committer.timestamp() ++ '\n'",
|
||||||
|
"-r",
|
||||||
|
format!("committer_date(before:'{committer_date}') ~ root()").as_str(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let after_log = test_env.jj_cmd_success(
|
||||||
|
&repo_path,
|
||||||
|
&[
|
||||||
|
"--config-toml",
|
||||||
|
config.as_str(),
|
||||||
|
"log",
|
||||||
|
"--no-graph",
|
||||||
|
"-T",
|
||||||
|
"description.first_line() ++ ' ' ++ committer.timestamp() ++ '\n'",
|
||||||
|
"-r",
|
||||||
|
format!("committer_date(after:'{committer_date}')").as_str(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
(before_log, after_log)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (before_log, after_log) =
|
||||||
|
log_commits_before_and_after("2023-01-25 12:00", "2023-02-01T00:00:00-05:00", NEW_YORK);
|
||||||
|
insta::assert_snapshot!(before_log, @r###"
|
||||||
|
first 2023-01-25 11:30:00.000 -05:00
|
||||||
|
"###);
|
||||||
|
insta::assert_snapshot!(after_log, @r###"
|
||||||
|
third 2023-01-25 13:30:00.000 -05:00
|
||||||
|
second 2023-01-25 12:30:00.000 -05:00
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Switch to DST and ensure we get the same results, because it should
|
||||||
|
// evaluate 12:00 on commit date, not the current date
|
||||||
|
let (before_log, after_log) =
|
||||||
|
log_commits_before_and_after("2023-01-25 12:00", "2023-06-01T00:00:00-04:00", NEW_YORK);
|
||||||
|
insta::assert_snapshot!(before_log, @r###"
|
||||||
|
first 2023-01-25 11:30:00.000 -05:00
|
||||||
|
"###);
|
||||||
|
insta::assert_snapshot!(after_log, @r###"
|
||||||
|
third 2023-01-25 13:30:00.000 -05:00
|
||||||
|
second 2023-01-25 12:30:00.000 -05:00
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Change the local time zone and ensure the result changes
|
||||||
|
let (before_log, after_log) =
|
||||||
|
log_commits_before_and_after("2023-01-25 12:00", "2023-06-01T00:00:00-06:00", CHICAGO);
|
||||||
|
insta::assert_snapshot!(before_log, @r###"
|
||||||
|
second 2023-01-25 12:30:00.000 -05:00
|
||||||
|
first 2023-01-25 11:30:00.000 -05:00
|
||||||
|
"###);
|
||||||
|
insta::assert_snapshot!(after_log, @"third 2023-01-25 13:30:00.000 -05:00");
|
||||||
|
|
||||||
|
// Time zone far outside USA with no DST
|
||||||
|
let (before_log, after_log) =
|
||||||
|
log_commits_before_and_after("2023-01-26 03:00", "2023-06-01T00:00:00+10:00", AUSTRALIA);
|
||||||
|
insta::assert_snapshot!(before_log, @r###"
|
||||||
|
first 2023-01-25 11:30:00.000 -05:00
|
||||||
|
"###);
|
||||||
|
insta::assert_snapshot!(after_log, @r###"
|
||||||
|
third 2023-01-25 13:30:00.000 -05:00
|
||||||
|
second 2023-01-25 12:30:00.000 -05:00
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
|
@ -265,6 +265,12 @@ revsets (expressions) as arguments.
|
||||||
* `committer(pattern)`: Commits with the committer's name or email matching the
|
* `committer(pattern)`: Commits with the committer's name or email matching the
|
||||||
given [string pattern](#string-patterns).
|
given [string pattern](#string-patterns).
|
||||||
|
|
||||||
|
* `author_date(pattern)`: Commits with author dates matching the specified [date
|
||||||
|
pattern](#date-patterns).
|
||||||
|
|
||||||
|
* `committer_date(pattern)`: Commits with committer dates matching the specified
|
||||||
|
[date pattern](#date-patterns).
|
||||||
|
|
||||||
* `empty()`: Commits modifying no files. This also includes `merges()` without
|
* `empty()`: Commits modifying no files. This also includes `merges()` without
|
||||||
user modifications and `root()`.
|
user modifications and `root()`.
|
||||||
|
|
||||||
|
@ -359,6 +365,26 @@ Functions that perform string matching support the following pattern syntax:
|
||||||
You can append `-i` after the kind to match case‐insensitively (e.g.
|
You can append `-i` after the kind to match case‐insensitively (e.g.
|
||||||
`glob-i:"fix*jpeg*"`).
|
`glob-i:"fix*jpeg*"`).
|
||||||
|
|
||||||
|
## Date patterns
|
||||||
|
|
||||||
|
Functions that perform date matching support the following pattern syntax:
|
||||||
|
|
||||||
|
* `after:"string"`: Matches dates exactly at or after the given date.
|
||||||
|
* `before:"string"`: Matches dates before, but not including, the given date.
|
||||||
|
|
||||||
|
Date strings can be specified in several forms, including:
|
||||||
|
|
||||||
|
* 2024-02-01
|
||||||
|
* 2024-02-01T12:00:00
|
||||||
|
* 2024-02-01T12:00:00-08:00
|
||||||
|
* 2024-02-01 12:00:00
|
||||||
|
* 2 days ago
|
||||||
|
* 5 minutes ago
|
||||||
|
* yesterday
|
||||||
|
* yesterday 5pm
|
||||||
|
* yesterday 10:30
|
||||||
|
* yesterday 15:30
|
||||||
|
|
||||||
## Aliases
|
## Aliases
|
||||||
|
|
||||||
New symbols and functions can be defined in the config file, by using any
|
New symbols and functions can be defined in the config file, by using any
|
||||||
|
|
|
@ -1074,6 +1074,24 @@ fn build_predicate_fn(
|
||||||
|| pattern.matches(&commit.committer().email)
|
|| pattern.matches(&commit.committer().email)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
RevsetFilterPredicate::AuthorDate(expression) => {
|
||||||
|
let expression = *expression;
|
||||||
|
box_pure_predicate_fn(move |index, pos| {
|
||||||
|
let entry = index.entry_by_pos(pos);
|
||||||
|
let commit = store.get_commit(&entry.commit_id()).unwrap();
|
||||||
|
let author_date = &commit.author().timestamp;
|
||||||
|
expression.matches(author_date)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
RevsetFilterPredicate::CommitterDate(expression) => {
|
||||||
|
let expression = *expression;
|
||||||
|
box_pure_predicate_fn(move |index, pos| {
|
||||||
|
let entry = index.entry_by_pos(pos);
|
||||||
|
let commit = store.get_commit(&entry.commit_id()).unwrap();
|
||||||
|
let committer_date = &commit.committer().timestamp;
|
||||||
|
expression.matches(committer_date)
|
||||||
|
})
|
||||||
|
}
|
||||||
RevsetFilterPredicate::File(expr) => {
|
RevsetFilterPredicate::File(expr) => {
|
||||||
let matcher: Rc<dyn Matcher> = expr.to_matcher().into();
|
let matcher: Rc<dyn Matcher> = expr.to_matcher().into();
|
||||||
box_pure_predicate_fn(move |index, pos| {
|
box_pure_predicate_fn(move |index, pos| {
|
||||||
|
|
|
@ -43,6 +43,7 @@ pub use crate::revset_parser::{
|
||||||
};
|
};
|
||||||
use crate::store::Store;
|
use crate::store::Store;
|
||||||
use crate::str_util::StringPattern;
|
use crate::str_util::StringPattern;
|
||||||
|
use crate::time_util::{DatePattern, DatePatternContext};
|
||||||
use crate::{dsl_util, fileset, revset_parser};
|
use crate::{dsl_util, fileset, revset_parser};
|
||||||
|
|
||||||
/// Error occurred during symbol resolution.
|
/// Error occurred during symbol resolution.
|
||||||
|
@ -132,6 +133,10 @@ pub enum RevsetFilterPredicate {
|
||||||
Author(StringPattern),
|
Author(StringPattern),
|
||||||
/// Commits with committer name or email matching the pattern.
|
/// Commits with committer name or email matching the pattern.
|
||||||
Committer(StringPattern),
|
Committer(StringPattern),
|
||||||
|
/// Commits with author dates matching the given date pattern.
|
||||||
|
AuthorDate(DatePattern),
|
||||||
|
/// Commits with committer dates matching the given date pattern.
|
||||||
|
CommitterDate(DatePattern),
|
||||||
/// Commits modifying the paths specified by the fileset.
|
/// Commits modifying the paths specified by the fileset.
|
||||||
File(FilesetExpression),
|
File(FilesetExpression),
|
||||||
/// Commits containing diffs matching the `text` pattern within the `files`.
|
/// Commits containing diffs matching the `text` pattern within the `files`.
|
||||||
|
@ -684,6 +689,13 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
|
||||||
pattern,
|
pattern,
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
map.insert("author_date", |function, context| {
|
||||||
|
let [arg] = function.expect_exact_arguments()?;
|
||||||
|
let pattern = expect_date_pattern(arg, context.date_pattern_context())?;
|
||||||
|
Ok(RevsetExpression::filter(RevsetFilterPredicate::AuthorDate(
|
||||||
|
pattern,
|
||||||
|
)))
|
||||||
|
});
|
||||||
map.insert("mine", |function, context| {
|
map.insert("mine", |function, context| {
|
||||||
function.expect_no_arguments()?;
|
function.expect_no_arguments()?;
|
||||||
// Email address domains are inherently case‐insensitive, and the local‐parts
|
// Email address domains are inherently case‐insensitive, and the local‐parts
|
||||||
|
@ -700,6 +712,13 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
|
||||||
pattern,
|
pattern,
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
map.insert("committer_date", |function, context| {
|
||||||
|
let [arg] = function.expect_exact_arguments()?;
|
||||||
|
let pattern = expect_date_pattern(arg, context.date_pattern_context())?;
|
||||||
|
Ok(RevsetExpression::filter(
|
||||||
|
RevsetFilterPredicate::CommitterDate(pattern),
|
||||||
|
))
|
||||||
|
});
|
||||||
map.insert("empty", |function, _context| {
|
map.insert("empty", |function, _context| {
|
||||||
function.expect_no_arguments()?;
|
function.expect_no_arguments()?;
|
||||||
Ok(RevsetExpression::is_empty())
|
Ok(RevsetExpression::is_empty())
|
||||||
|
@ -774,6 +793,20 @@ pub fn expect_string_pattern(node: &ExpressionNode) -> Result<StringPattern, Rev
|
||||||
revset_parser::expect_pattern_with("string pattern", node, parse_pattern)
|
revset_parser::expect_pattern_with("string pattern", node, parse_pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expect_date_pattern(
|
||||||
|
node: &ExpressionNode,
|
||||||
|
context: &DatePatternContext,
|
||||||
|
) -> Result<DatePattern, RevsetParseError> {
|
||||||
|
let parse_pattern =
|
||||||
|
|value: &str, kind: Option<&str>| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
match kind {
|
||||||
|
None => Err("Date pattern must specify 'after' or 'before'".into()),
|
||||||
|
Some(kind) => Ok(context.parse_relative(value, kind)?),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
revset_parser::expect_pattern_with("date pattern", node, parse_pattern)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_remote_branches_arguments(
|
fn parse_remote_branches_arguments(
|
||||||
function: &FunctionCallNode,
|
function: &FunctionCallNode,
|
||||||
remote_ref_state: Option<RemoteRefState>,
|
remote_ref_state: Option<RemoteRefState>,
|
||||||
|
@ -2035,6 +2068,7 @@ impl RevsetExtensions {
|
||||||
pub struct RevsetParseContext<'a> {
|
pub struct RevsetParseContext<'a> {
|
||||||
aliases_map: &'a RevsetAliasesMap,
|
aliases_map: &'a RevsetAliasesMap,
|
||||||
user_email: String,
|
user_email: String,
|
||||||
|
date_pattern_context: DatePatternContext,
|
||||||
extensions: &'a RevsetExtensions,
|
extensions: &'a RevsetExtensions,
|
||||||
workspace: Option<RevsetWorkspaceContext<'a>>,
|
workspace: Option<RevsetWorkspaceContext<'a>>,
|
||||||
}
|
}
|
||||||
|
@ -2043,12 +2077,14 @@ impl<'a> RevsetParseContext<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
aliases_map: &'a RevsetAliasesMap,
|
aliases_map: &'a RevsetAliasesMap,
|
||||||
user_email: String,
|
user_email: String,
|
||||||
|
date_pattern_context: DatePatternContext,
|
||||||
extensions: &'a RevsetExtensions,
|
extensions: &'a RevsetExtensions,
|
||||||
workspace: Option<RevsetWorkspaceContext<'a>>,
|
workspace: Option<RevsetWorkspaceContext<'a>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
aliases_map,
|
aliases_map,
|
||||||
user_email,
|
user_email,
|
||||||
|
date_pattern_context,
|
||||||
extensions,
|
extensions,
|
||||||
workspace,
|
workspace,
|
||||||
}
|
}
|
||||||
|
@ -2062,6 +2098,10 @@ impl<'a> RevsetParseContext<'a> {
|
||||||
&self.user_email
|
&self.user_email
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn date_pattern_context(&self) -> &DatePatternContext {
|
||||||
|
&self.date_pattern_context
|
||||||
|
}
|
||||||
|
|
||||||
pub fn symbol_resolvers(&self) -> &[impl AsRef<dyn SymbolResolverExtension>] {
|
pub fn symbol_resolvers(&self) -> &[impl AsRef<dyn SymbolResolverExtension>] {
|
||||||
self.extensions.symbol_resolvers()
|
self.extensions.symbol_resolvers()
|
||||||
}
|
}
|
||||||
|
@ -2105,6 +2145,7 @@ mod tests {
|
||||||
let context = RevsetParseContext::new(
|
let context = RevsetParseContext::new(
|
||||||
&aliases_map,
|
&aliases_map,
|
||||||
"test.user@example.com".to_string(),
|
"test.user@example.com".to_string(),
|
||||||
|
chrono::Utc::now().fixed_offset().into(),
|
||||||
&extensions,
|
&extensions,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
@ -2133,6 +2174,7 @@ mod tests {
|
||||||
let context = RevsetParseContext::new(
|
let context = RevsetParseContext::new(
|
||||||
&aliases_map,
|
&aliases_map,
|
||||||
"test.user@example.com".to_string(),
|
"test.user@example.com".to_string(),
|
||||||
|
chrono::Utc::now().fixed_offset().into(),
|
||||||
&extensions,
|
&extensions,
|
||||||
Some(workspace_ctx),
|
Some(workspace_ctx),
|
||||||
);
|
);
|
||||||
|
@ -2157,6 +2199,7 @@ mod tests {
|
||||||
let context = RevsetParseContext::new(
|
let context = RevsetParseContext::new(
|
||||||
&aliases_map,
|
&aliases_map,
|
||||||
"test.user@example.com".to_string(),
|
"test.user@example.com".to_string(),
|
||||||
|
chrono::Utc::now().fixed_offset().into(),
|
||||||
&extensions,
|
&extensions,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
|
@ -172,6 +172,10 @@ impl UserSettings {
|
||||||
// address
|
// address
|
||||||
pub const USER_EMAIL_PLACEHOLDER: &'static str = "(no email configured)";
|
pub const USER_EMAIL_PLACEHOLDER: &'static str = "(no email configured)";
|
||||||
|
|
||||||
|
pub fn commit_timestamp(&self) -> Option<Timestamp> {
|
||||||
|
self.timestamp.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn operation_timestamp(&self) -> Option<Timestamp> {
|
pub fn operation_timestamp(&self) -> Option<Timestamp> {
|
||||||
get_timestamp_config(&self.config, "debug.operation-timestamp")
|
get_timestamp_config(&self.config, "debug.operation-timestamp")
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use std::iter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
|
use chrono::DateTime;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use jj_lib::backend::{CommitId, MillisSinceEpoch, Signature, Timestamp};
|
use jj_lib::backend::{CommitId, MillisSinceEpoch, Signature, Timestamp};
|
||||||
use jj_lib::commit::Commit;
|
use jj_lib::commit::Commit;
|
||||||
|
@ -46,7 +47,9 @@ fn resolve_symbol_with_extensions(
|
||||||
symbol: &str,
|
symbol: &str,
|
||||||
) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
||||||
let aliases_map = RevsetAliasesMap::default();
|
let aliases_map = RevsetAliasesMap::default();
|
||||||
let context = RevsetParseContext::new(&aliases_map, String::new(), extensions, None);
|
let now = chrono::Local::now();
|
||||||
|
let context =
|
||||||
|
RevsetParseContext::new(&aliases_map, String::new(), now.into(), extensions, None);
|
||||||
let expression = parse(symbol, &context).unwrap();
|
let expression = parse(symbol, &context).unwrap();
|
||||||
assert_matches!(*expression, RevsetExpression::CommitRef(_));
|
assert_matches!(*expression, RevsetExpression::CommitRef(_));
|
||||||
let symbol_resolver = DefaultSymbolResolver::new(repo, extensions.symbol_resolvers());
|
let symbol_resolver = DefaultSymbolResolver::new(repo, extensions.symbol_resolvers());
|
||||||
|
@ -180,7 +183,13 @@ fn test_resolve_symbol_commit_id() {
|
||||||
);
|
);
|
||||||
let aliases_map = RevsetAliasesMap::default();
|
let aliases_map = RevsetAliasesMap::default();
|
||||||
let extensions = RevsetExtensions::default();
|
let extensions = RevsetExtensions::default();
|
||||||
let context = RevsetParseContext::new(&aliases_map, settings.user_email(), &extensions, None);
|
let context = RevsetParseContext::new(
|
||||||
|
&aliases_map,
|
||||||
|
settings.user_email(),
|
||||||
|
chrono::Utc::now().fixed_offset().into(),
|
||||||
|
&extensions,
|
||||||
|
None,
|
||||||
|
);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
optimize(parse("present(04)", &context).unwrap()).resolve_user_expression(repo.as_ref(), &symbol_resolver),
|
optimize(parse("present(04)", &context).unwrap()).resolve_user_expression(repo.as_ref(), &symbol_resolver),
|
||||||
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "04"
|
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "04"
|
||||||
|
@ -838,6 +847,7 @@ fn resolve_commit_ids(repo: &dyn Repo, revset_str: &str) -> Vec<CommitId> {
|
||||||
let context = RevsetParseContext::new(
|
let context = RevsetParseContext::new(
|
||||||
&aliases_map,
|
&aliases_map,
|
||||||
settings.user_email(),
|
settings.user_email(),
|
||||||
|
chrono::Utc::now().fixed_offset().into(),
|
||||||
&revset_extensions,
|
&revset_extensions,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
@ -869,6 +879,7 @@ fn resolve_commit_ids_in_workspace(
|
||||||
let context = RevsetParseContext::new(
|
let context = RevsetParseContext::new(
|
||||||
&aliases_map,
|
&aliases_map,
|
||||||
settings.user_email(),
|
settings.user_email(),
|
||||||
|
chrono::Utc::now().fixed_offset().into(),
|
||||||
&extensions,
|
&extensions,
|
||||||
Some(workspace_ctx),
|
Some(workspace_ctx),
|
||||||
);
|
);
|
||||||
|
@ -2478,6 +2489,144 @@ fn test_evaluate_expression_author() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_timestamp(s: &str) -> Timestamp {
|
||||||
|
Timestamp::from_datetime(s.parse::<DateTime<chrono::FixedOffset>>().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_evaluate_expression_author_date() {
|
||||||
|
let settings = testutils::user_settings();
|
||||||
|
let test_repo = TestRepo::init();
|
||||||
|
let repo = &test_repo.repo;
|
||||||
|
|
||||||
|
let mut tx = repo.start_transaction(&settings);
|
||||||
|
let mut_repo = tx.mut_repo();
|
||||||
|
|
||||||
|
let timestamp1 = parse_timestamp("2023-03-25T11:30:00Z");
|
||||||
|
let timestamp2 = parse_timestamp("2023-03-25T12:30:00Z");
|
||||||
|
let timestamp3 = parse_timestamp("2023-03-25T13:30:00Z");
|
||||||
|
|
||||||
|
let root_commit = repo.store().root_commit();
|
||||||
|
let commit1 = create_random_commit(mut_repo, &settings)
|
||||||
|
.set_author(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp1.clone(),
|
||||||
|
})
|
||||||
|
.set_committer(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.write()
|
||||||
|
.unwrap();
|
||||||
|
let commit2 = create_random_commit(mut_repo, &settings)
|
||||||
|
.set_parents(vec![commit1.id().clone()])
|
||||||
|
.set_author(Signature {
|
||||||
|
name: "name2".to_string(),
|
||||||
|
email: "email2".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.set_committer(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.write()
|
||||||
|
.unwrap();
|
||||||
|
let commit3 = create_random_commit(mut_repo, &settings)
|
||||||
|
.set_parents(vec![commit2.id().clone()])
|
||||||
|
.set_author(Signature {
|
||||||
|
name: "name3".to_string(),
|
||||||
|
email: "email3".to_string(),
|
||||||
|
timestamp: timestamp3,
|
||||||
|
})
|
||||||
|
.set_committer(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.write()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Can find multiple matches
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(mut_repo, "author_date(after:'2023-03-25 12:00')"),
|
||||||
|
vec![commit3.id().clone(), commit2.id().clone()]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(mut_repo, "author_date(before:'2023-03-25 12:00')"),
|
||||||
|
vec![commit1.id().clone(), root_commit.id().clone()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_evaluate_expression_committer_date() {
|
||||||
|
let settings = testutils::user_settings();
|
||||||
|
let test_repo = TestRepo::init();
|
||||||
|
let repo = &test_repo.repo;
|
||||||
|
|
||||||
|
let mut tx = repo.start_transaction(&settings);
|
||||||
|
let mut_repo = tx.mut_repo();
|
||||||
|
|
||||||
|
let timestamp1 = parse_timestamp("2023-03-25T11:30:00Z");
|
||||||
|
let timestamp2 = parse_timestamp("2023-03-25T12:30:00Z");
|
||||||
|
let timestamp3 = parse_timestamp("2023-03-25T13:30:00Z");
|
||||||
|
|
||||||
|
let root_commit = repo.store().root_commit();
|
||||||
|
let commit1 = create_random_commit(mut_repo, &settings)
|
||||||
|
.set_author(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.set_committer(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp1.clone(),
|
||||||
|
})
|
||||||
|
.write()
|
||||||
|
.unwrap();
|
||||||
|
let commit2 = create_random_commit(mut_repo, &settings)
|
||||||
|
.set_parents(vec![commit1.id().clone()])
|
||||||
|
.set_author(Signature {
|
||||||
|
name: "name2".to_string(),
|
||||||
|
email: "email2".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.set_committer(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.write()
|
||||||
|
.unwrap();
|
||||||
|
let commit3 = create_random_commit(mut_repo, &settings)
|
||||||
|
.set_parents(vec![commit2.id().clone()])
|
||||||
|
.set_author(Signature {
|
||||||
|
name: "name3".to_string(),
|
||||||
|
email: "email3".to_string(),
|
||||||
|
timestamp: timestamp2.clone(),
|
||||||
|
})
|
||||||
|
.set_committer(Signature {
|
||||||
|
name: "name1".to_string(),
|
||||||
|
email: "email1".to_string(),
|
||||||
|
timestamp: timestamp3,
|
||||||
|
})
|
||||||
|
.write()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Can find multiple matches
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(mut_repo, "committer_date(after:'2023-03-25 12:00')"),
|
||||||
|
vec![commit3.id().clone(), commit2.id().clone()]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(mut_repo, "committer_date(before:'2023-03-25 12:00')"),
|
||||||
|
vec![commit1.id().clone(), root_commit.id().clone()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_evaluate_expression_mine() {
|
fn test_evaluate_expression_mine() {
|
||||||
let settings = testutils::user_settings();
|
let settings = testutils::user_settings();
|
||||||
|
|
Loading…
Reference in a new issue