mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-25 05:29:39 +00:00
revsets: add author()
and committer()
functions (#46)
Filtering by the author or committer is quite common.
This commit is contained in:
parent
7d3d0fe83c
commit
277f42d98a
3 changed files with 216 additions and 2 deletions
|
@ -105,6 +105,12 @@ revsets (expressions) as arguments.
|
|||
* `description(needle[, x])`: Commits with the given string in their
|
||||
description. If a second argument was provided, then only commits in that set
|
||||
are considered, otherwise all visible commits are considered.
|
||||
* `author(needle[, x])`: Commits with the given string in the author's name or
|
||||
email. If a second argument was provided, then only commits in that set
|
||||
are considered, otherwise all visible commits are considered.
|
||||
* `committer(needle[, x])`: Commits with the given string in the committer's
|
||||
name or email. If a second argument was provided, then only commits in that
|
||||
set are considered, otherwise all visible commits are considered.
|
||||
|
||||
|
||||
## Examples
|
||||
|
@ -139,3 +145,9 @@ those commits:
|
|||
```
|
||||
jj log -r '(remote_branches()..@):'
|
||||
```
|
||||
|
||||
Show commits authored by "martinvonz" and containing the word "reset" in the
|
||||
description:
|
||||
```
|
||||
jj log -r 'author(martinvonz) & description(reset)'
|
||||
```
|
||||
|
|
|
@ -211,6 +211,16 @@ pub enum RevsetExpression {
|
|||
needle: String,
|
||||
candidates: Rc<RevsetExpression>,
|
||||
},
|
||||
Author {
|
||||
// Matches against both name and email
|
||||
needle: String,
|
||||
candidates: Rc<RevsetExpression>,
|
||||
},
|
||||
Committer {
|
||||
// Matches against both name and email
|
||||
needle: String,
|
||||
candidates: Rc<RevsetExpression>,
|
||||
},
|
||||
Union(Rc<RevsetExpression>, Rc<RevsetExpression>),
|
||||
Intersection(Rc<RevsetExpression>, Rc<RevsetExpression>),
|
||||
Difference(Rc<RevsetExpression>, Rc<RevsetExpression>),
|
||||
|
@ -334,6 +344,22 @@ impl RevsetExpression {
|
|||
})
|
||||
}
|
||||
|
||||
/// Commits in `self` with author's name or email containing `needle`.
|
||||
pub fn with_author(self: &Rc<RevsetExpression>, needle: String) -> Rc<RevsetExpression> {
|
||||
Rc::new(RevsetExpression::Author {
|
||||
candidates: self.clone(),
|
||||
needle,
|
||||
})
|
||||
}
|
||||
|
||||
/// Commits in `self` with committer's name or email containing `needle`.
|
||||
pub fn with_committer(self: &Rc<RevsetExpression>, needle: String) -> Rc<RevsetExpression> {
|
||||
Rc::new(RevsetExpression::Committer {
|
||||
candidates: self.clone(),
|
||||
needle,
|
||||
})
|
||||
}
|
||||
|
||||
/// Commits that are in `self` or in `other` (or both).
|
||||
pub fn union(
|
||||
self: &Rc<RevsetExpression>,
|
||||
|
@ -665,7 +691,7 @@ fn parse_function_expression(
|
|||
};
|
||||
Ok(candidates.with_parent_count(2..u32::MAX))
|
||||
}
|
||||
"description" => {
|
||||
"description" | "author" | "committer" => {
|
||||
if !(1..=2).contains(&arg_count) {
|
||||
return Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name,
|
||||
|
@ -681,7 +707,14 @@ fn parse_function_expression(
|
|||
} else {
|
||||
parse_expression_rule(argument_pairs.next().unwrap().into_inner())?
|
||||
};
|
||||
Ok(candidates.with_description(needle))
|
||||
match name.as_str() {
|
||||
"description" => Ok(candidates.with_description(needle)),
|
||||
"author" => Ok(candidates.with_author(needle)),
|
||||
"committer" => Ok(candidates.with_committer(needle)),
|
||||
_ => {
|
||||
panic!("unexpected function name: {}", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(RevsetParseError::NoSuchFunction(name)),
|
||||
}
|
||||
|
@ -1182,6 +1215,35 @@ pub fn evaluate_expression<'repo>(
|
|||
}),
|
||||
}))
|
||||
}
|
||||
RevsetExpression::Author { needle, candidates } => {
|
||||
let candidates = candidates.evaluate(repo)?;
|
||||
let repo = repo;
|
||||
let needle = needle.clone();
|
||||
// TODO: Make these functions that take a needle to search for accept some
|
||||
// syntax for specifying whether it's a regex and whether it's
|
||||
// case-sensitive.
|
||||
Ok(Box::new(FilterRevset {
|
||||
candidates,
|
||||
predicate: Box::new(move |entry| {
|
||||
let commit = repo.store().get_commit(&entry.commit_id()).unwrap();
|
||||
commit.author().name.contains(needle.as_str())
|
||||
|| commit.author().email.contains(needle.as_str())
|
||||
}),
|
||||
}))
|
||||
}
|
||||
RevsetExpression::Committer { needle, candidates } => {
|
||||
let candidates = candidates.evaluate(repo)?;
|
||||
let repo = repo;
|
||||
let needle = needle.clone();
|
||||
Ok(Box::new(FilterRevset {
|
||||
candidates,
|
||||
predicate: Box::new(move |entry| {
|
||||
let commit = repo.store().get_commit(&entry.commit_id()).unwrap();
|
||||
commit.committer().name.contains(needle.as_str())
|
||||
|| commit.committer().email.contains(needle.as_str())
|
||||
}),
|
||||
}))
|
||||
}
|
||||
RevsetExpression::Union(expression1, expression2) => {
|
||||
let set1 = expression1.evaluate(repo)?;
|
||||
let set2 = expression2.evaluate(repo)?;
|
||||
|
@ -1292,6 +1354,20 @@ mod tests {
|
|||
needle: "needle".to_string()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
foo_symbol.with_author("needle".to_string()),
|
||||
Rc::new(RevsetExpression::Author {
|
||||
candidates: foo_symbol.clone(),
|
||||
needle: "needle".to_string()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
foo_symbol.with_committer("needle".to_string()),
|
||||
Rc::new(RevsetExpression::Committer {
|
||||
candidates: foo_symbol.clone(),
|
||||
needle: "needle".to_string()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
foo_symbol.union(&checkout_symbol),
|
||||
Rc::new(RevsetExpression::Union(
|
||||
|
|
|
@ -1227,6 +1227,132 @@ fn test_evaluate_expression_description(use_git: bool) {
|
|||
);
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_evaluate_expression_author(use_git: bool) {
|
||||
let settings = testutils::user_settings();
|
||||
let test_workspace = testutils::init_repo(&settings, use_git);
|
||||
let repo = &test_workspace.repo;
|
||||
|
||||
let mut tx = repo.start_transaction("test");
|
||||
let mut_repo = tx.mut_repo();
|
||||
|
||||
let timestamp = Timestamp {
|
||||
timestamp: MillisSinceEpoch(0),
|
||||
tz_offset: 0,
|
||||
};
|
||||
let commit1 = testutils::create_random_commit(&settings, repo)
|
||||
.set_author(Signature {
|
||||
name: "name1".to_string(),
|
||||
email: "email1".to_string(),
|
||||
timestamp: timestamp.clone(),
|
||||
})
|
||||
.write_to_repo(mut_repo);
|
||||
let commit2 = testutils::create_random_commit(&settings, repo)
|
||||
.set_parents(vec![commit1.id().clone()])
|
||||
.set_author(Signature {
|
||||
name: "name2".to_string(),
|
||||
email: "email2".to_string(),
|
||||
timestamp: timestamp.clone(),
|
||||
})
|
||||
.write_to_repo(mut_repo);
|
||||
let commit3 = testutils::create_random_commit(&settings, repo)
|
||||
.set_parents(vec![commit2.id().clone()])
|
||||
.set_author(Signature {
|
||||
name: "name3".to_string(),
|
||||
email: "email3".to_string(),
|
||||
timestamp,
|
||||
})
|
||||
.write_to_repo(mut_repo);
|
||||
|
||||
// Can find multiple matches
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "author(name)"),
|
||||
vec![
|
||||
commit3.id().clone(),
|
||||
commit2.id().clone(),
|
||||
commit1.id().clone()
|
||||
]
|
||||
);
|
||||
// Can find a unique match by either name or email
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "author(\"name2\")"),
|
||||
vec![commit2.id().clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "author(\"name3\")"),
|
||||
vec![commit3.id().clone()]
|
||||
);
|
||||
// Searches only among candidates if specified
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "author(\"name2\",heads())"),
|
||||
vec![]
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_evaluate_expression_committer(use_git: bool) {
|
||||
let settings = testutils::user_settings();
|
||||
let test_workspace = testutils::init_repo(&settings, use_git);
|
||||
let repo = &test_workspace.repo;
|
||||
|
||||
let mut tx = repo.start_transaction("test");
|
||||
let mut_repo = tx.mut_repo();
|
||||
|
||||
let timestamp = Timestamp {
|
||||
timestamp: MillisSinceEpoch(0),
|
||||
tz_offset: 0,
|
||||
};
|
||||
let commit1 = testutils::create_random_commit(&settings, repo)
|
||||
.set_committer(Signature {
|
||||
name: "name1".to_string(),
|
||||
email: "email1".to_string(),
|
||||
timestamp: timestamp.clone(),
|
||||
})
|
||||
.write_to_repo(mut_repo);
|
||||
let commit2 = testutils::create_random_commit(&settings, repo)
|
||||
.set_parents(vec![commit1.id().clone()])
|
||||
.set_committer(Signature {
|
||||
name: "name2".to_string(),
|
||||
email: "email2".to_string(),
|
||||
timestamp: timestamp.clone(),
|
||||
})
|
||||
.write_to_repo(mut_repo);
|
||||
let commit3 = testutils::create_random_commit(&settings, repo)
|
||||
.set_parents(vec![commit2.id().clone()])
|
||||
.set_committer(Signature {
|
||||
name: "name3".to_string(),
|
||||
email: "email3".to_string(),
|
||||
timestamp,
|
||||
})
|
||||
.write_to_repo(mut_repo);
|
||||
|
||||
// Can find multiple matches
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "committer(name)"),
|
||||
vec![
|
||||
commit3.id().clone(),
|
||||
commit2.id().clone(),
|
||||
commit1.id().clone()
|
||||
]
|
||||
);
|
||||
// Can find a unique match by either name or email
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "committer(\"name2\")"),
|
||||
vec![commit2.id().clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "committer(\"name3\")"),
|
||||
vec![commit3.id().clone()]
|
||||
);
|
||||
// Searches only among candidates if specified
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "committer(\"name2\",heads())"),
|
||||
vec![]
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_evaluate_expression_union(use_git: bool) {
|
||||
|
|
Loading…
Reference in a new issue