forked from mirrors/jj
revset: pass separate repo to disambiguation index
The idea is that the disambiguation index can be loaded from a repo which is different from the symbol resolution context. Suppose we add at_operation(op, expr) revset, a symbol inside at_operation() expression will have to be resolved within that operation, whereas the disambiguation index is cached globally by WorkspaceCommandHelper. We could build temporary disambiguation index for each at-op repo, but that would be complicated implementation-wise, and wouldn't be useful. For example, a query "x | at_operation(@-, x)" might be resolved to "xy | at_operation(@-, xz)" if disambiguation index were reloaded for the @- operation. Instead, the short change ID "x" can be disambiguated to "xy", then resolved to the corresponding commit IDs at each operation.
This commit is contained in:
parent
3d41237efe
commit
3ff1f985f3
3 changed files with 121 additions and 25 deletions
|
@ -161,7 +161,13 @@ impl IdPrefixIndex<'_> {
|
||||||
.commit_index
|
.commit_index
|
||||||
.resolve_prefix_to_key(&*indexes.commit_change_ids, prefix);
|
.resolve_prefix_to_key(&*indexes.commit_change_ids, prefix);
|
||||||
if let PrefixResolution::SingleMatch(id) = resolution {
|
if let PrefixResolution::SingleMatch(id) = resolution {
|
||||||
return PrefixResolution::SingleMatch(id);
|
// The disambiguation set may be loaded from a different repo,
|
||||||
|
// and contain a commit that doesn't exist in the current repo.
|
||||||
|
if repo.index().has_id(&id) {
|
||||||
|
return PrefixResolution::SingleMatch(id);
|
||||||
|
} else {
|
||||||
|
return PrefixResolution::NoMatch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repo.index().resolve_commit_id_prefix(prefix)
|
repo.index().resolve_commit_id_prefix(prefix)
|
||||||
|
|
|
@ -1531,14 +1531,23 @@ fn make_no_such_symbol_error(repo: &dyn Repo, name: impl Into<String>) -> Revset
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SymbolResolver {
|
pub trait SymbolResolver {
|
||||||
fn resolve_symbol(&self, symbol: &str) -> Result<Vec<CommitId>, RevsetResolutionError>;
|
/// Looks up `symbol` in the given `repo`.
|
||||||
|
fn resolve_symbol(
|
||||||
|
&self,
|
||||||
|
repo: &dyn Repo,
|
||||||
|
symbol: &str,
|
||||||
|
) -> Result<Vec<CommitId>, RevsetResolutionError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fails on any attempt to resolve a symbol.
|
/// Fails on any attempt to resolve a symbol.
|
||||||
pub struct FailingSymbolResolver;
|
pub struct FailingSymbolResolver;
|
||||||
|
|
||||||
impl SymbolResolver for FailingSymbolResolver {
|
impl SymbolResolver for FailingSymbolResolver {
|
||||||
fn resolve_symbol(&self, symbol: &str) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
fn resolve_symbol(
|
||||||
|
&self,
|
||||||
|
_repo: &dyn Repo,
|
||||||
|
symbol: &str,
|
||||||
|
) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
||||||
Err(RevsetResolutionError::NoSuchRevision {
|
Err(RevsetResolutionError::NoSuchRevision {
|
||||||
name: format!(
|
name: format!(
|
||||||
"Won't resolve symbol {symbol:?}. When creating revsets programmatically, avoid \
|
"Won't resolve symbol {symbol:?}. When creating revsets programmatically, avoid \
|
||||||
|
@ -1613,8 +1622,8 @@ impl PartialSymbolResolver for GitRefResolver {
|
||||||
const DEFAULT_RESOLVERS: &[&'static dyn PartialSymbolResolver] =
|
const DEFAULT_RESOLVERS: &[&'static dyn PartialSymbolResolver] =
|
||||||
&[&TagResolver, &BookmarkResolver, &GitRefResolver];
|
&[&TagResolver, &BookmarkResolver, &GitRefResolver];
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct CommitPrefixResolver<'a> {
|
struct CommitPrefixResolver<'a> {
|
||||||
|
context_repo: &'a dyn Repo,
|
||||||
context: Option<&'a IdPrefixContext>,
|
context: Option<&'a IdPrefixContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1625,9 +1634,9 @@ impl PartialSymbolResolver for CommitPrefixResolver<'_> {
|
||||||
symbol: &str,
|
symbol: &str,
|
||||||
) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
|
) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
|
||||||
if let Some(prefix) = HexPrefix::new(symbol) {
|
if let Some(prefix) = HexPrefix::new(symbol) {
|
||||||
let index = self
|
let index = self.context.map_or(IdPrefixIndex::empty(), |ctx| {
|
||||||
.context
|
ctx.populate(self.context_repo)
|
||||||
.map_or(IdPrefixIndex::empty(), |ctx| ctx.populate(repo));
|
});
|
||||||
match index.resolve_commit_prefix(repo, &prefix) {
|
match index.resolve_commit_prefix(repo, &prefix) {
|
||||||
PrefixResolution::AmbiguousMatch => Err(
|
PrefixResolution::AmbiguousMatch => Err(
|
||||||
RevsetResolutionError::AmbiguousCommitIdPrefix(symbol.to_owned()),
|
RevsetResolutionError::AmbiguousCommitIdPrefix(symbol.to_owned()),
|
||||||
|
@ -1641,8 +1650,8 @@ impl PartialSymbolResolver for CommitPrefixResolver<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ChangePrefixResolver<'a> {
|
struct ChangePrefixResolver<'a> {
|
||||||
|
context_repo: &'a dyn Repo,
|
||||||
context: Option<&'a IdPrefixContext>,
|
context: Option<&'a IdPrefixContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1653,9 +1662,9 @@ impl PartialSymbolResolver for ChangePrefixResolver<'_> {
|
||||||
symbol: &str,
|
symbol: &str,
|
||||||
) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
|
) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
|
||||||
if let Some(prefix) = to_forward_hex(symbol).as_deref().and_then(HexPrefix::new) {
|
if let Some(prefix) = to_forward_hex(symbol).as_deref().and_then(HexPrefix::new) {
|
||||||
let index = self
|
let index = self.context.map_or(IdPrefixIndex::empty(), |ctx| {
|
||||||
.context
|
ctx.populate(self.context_repo)
|
||||||
.map_or(IdPrefixIndex::empty(), |ctx| ctx.populate(repo));
|
});
|
||||||
match index.resolve_change_prefix(repo, &prefix) {
|
match index.resolve_change_prefix(repo, &prefix) {
|
||||||
PrefixResolution::AmbiguousMatch => Err(
|
PrefixResolution::AmbiguousMatch => Err(
|
||||||
RevsetResolutionError::AmbiguousChangeIdPrefix(symbol.to_owned()),
|
RevsetResolutionError::AmbiguousChangeIdPrefix(symbol.to_owned()),
|
||||||
|
@ -1676,30 +1685,43 @@ impl PartialSymbolResolver for ChangePrefixResolver<'_> {
|
||||||
/// may provide a way for extensions to override native resolvers like tags and
|
/// may provide a way for extensions to override native resolvers like tags and
|
||||||
/// bookmarks.
|
/// bookmarks.
|
||||||
pub trait SymbolResolverExtension {
|
pub trait SymbolResolverExtension {
|
||||||
/// PartialSymbolResolvers can capture `repo` for caching purposes if
|
/// PartialSymbolResolvers can initialize some global data by using the
|
||||||
/// desired, but they do not have to since `repo` is passed into
|
/// `context_repo`, but the `context_repo` may point to a different
|
||||||
/// `resolve_symbol()` as well.
|
/// operation from the `repo` passed into `resolve_symbol()`. For
|
||||||
fn new_resolvers<'a>(&self, repo: &'a dyn Repo) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
|
/// resolution, the latter `repo` should be used.
|
||||||
|
fn new_resolvers<'a>(
|
||||||
|
&self,
|
||||||
|
context_repo: &'a dyn Repo,
|
||||||
|
) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves bookmarks, remote bookmarks, tags, git refs, and full and
|
/// Resolves bookmarks, remote bookmarks, tags, git refs, and full and
|
||||||
/// abbreviated commit and change ids.
|
/// abbreviated commit and change ids.
|
||||||
pub struct DefaultSymbolResolver<'a> {
|
pub struct DefaultSymbolResolver<'a> {
|
||||||
repo: &'a dyn Repo,
|
|
||||||
commit_id_resolver: CommitPrefixResolver<'a>,
|
commit_id_resolver: CommitPrefixResolver<'a>,
|
||||||
change_id_resolver: ChangePrefixResolver<'a>,
|
change_id_resolver: ChangePrefixResolver<'a>,
|
||||||
extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
|
extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DefaultSymbolResolver<'a> {
|
impl<'a> DefaultSymbolResolver<'a> {
|
||||||
pub fn new(repo: &'a dyn Repo, extensions: &[impl AsRef<dyn SymbolResolverExtension>]) -> Self {
|
/// Creates new symbol resolver that will first disambiguate short ID
|
||||||
|
/// prefixes within the given `context_repo` if configured.
|
||||||
|
pub fn new(
|
||||||
|
context_repo: &'a dyn Repo,
|
||||||
|
extensions: &[impl AsRef<dyn SymbolResolverExtension>],
|
||||||
|
) -> Self {
|
||||||
DefaultSymbolResolver {
|
DefaultSymbolResolver {
|
||||||
repo,
|
commit_id_resolver: CommitPrefixResolver {
|
||||||
commit_id_resolver: Default::default(),
|
context_repo,
|
||||||
change_id_resolver: Default::default(),
|
context: None,
|
||||||
|
},
|
||||||
|
change_id_resolver: ChangePrefixResolver {
|
||||||
|
context_repo,
|
||||||
|
context: None,
|
||||||
|
},
|
||||||
extensions: extensions
|
extensions: extensions
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|ext| ext.as_ref().new_resolvers(repo))
|
.flat_map(|ext| ext.as_ref().new_resolvers(context_repo))
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1722,18 +1744,22 @@ impl<'a> DefaultSymbolResolver<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SymbolResolver for DefaultSymbolResolver<'_> {
|
impl SymbolResolver for DefaultSymbolResolver<'_> {
|
||||||
fn resolve_symbol(&self, symbol: &str) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
fn resolve_symbol(
|
||||||
|
&self,
|
||||||
|
repo: &dyn Repo,
|
||||||
|
symbol: &str,
|
||||||
|
) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
||||||
if symbol.is_empty() {
|
if symbol.is_empty() {
|
||||||
return Err(RevsetResolutionError::EmptyString);
|
return Err(RevsetResolutionError::EmptyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
for partial_resolver in self.partial_resolvers() {
|
for partial_resolver in self.partial_resolvers() {
|
||||||
if let Some(ids) = partial_resolver.resolve_symbol(self.repo, symbol)? {
|
if let Some(ids) = partial_resolver.resolve_symbol(repo, symbol)? {
|
||||||
return Ok(ids);
|
return Ok(ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(make_no_such_symbol_error(self.repo, symbol))
|
Err(make_no_such_symbol_error(repo, symbol))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1743,7 +1769,7 @@ fn resolve_commit_ref(
|
||||||
symbol_resolver: &dyn SymbolResolver,
|
symbol_resolver: &dyn SymbolResolver,
|
||||||
) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
) -> Result<Vec<CommitId>, RevsetResolutionError> {
|
||||||
match commit_ref {
|
match commit_ref {
|
||||||
RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(symbol),
|
RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(repo, symbol),
|
||||||
RevsetCommitRef::RemoteSymbol { name, remote } => {
|
RevsetCommitRef::RemoteSymbol { name, remote } => {
|
||||||
resolve_remote_bookmark(repo, name, remote)
|
resolve_remote_bookmark(repo, name, remote)
|
||||||
.ok_or_else(|| make_no_such_symbol_error(repo, format!("{name}@{remote}")))
|
.ok_or_else(|| make_no_such_symbol_error(repo, format!("{name}@{remote}")))
|
||||||
|
|
|
@ -29,6 +29,8 @@ use jj_lib::git;
|
||||||
use jj_lib::git_backend::GitBackend;
|
use jj_lib::git_backend::GitBackend;
|
||||||
use jj_lib::graph::GraphEdge;
|
use jj_lib::graph::GraphEdge;
|
||||||
use jj_lib::graph::ReverseGraphIterator;
|
use jj_lib::graph::ReverseGraphIterator;
|
||||||
|
use jj_lib::hex_util::to_reverse_hex;
|
||||||
|
use jj_lib::id_prefix::IdPrefixContext;
|
||||||
use jj_lib::object_id::ObjectId;
|
use jj_lib::object_id::ObjectId;
|
||||||
use jj_lib::op_store::RefTarget;
|
use jj_lib::op_store::RefTarget;
|
||||||
use jj_lib::op_store::RemoteRef;
|
use jj_lib::op_store::RemoteRef;
|
||||||
|
@ -51,6 +53,7 @@ use jj_lib::revset::RevsetFilterPredicate;
|
||||||
use jj_lib::revset::RevsetParseContext;
|
use jj_lib::revset::RevsetParseContext;
|
||||||
use jj_lib::revset::RevsetResolutionError;
|
use jj_lib::revset::RevsetResolutionError;
|
||||||
use jj_lib::revset::RevsetWorkspaceContext;
|
use jj_lib::revset::RevsetWorkspaceContext;
|
||||||
|
use jj_lib::revset::SymbolResolver;
|
||||||
use jj_lib::revset::SymbolResolverExtension;
|
use jj_lib::revset::SymbolResolverExtension;
|
||||||
use jj_lib::settings::GitSettings;
|
use jj_lib::settings::GitSettings;
|
||||||
use jj_lib::workspace::Workspace;
|
use jj_lib::workspace::Workspace;
|
||||||
|
@ -369,6 +372,67 @@ fn test_resolve_symbol_change_id(readonly: bool) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_symbol_in_different_disambiguation_context() {
|
||||||
|
let settings = testutils::user_settings();
|
||||||
|
let test_repo = TestRepo::init();
|
||||||
|
let repo0 = &test_repo.repo;
|
||||||
|
|
||||||
|
let mut tx = repo0.start_transaction(&settings);
|
||||||
|
let commit1 = write_random_commit(tx.repo_mut(), &settings);
|
||||||
|
// Create more commits that are likely to conflict with 1-char hex prefix.
|
||||||
|
for _ in 0..50 {
|
||||||
|
write_random_commit(tx.repo_mut(), &settings);
|
||||||
|
}
|
||||||
|
let repo1 = tx.commit("test");
|
||||||
|
|
||||||
|
let mut tx = repo1.start_transaction(&settings);
|
||||||
|
let commit2 = tx
|
||||||
|
.repo_mut()
|
||||||
|
.rewrite_commit(&settings, &commit1)
|
||||||
|
.write()
|
||||||
|
.unwrap();
|
||||||
|
tx.repo_mut().rebase_descendants(&settings).unwrap();
|
||||||
|
let repo2 = tx.commit("test");
|
||||||
|
|
||||||
|
// Set up disambiguation index which only contains the commit2.id().
|
||||||
|
let id_prefix_context = IdPrefixContext::new(Default::default())
|
||||||
|
.disambiguate_within(RevsetExpression::commit(commit2.id().clone()));
|
||||||
|
let symbol_resolver =
|
||||||
|
DefaultSymbolResolver::new(repo2.as_ref(), &[] as &[Box<dyn SymbolResolverExtension>])
|
||||||
|
.with_id_prefix_context(&id_prefix_context);
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
let change_hex = &to_reverse_hex(&commit2.change_id().hex()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
symbol_resolver
|
||||||
|
.resolve_symbol(repo2.as_ref(), &change_hex[0..1])
|
||||||
|
.unwrap(),
|
||||||
|
vec![commit2.id().clone()]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
symbol_resolver
|
||||||
|
.resolve_symbol(repo2.as_ref(), &commit2.id().hex()[0..1])
|
||||||
|
.unwrap(),
|
||||||
|
vec![commit2.id().clone()]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Change ID is disambiguated within repo2, then resolved in repo1.
|
||||||
|
assert_eq!(
|
||||||
|
symbol_resolver
|
||||||
|
.resolve_symbol(repo1.as_ref(), &change_hex[0..1])
|
||||||
|
.unwrap(),
|
||||||
|
vec![commit1.id().clone()]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Commit ID can be found in the disambiguation index, but doesn't exist in
|
||||||
|
// repo1.
|
||||||
|
assert_matches!(
|
||||||
|
symbol_resolver.resolve_symbol(repo1.as_ref(), &commit2.id().hex()[0..1]),
|
||||||
|
Err(RevsetResolutionError::NoSuchRevision { .. })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_working_copy() {
|
fn test_resolve_working_copy() {
|
||||||
let settings = testutils::user_settings();
|
let settings = testutils::user_settings();
|
||||||
|
|
Loading…
Reference in a new issue