Skip to content

Commit

Permalink
revset: add author_date and committer_date revset functions
Browse files Browse the repository at this point in the history
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
    committer_date("1 hour ago")        # 1 hour ago or less ("after" is the default)

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")
  • Loading branch information
jennings committed Jul 3, 2024
1 parent 098513f commit 5e1276d
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

* `jj commit` now accepts `--reset-author` option to match `jj describe`.

* Added revset functions `author_date` and `committer_date`.

### Fixed bugs

* `jj git push` now ignores immutable commits when checking whether a
Expand Down
1 change: 1 addition & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,7 @@ impl WorkspaceCommandHelper {
RevsetParseContext::new(
&self.revset_aliases_map,
self.settings.user_email(),
chrono::Local::now(),
&self.revset_extensions,
Some(workspace_context),
)
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/test_revset_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ fn test_function_name_hint() {
| ^-----^
|
= 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###"
Expand Down
26 changes: 26 additions & 0 deletions docs/revsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ revsets (expressions) as arguments.
* `committer(pattern)`: Commits with the committer's name or email matching the
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
user modifications and `root()`.

Expand Down Expand Up @@ -333,6 +339,26 @@ Functions that perform string matching support the following pattern syntax:
* `glob:"pattern"`: Matches strings with Unix-style shell [wildcard
`pattern`](https://docs.rs/glob/latest/glob/struct.Pattern.html).

## Date patterns

Functions that perform date matching support the following pattern syntax:

* `after:"string"`, or `"string"`: Matches dates 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

New symbols and functions can be defined in the config file, by using any
Expand Down
25 changes: 25 additions & 0 deletions lib/src/default_index/revset_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use crate::revset::{
RevsetFilterPredicate, GENERATION_RANGE_FULL,
};
use crate::store::Store;
use crate::time_pattern::TimePattern;
use crate::{rewrite, union_find};

type BoxedPredicateFn<'a> = Box<dyn FnMut(&CompositeIndex, IndexPosition) -> bool + 'a>;
Expand Down Expand Up @@ -1069,6 +1070,30 @@ fn build_predicate_fn(
|| pattern.matches(&commit.committer().email)
})
}
RevsetFilterPredicate::AuthorDate(expression) => {
let expression = expression.clone();
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;
match &expression {
TimePattern::AtOrAfter(ts) => ts.le(author_date),
TimePattern::Before(ts) => ts.gt(author_date),
}
})
}
RevsetFilterPredicate::CommitterDate(expression) => {
let expression = expression.clone();
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;
match &expression {
TimePattern::AtOrAfter(ts) => ts.le(committer_date),
TimePattern::Before(ts) => ts.gt(committer_date),
}
})
}
RevsetFilterPredicate::File(expr) => {
let matcher: Rc<dyn Matcher> = expr.to_matcher().into();
box_pure_predicate_fn(move |index, pos| {
Expand Down
56 changes: 55 additions & 1 deletion lib/src/revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;

use chrono::{DateTime, FixedOffset, NaiveDateTime, Offset, TimeZone};
use itertools::Itertools;
use once_cell::sync::Lazy;
use thiserror::Error;
Expand All @@ -43,6 +44,7 @@ pub use crate::revset_parser::{
};
use crate::store::Store;
use crate::str_util::StringPattern;
use crate::time_pattern::TimePattern;
use crate::{dsl_util, revset_parser};

/// Error occurred during symbol resolution.
Expand Down Expand Up @@ -131,6 +133,10 @@ pub enum RevsetFilterPredicate {
Author(StringPattern),
/// Commits with committer's name or email containing the needle.
Committer(StringPattern),
/// Commits authored after the given date.
AuthorDate(TimePattern),
/// Commits committed after the given date.
CommitterDate(TimePattern),
/// Commits modifying the paths specified by the fileset.
File(FilesetExpression),
/// Commits with conflicts
Expand Down Expand Up @@ -685,6 +691,13 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
pattern,
)))
});
map.insert("author_date", |function, context| {
let [arg] = function.expect_exact_arguments()?;
let pattern = expect_time_pattern(arg, context.now().to_owned())?;
Ok(RevsetExpression::filter(RevsetFilterPredicate::AuthorDate(
pattern,
)))
});
map.insert("mine", |function, context| {
function.expect_no_arguments()?;
Ok(RevsetExpression::filter(RevsetFilterPredicate::Author(
Expand All @@ -698,6 +711,13 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
pattern,
)))
});
map.insert("committer_date", |function, context| {
let [arg] = function.expect_exact_arguments()?;
let pattern = expect_time_pattern(arg, context.now().to_owned())?;
Ok(RevsetExpression::filter(
RevsetFilterPredicate::CommitterDate(pattern),
))
});
map.insert("empty", |function, _context| {
function.expect_no_arguments()?;
Ok(RevsetExpression::is_empty())
Expand Down Expand Up @@ -749,6 +769,24 @@ pub fn expect_string_pattern(node: &ExpressionNode) -> Result<StringPattern, Rev
revset_parser::expect_pattern_with("string pattern", node, parse_pattern)
}

fn expect_time_pattern<Tz: TimeZone>(
node: &ExpressionNode,
now: DateTime<Tz>,
) -> Result<TimePattern, RevsetParseError>
where
Tz::Offset: Copy,
{
revset_parser::expect_pattern_with("time expression", node, |value, kind| {
TimePattern::from_str_kind(value, kind, now).map_err(|err| {
RevsetParseError::expression(
format!("Unable to parse time expression: {err}"),
node.span,
)
.with_source(err)
})
})
}

/// Resolves function call by using the given function map.
fn lower_function_call(
function: &FunctionCallNode,
Expand Down Expand Up @@ -1977,20 +2015,29 @@ impl RevsetExtensions {
pub struct RevsetParseContext<'a> {
aliases_map: &'a RevsetAliasesMap,
user_email: String,
/// The current local time when the revset expression was written.
now: NaiveDateTime,
/// The offset from UTC at the time the revset expression was written. TODO:
/// It would be better if this was the TimeZone so times could be computed
/// correctly across DST shifts.
offset: FixedOffset,
extensions: &'a RevsetExtensions,
workspace: Option<RevsetWorkspaceContext<'a>>,
}

impl<'a> RevsetParseContext<'a> {
pub fn new(
pub fn new<Tz: TimeZone>(
aliases_map: &'a RevsetAliasesMap,
user_email: String,
now: DateTime<Tz>,
extensions: &'a RevsetExtensions,
workspace: Option<RevsetWorkspaceContext<'a>>,
) -> Self {
Self {
aliases_map,
user_email,
now: now.naive_local(),
offset: now.offset().fix(),
extensions,
workspace,
}
Expand All @@ -2004,6 +2051,10 @@ impl<'a> RevsetParseContext<'a> {
&self.user_email
}

pub fn now(&self) -> DateTime<FixedOffset> {
DateTime::from_naive_utc_and_offset(self.now, self.offset)
}

pub fn symbol_resolvers(&self) -> &[impl AsRef<dyn SymbolResolverExtension>] {
self.extensions.symbol_resolvers()
}
Expand Down Expand Up @@ -2047,6 +2098,7 @@ mod tests {
let context = RevsetParseContext::new(
&aliases_map,
"[email protected]".to_string(),
chrono::Utc::now(),
&extensions,
None,
);
Expand Down Expand Up @@ -2076,6 +2128,7 @@ mod tests {
let context = RevsetParseContext::new(
&aliases_map,
"[email protected]".to_string(),
chrono::Utc::now(),
&extensions,
Some(workspace_ctx),
);
Expand All @@ -2101,6 +2154,7 @@ mod tests {
let context = RevsetParseContext::new(
&aliases_map,
"[email protected]".to_string(),
chrono::Utc::now(),
&extensions,
None,
);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/time_pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! Provides a TimeExpression type that represents a range of time.
//! Provides a TimePattern type that represents a range of time.
use chrono::{DateTime, TimeZone};
use chrono_english::{parse_date_string, DateError, Dialect};
Expand Down
Loading

0 comments on commit 5e1276d

Please sign in to comment.