revset: add signed function

This commit is contained in:
Nils Koch 2025-04-09 14:55:08 -07:00
parent 1e94ee263b
commit fbaa51b4ce
5 changed files with 68 additions and 0 deletions

View File

@ -36,6 +36,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* Added `ui.bookmark-list-sort-keys` setting to configure default sort keys for the
`jj bookmark list` command.
* New `signed` revset function to filter for cryptographically signed commits.
### Fixed bugs
### Packaging changes

View File

@ -304,6 +304,8 @@ revsets (expressions) as arguments.
* `committer_date(pattern)`: Commits with committer dates matching the specified
[date pattern](#date-patterns).
* `signed()`: Commits that are cryptographically signed.
* `empty()`: Commits modifying no files. This also includes `merges()` without
user modifications and `root()`.

View File

@ -1248,6 +1248,11 @@ fn build_predicate_fn(
let commit = store.get_commit(&entry.commit_id())?;
Ok(commit.has_conflict()?)
}),
RevsetFilterPredicate::Signed => box_pure_predicate_fn(move |index, pos| {
let entry = index.entry_by_pos(pos);
let commit = store.get_commit(&entry.commit_id())?;
Ok(commit.is_signed())
}),
RevsetFilterPredicate::Extension(ext) => {
let ext = ext.clone();
box_pure_predicate_fn(move |index, pos| {

View File

@ -181,6 +181,8 @@ pub enum RevsetFilterPredicate {
},
/// Commits with conflicts
HasConflict,
/// Commits that are cryptographically signed.
Signed,
/// Custom predicates provided by extensions
Extension(Rc<dyn RevsetFilterExtension>),
}
@ -831,6 +833,11 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
pattern,
)))
});
map.insert("signed", |_diagnostics, function, _context| {
function.expect_no_arguments()?;
let predicate = RevsetFilterPredicate::Signed;
Ok(RevsetExpression::filter(predicate))
});
map.insert("mine", |_diagnostics, function, context| {
function.expect_no_arguments()?;
// Email address domains are inherently caseinsensitive, and the localparts
@ -3246,6 +3253,7 @@ mod tests {
),
)
"#);
insta::assert_debug_snapshot!(parse("signed()").unwrap(), @"Filter(Signed)");
}
#[test]

View File

@ -56,6 +56,9 @@ use jj_lib::revset::RevsetResolutionError;
use jj_lib::revset::RevsetWorkspaceContext;
use jj_lib::revset::SymbolResolver as _;
use jj_lib::revset::SymbolResolverExtension;
use jj_lib::signing::SignBehavior;
use jj_lib::signing::Signer;
use jj_lib::test_signing_backend::TestSigningBackend;
use jj_lib::workspace::Workspace;
use test_case::test_case;
use testutils::create_random_commit;
@ -3049,6 +3052,54 @@ fn test_evaluate_expression_mine() {
);
}
#[test]
fn test_evaluate_expression_signed() {
let signer = Signer::new(Some(Box::new(TestSigningBackend)), vec![]);
let settings = testutils::user_settings();
let test_workspace =
TestWorkspace::init_with_backend_and_signer(TestRepoBackend::Test, signer, &settings);
let repo = &test_workspace.repo;
let repo = repo.clone();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let timestamp = Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
};
let commit1 = create_random_commit(mut_repo)
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp,
})
.set_sign_behavior(SignBehavior::Own)
.write()
.unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_committer(Signature {
name: "name2".to_string(),
email: "email2".to_string(),
timestamp,
})
.set_sign_behavior(SignBehavior::Drop)
.write()
.unwrap();
assert!(commit1.is_signed());
assert!(!commit2.is_signed());
let signed_commits = resolve_commit_ids(mut_repo, "signed()");
assert!(signed_commits.contains(commit1.id()));
assert!(!signed_commits.contains(commit2.id()));
let unsigned_commits = resolve_commit_ids(mut_repo, "~signed()");
assert!(!unsigned_commits.contains(commit1.id()));
assert!(unsigned_commits.contains(commit2.id()));
}
#[test]
fn test_evaluate_expression_committer() {
let test_repo = TestRepo::init();