Skip to content

Commit

Permalink
revset: add subject() predicate that matches first line of descriptions
Browse files Browse the repository at this point in the history
It's generally useful, and we can get by without introducing weird special case
about newline terminator.

Closes #5227
yuja committed Jan 21, 2025
1 parent 3d7858d commit a3636d8
Showing 5 changed files with 64 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -107,6 +107,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* New `author_name`/`author_email`/`committer_name`/`committer_email(pattern)`
revset functions to match either name or email field explicitly.

* New `subject(pattern)` revset function that matches first line of commit
descriptions.

### Fixed bugs

* Fixed diff selection by external tools with `jj split`/`commit -i FILESETS`.
8 changes: 8 additions & 0 deletions docs/revsets.md
Original file line number Diff line number Diff line change
@@ -267,6 +267,14 @@ revsets (expressions) as arguments.
* `description(pattern)`: Commits that have a description matching the given
[string pattern](#string-patterns).

A non-empty description is usually terminated with newline character. For
example, `description(exact:"")` matches commits without description, and
`description(exact:"foo\n")` matches commits with description `"foo\n"`.

* `subject(pattern)`: Commits that have a subject matching the given [string
pattern](#string-patterns). A subject is the first line of the description
(without newline character.)

* `author(pattern)`: Commits with the author's name or email matching the given
[string pattern](#string-patterns). Equivalent to `author_name(pattern) |
author_email(pattern)`.
8 changes: 8 additions & 0 deletions lib/src/default_index/revset_engine.rs
Original file line number Diff line number Diff line change
@@ -1161,6 +1161,14 @@ fn build_predicate_fn(
Ok(pattern.matches(commit.description()))
})
}
RevsetFilterPredicate::Subject(pattern) => {
let pattern = pattern.clone();
box_pure_predicate_fn(move |index, pos| {
let entry = index.entry_by_pos(pos);
let commit = store.get_commit(&entry.commit_id())?;
Ok(pattern.matches(commit.description().lines().next().unwrap_or_default()))
})
}
RevsetFilterPredicate::AuthorName(pattern) => {
let pattern = pattern.clone();
box_pure_predicate_fn(move |index, pos| {
8 changes: 8 additions & 0 deletions lib/src/revset.rs
Original file line number Diff line number Diff line change
@@ -156,6 +156,8 @@ pub enum RevsetFilterPredicate {
ParentCount(Range<u32>),
/// Commits with description matching the pattern.
Description(StringPattern),
/// Commits with first line of the description matching the pattern.
Subject(StringPattern),
/// Commits with author name matching the pattern.
AuthorName(StringPattern),
/// Commits with author email matching the pattern.
@@ -834,6 +836,12 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
RevsetFilterPredicate::Description(pattern),
))
});
map.insert("subject", |diagnostics, function, _context| {
let [arg] = function.expect_exact_arguments()?;
let pattern = expect_string_pattern(diagnostics, arg)?;
let predicate = RevsetFilterPredicate::Subject(pattern);
Ok(RevsetExpression::filter(predicate))
});
map.insert("author", |diagnostics, function, _context| {
let [arg] = function.expect_exact_arguments()?;
let pattern = expect_string_pattern(diagnostics, arg)?;
40 changes: 37 additions & 3 deletions lib/tests/test_revset.rs
Original file line number Diff line number Diff line change
@@ -2654,17 +2654,17 @@ fn test_evaluate_expression_description() {
let mut_repo = tx.repo_mut();

let commit1 = create_random_commit(mut_repo)
.set_description("commit 1")
.set_description("commit 1\n")
.write()
.unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_description("commit 2")
.set_description("commit 2\n\nblah blah...\n")
.write()
.unwrap();
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_description("commit 3")
.set_description("commit 3\n")
.write()
.unwrap();

@@ -2687,6 +2687,40 @@ fn test_evaluate_expression_description() {
resolve_commit_ids(mut_repo, "visible_heads() & description(\"commit 2\")"),
vec![]
);

// Exact match
assert_eq!(
resolve_commit_ids(mut_repo, r#"description(exact:"commit 1\n")"#),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"description(exact:"commit 2\n")"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "description(exact:'')"),
vec![mut_repo.store().root_commit_id().clone()]
);

// Match subject line
assert_eq!(
resolve_commit_ids(mut_repo, "subject(glob:'commit ?')"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(resolve_commit_ids(mut_repo, "subject('blah')"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, "subject(exact:'commit 2')"),
vec![commit2.id().clone()]
);
// Empty description should have empty subject line
assert_eq!(
resolve_commit_ids(mut_repo, "subject(exact:'')"),
vec![mut_repo.store().root_commit_id().clone()]
);
}

#[test]

0 comments on commit a3636d8

Please sign in to comment.