revset: for short hash, look up both commit and change ids to disambiguate

Because the use of the change id is recommended, any operation should abort
if a valid change id happens to match a commit id. We still try the commit
id lookup first as the change id lookup is more costly.

Ambiguous change/commit id is reported as AmbiguousCommitIdPrefix for now.
Maybe we can merge AmbiguousCommit/ChangeIdPrefix errors into one?

Closes #799
This commit is contained in:
Yuya Nishihara 2022-11-27 18:39:35 +09:00
parent 1fa0392a3e
commit c5ed3e1477
3 changed files with 52 additions and 22 deletions

View file

@ -46,8 +46,7 @@ Jujutsu attempts to resolve a symbol in the following order:
3. Tag name
4. Branch name
5. Git ref
6. Commit ID
7. Change ID
6. Commit ID or change ID
## Operators

View file

@ -83,30 +83,37 @@ fn resolve_branch(repo: RepoRef, symbol: &str) -> Option<Vec<CommitId>> {
None
}
fn resolve_commit_id(repo: RepoRef, symbol: &str) -> Result<Option<Vec<CommitId>>, RevsetError> {
// First check if it's a full commit id.
fn resolve_full_commit_id(
repo: RepoRef,
symbol: &str,
) -> Result<Option<Vec<CommitId>>, RevsetError> {
if let Ok(binary_commit_id) = hex::decode(symbol) {
let commit_id = CommitId::new(binary_commit_id);
match repo.store().get_commit(&commit_id) {
Ok(_) => return Ok(Some(vec![commit_id])),
Err(BackendError::NotFound) => {} // fall through
Err(err) => return Err(RevsetError::StoreError(err)),
Ok(_) => Ok(Some(vec![commit_id])),
Err(BackendError::NotFound) => Ok(None),
Err(err) => Err(RevsetError::StoreError(err)),
}
} else {
Ok(None)
}
}
fn resolve_short_commit_id(
repo: RepoRef,
symbol: &str,
) -> Result<Option<Vec<CommitId>>, RevsetError> {
if let Some(prefix) = HexPrefix::new(symbol.to_owned()) {
match repo.index().resolve_prefix(&prefix) {
PrefixResolution::NoMatch => {
return Ok(None);
}
PrefixResolution::NoMatch => Ok(None),
PrefixResolution::AmbiguousMatch => {
return Err(RevsetError::AmbiguousCommitIdPrefix(symbol.to_owned()));
Err(RevsetError::AmbiguousCommitIdPrefix(symbol.to_owned()))
}
PrefixResolution::SingleMatch(commit_id) => return Ok(Some(vec![commit_id])),
PrefixResolution::SingleMatch(commit_id) => Ok(Some(vec![commit_id])),
}
} else {
Ok(None)
}
Ok(None)
}
fn resolve_change_id(
@ -177,17 +184,24 @@ pub fn resolve_symbol(
return Ok(ids);
}
// Try to resolve as a commit id.
if let Some(ids) = resolve_commit_id(repo, symbol)? {
// Try to resolve as a full commit id. We assume a full commit id is unambiguous
// even if it's shorter than change id.
if let Some(ids) = resolve_full_commit_id(repo, symbol)? {
return Ok(ids);
}
// Try to resolve as a change id.
if let Some(ids) = resolve_change_id(repo, symbol)? {
return Ok(ids);
// Try to resolve as a commit/change id.
match (
resolve_short_commit_id(repo, symbol)?,
resolve_change_id(repo, symbol)?,
) {
// Likely a root_commit_id, but not limited to it.
(Some(ids1), Some(ids2)) if ids1 == ids2 => Ok(ids1),
// TODO: maybe unify Ambiguous*IdPrefix error variants?
(Some(_), Some(_)) => Err(RevsetError::AmbiguousCommitIdPrefix(symbol.to_owned())),
(Some(ids), None) | (None, Some(ids)) => Ok(ids),
(None, None) => Err(RevsetError::NoSuchRevision(symbol.to_owned())),
}
Err(RevsetError::NoSuchRevision(symbol.to_owned()))
}
}

View file

@ -171,7 +171,7 @@ fn test_resolve_symbol_change_id() {
.unwrap();
let git_tree = git_repo.find_tree(empty_tree_id).unwrap();
let mut git_commit_ids = vec![];
for i in &[133, 664, 840] {
for i in &[133, 664, 840, 5085] {
let git_commit_id = git_repo
.commit(
Some(&format!("refs/heads/branch{}", i)),
@ -205,6 +205,11 @@ fn test_resolve_symbol_change_id() {
// "04e1c7082e4e34f3f371d8a1a46770b861b9b547" reversed
"e2ad9d861d0ee625851b8ecfcf2c727410e38720"
);
assert_eq!(
hex::encode(git_commit_ids[3]),
// "911d7e52fd5ba04b8f289e14c3d30b52d38c0020" reversed
"040031cb4ad0cbc3287914f1d205dabf4a7eb889"
);
// Test lookup by full change id
let repo_ref = repo.as_repo_ref();
@ -254,6 +259,18 @@ fn test_resolve_symbol_change_id() {
Err(RevsetError::NoSuchRevision("04e13".to_string()))
);
// Test commit/changed id conflicts.
assert_eq!(
resolve_symbol(repo_ref, "040b", None),
Ok(vec![CommitId::from_hex(
"5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"
)])
);
assert_eq!(
resolve_symbol(repo_ref, "040", None),
Err(RevsetError::AmbiguousCommitIdPrefix("040".to_string()))
);
// Test non-hex string
assert_eq!(
resolve_symbol(repo_ref, "foo", None),