mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-10 01:42:49 +00:00
revsets: add a non_obsolete_heads() revset
This change adds a `non_obsolete_heads(<set>)` revset, which walks up ancestors of the input set until it gets to a non-obsolete and non-pruned commit. That's what we do by default in `jj log` (i.e. without `--all`). Now we can make `jj log` use revsets and teach it a `-r` option!
This commit is contained in:
parent
dbfa267d60
commit
05e9149157
@ -18,6 +18,7 @@ use std::cmp::{max, min, Ordering};
|
|||||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet};
|
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet};
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::{Cursor, Read, Write};
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
@ -1327,6 +1328,12 @@ impl PartialEq for IndexEntry<'_> {
|
|||||||
}
|
}
|
||||||
impl Eq for IndexEntry<'_> {}
|
impl Eq for IndexEntry<'_> {}
|
||||||
|
|
||||||
|
impl Hash for IndexEntry<'_> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.pos.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> IndexEntry<'a> {
|
impl<'a> IndexEntry<'a> {
|
||||||
pub fn position(&self) -> u32 {
|
pub fn position(&self) -> u32 {
|
||||||
self.pos
|
self.pos
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use pest::iterators::Pairs;
|
use pest::iterators::Pairs;
|
||||||
use pest::Parser;
|
use pest::Parser;
|
||||||
@ -100,6 +101,7 @@ pub enum RevsetExpression {
|
|||||||
Parents(Box<RevsetExpression>),
|
Parents(Box<RevsetExpression>),
|
||||||
Ancestors(Box<RevsetExpression>),
|
Ancestors(Box<RevsetExpression>),
|
||||||
AllHeads,
|
AllHeads,
|
||||||
|
NonObsoleteHeads(Box<RevsetExpression>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_expression_rule(mut pairs: Pairs<Rule>) -> Result<RevsetExpression, RevsetParseError> {
|
fn parse_expression_rule(mut pairs: Pairs<Rule>) -> Result<RevsetExpression, RevsetParseError> {
|
||||||
@ -175,6 +177,25 @@ fn parse_function_expression(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"non_obsolete_heads" => {
|
||||||
|
if arg_count == 0 {
|
||||||
|
Ok(RevsetExpression::NonObsoleteHeads(Box::new(
|
||||||
|
RevsetExpression::AllHeads,
|
||||||
|
)))
|
||||||
|
} else if arg_count == 1 {
|
||||||
|
Ok(RevsetExpression::NonObsoleteHeads(Box::new(
|
||||||
|
parse_function_argument_to_expression(
|
||||||
|
&name,
|
||||||
|
argument_pairs.next().unwrap().into_inner(),
|
||||||
|
)?,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(RevsetParseError::InvalidFunctionArguments {
|
||||||
|
name,
|
||||||
|
message: "Expected 0 or 1 argument".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => Err(RevsetParseError::NoSuchFunction(name)),
|
_ => Err(RevsetParseError::NoSuchFunction(name)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,5 +311,41 @@ pub fn evaluate_expression<'repo>(
|
|||||||
index_entries.sort_by_key(|b| Reverse(b.position()));
|
index_entries.sort_by_key(|b| Reverse(b.position()));
|
||||||
Ok(Box::new(EagerRevset { index_entries }))
|
Ok(Box::new(EagerRevset { index_entries }))
|
||||||
}
|
}
|
||||||
|
RevsetExpression::NonObsoleteHeads(base_expression) => {
|
||||||
|
let base_set = evaluate_expression(repo, base_expression.as_ref())?;
|
||||||
|
Ok(non_obsolete_heads(repo, base_set))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn non_obsolete_heads<'repo>(
|
||||||
|
repo: RepoRef<'repo>,
|
||||||
|
heads: Box<dyn Revset<'repo> + 'repo>,
|
||||||
|
) -> Box<dyn Revset<'repo> + 'repo> {
|
||||||
|
let mut commit_ids = HashSet::new();
|
||||||
|
let mut work: Vec<_> = heads.iter().collect();
|
||||||
|
let evolution = repo.evolution();
|
||||||
|
while !work.is_empty() {
|
||||||
|
let index_entry = work.pop().unwrap();
|
||||||
|
let commit_id = index_entry.commit_id();
|
||||||
|
if commit_ids.contains(&commit_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !index_entry.is_pruned() && !evolution.is_obsolete(&commit_id) {
|
||||||
|
commit_ids.insert(commit_id);
|
||||||
|
} else {
|
||||||
|
for parent_entry in index_entry.parents() {
|
||||||
|
work.push(parent_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let index = repo.index();
|
||||||
|
let commit_ids = index.heads(&commit_ids);
|
||||||
|
let mut index_entries: Vec<_> = commit_ids
|
||||||
|
.iter()
|
||||||
|
.map(|id| index.entry_by_id(id).unwrap())
|
||||||
|
.collect();
|
||||||
|
index_entries.sort_by_key(|b| Reverse(b.position()));
|
||||||
|
index_entries.sort_by_key(|b| Reverse(b.position()));
|
||||||
|
Box::new(EagerRevset { index_entries })
|
||||||
|
}
|
||||||
|
@ -422,3 +422,50 @@ fn test_evaluate_expression_all_heads(use_git: bool) {
|
|||||||
|
|
||||||
tx.discard();
|
tx.discard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(false ; "local store")]
|
||||||
|
#[test_case(true ; "git store")]
|
||||||
|
fn test_evaluate_expression_obsolete(use_git: bool) {
|
||||||
|
let settings = testutils::user_settings();
|
||||||
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
||||||
|
|
||||||
|
let mut tx = repo.start_transaction("test");
|
||||||
|
let mut_repo = tx.mut_repo();
|
||||||
|
|
||||||
|
let root_commit = repo.store().root_commit();
|
||||||
|
let wc_commit = repo.working_copy_locked().current_commit();
|
||||||
|
let commit1 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo);
|
||||||
|
let commit2 = testutils::create_random_commit(&settings, &repo)
|
||||||
|
.set_predecessors(vec![commit1.id().clone()])
|
||||||
|
.set_change_id(commit1.change_id().clone())
|
||||||
|
.write_to_repo(mut_repo);
|
||||||
|
let commit3 = testutils::create_random_commit(&settings, &repo)
|
||||||
|
.set_predecessors(vec![commit2.id().clone()])
|
||||||
|
.set_change_id(commit2.change_id().clone())
|
||||||
|
.write_to_repo(mut_repo);
|
||||||
|
let commit4 = testutils::create_random_commit(&settings, &repo)
|
||||||
|
.set_parents(vec![commit3.id().clone()])
|
||||||
|
.set_pruned(true)
|
||||||
|
.write_to_repo(mut_repo);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(mut_repo.as_repo_ref(), "non_obsolete_heads()"),
|
||||||
|
vec![commit3.id().clone(), wc_commit.id().clone()]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(
|
||||||
|
mut_repo.as_repo_ref(),
|
||||||
|
&format!("non_obsolete_heads({})", commit4.id().hex())
|
||||||
|
),
|
||||||
|
vec![commit3.id().clone()]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(
|
||||||
|
mut_repo.as_repo_ref(),
|
||||||
|
&format!("non_obsolete_heads({})", commit1.id().hex())
|
||||||
|
),
|
||||||
|
vec![root_commit.id().clone()]
|
||||||
|
);
|
||||||
|
|
||||||
|
tx.discard();
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user