Skip to content

Commit

Permalink
revsets: resolve symbol as change id if nothing else matches
Browse files Browse the repository at this point in the history
This patch makes it so we attempt to resolve a symbol as the
non-obsolete commits in a change id if all other resolutions
fail.

This addresses issue libfuse#15. I decided to not require any operator for
looking up by change id. I want to make it as easy as possible to use
change ids instead of commit ids to see how well it works to interact
mostly with change ids instead of commit ids (I'll try to test that by
using it myself).
  • Loading branch information
martinvonz committed May 31, 2021
1 parent ca1df00 commit f6a9523
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 3 deletions.
54 changes: 53 additions & 1 deletion lib/src/evolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::sync::Arc;
use crate::commit::Commit;
use crate::commit_builder::CommitBuilder;
use crate::dag_walk::{bfs, closest_common_node, leaves};
use crate::index::IndexPosition;
use crate::index::{HexPrefix, IndexPosition, PrefixResolution};
use crate::repo::{MutableRepo, ReadonlyRepo, RepoRef};
use crate::repo_path::RepoPath;
use crate::rewrite::{merge_commit_trees, rebase_commit};
Expand Down Expand Up @@ -139,6 +139,28 @@ impl State {
.map_or(false, |non_obsoletes| non_obsoletes.len() > 1)
}

// TODO: We should probably add a change id table to the commit index and move
// this there
fn resolve_change_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<ChangeId> {
let mut result = PrefixResolution::NoMatch;
for change_id in self.non_obsoletes_by_changeid.keys() {
if change_id.hex().starts_with(prefix.hex()) {
if result != PrefixResolution::NoMatch {
return PrefixResolution::AmbiguousMatch;
}
result = PrefixResolution::SingleMatch(change_id.clone());
}
}
result
}

fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
self.non_obsoletes_by_changeid
.get(change_id)
.cloned()
.unwrap_or_else(HashSet::new)
}

fn add_commit(&mut self, commit: &Commit) {
self.add_commit_data(
commit.id(),
Expand Down Expand Up @@ -339,13 +361,27 @@ impl EvolutionRef<'_> {
}
}

pub fn resolve_change_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<ChangeId> {
match self {
EvolutionRef::Readonly(evolution) => evolution.resolve_change_id_prefix(prefix),
EvolutionRef::Mutable(evolution) => evolution.resolve_change_id_prefix(prefix),
}
}

pub fn is_divergent(&self, change_id: &ChangeId) -> bool {
match self {
EvolutionRef::Readonly(evolution) => evolution.is_divergent(change_id),
EvolutionRef::Mutable(evolution) => evolution.is_divergent(change_id),
}
}

pub fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
match self {
EvolutionRef::Readonly(evolution) => evolution.non_obsoletes(change_id),
EvolutionRef::Mutable(evolution) => evolution.non_obsoletes(change_id),
}
}

/// Given a current parent, finds the new parent candidates. If the current
/// parent is not obsolete, then a singleton set of that commit will be
/// returned.
Expand Down Expand Up @@ -402,6 +438,14 @@ impl ReadonlyEvolution {
self.state.is_divergent(change_id)
}

pub fn resolve_change_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<ChangeId> {
self.state.resolve_change_id_prefix(prefix)
}

pub fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
self.state.non_obsoletes(change_id)
}

pub fn new_parent(&self, repo: RepoRef, old_parent_id: &CommitId) -> HashSet<CommitId> {
self.state.new_parent(repo, old_parent_id)
}
Expand Down Expand Up @@ -434,6 +478,14 @@ impl MutableEvolution {
self.state.is_divergent(change_id)
}

pub fn resolve_change_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<ChangeId> {
self.state.resolve_change_id_prefix(prefix)
}

pub fn non_obsoletes(&self, change_id: &ChangeId) -> HashSet<CommitId> {
self.state.non_obsoletes(change_id)
}

pub fn new_parent(&self, repo: RepoRef, old_parent_id: &CommitId) -> HashSet<CommitId> {
self.state.new_parent(repo, old_parent_id)
}
Expand Down
31 changes: 30 additions & 1 deletion lib/src/revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub enum RevsetError {
NoSuchRevision(String),
#[error("Commit id prefix \"{0}\" is ambiguous")]
AmbiguousCommitIdPrefix(String),
#[error("Change id prefix \"{0}\" is ambiguous")]
AmbiguousChangeIdPrefix(String),
#[error("Unexpected error from store: {0}")]
StoreError(#[from] StoreError),
}
Expand Down Expand Up @@ -74,8 +76,29 @@ fn resolve_commit_id(repo: RepoRef, symbol: &str) -> Result<Vec<CommitId>, Revse
Err(RevsetError::NoSuchRevision(symbol.to_owned()))
}

fn resolve_non_obsolete_change_id(
repo: RepoRef,
change_id_prefix: &str,
) -> Result<Vec<CommitId>, RevsetError> {
if let Some(hex_prefix) = HexPrefix::new(change_id_prefix.to_owned()) {
let evolution = repo.evolution();
match evolution.resolve_change_id_prefix(&hex_prefix) {
PrefixResolution::NoMatch => {
Err(RevsetError::NoSuchRevision(change_id_prefix.to_owned()))
}
PrefixResolution::AmbiguousMatch => Err(RevsetError::AmbiguousChangeIdPrefix(
change_id_prefix.to_owned(),
)),
PrefixResolution::SingleMatch(change_id) => {
Ok(evolution.non_obsoletes(&change_id).into_iter().collect())
}
}
} else {
Err(RevsetError::NoSuchRevision(change_id_prefix.to_owned()))
}
}

pub fn resolve_symbol(repo: RepoRef, symbol: &str) -> Result<Vec<CommitId>, RevsetError> {
// TODO: Support change ids.
if symbol == "@" {
Ok(vec![repo.view().checkout().clone()])
} else if symbol == "root" {
Expand All @@ -93,6 +116,12 @@ pub fn resolve_symbol(repo: RepoRef, symbol: &str) -> Result<Vec<CommitId>, Revs
return commit_id_result;
}

// Try to resolve as a change id (the non-obsolete commits in the change).
let change_id_result = resolve_non_obsolete_change_id(repo, symbol);
if !matches!(change_id_result, Err(RevsetError::NoSuchRevision(_))) {
return change_id_result;
}

Err(RevsetError::NoSuchRevision(symbol.to_owned()))
}
}
Expand Down
115 changes: 114 additions & 1 deletion lib/tests/test_revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use jujutsu_lib::commit_builder::CommitBuilder;
use jujutsu_lib::repo::RepoRef;
use jujutsu_lib::revset::{parse, resolve_symbol, RevsetError};
use jujutsu_lib::store::{CommitId, MillisSinceEpoch, Signature, Timestamp};
use jujutsu_lib::testutils;
use jujutsu_lib::testutils::CommitGraphBuilder;
use jujutsu_lib::{git, testutils};
use test_case::test_case;

#[test_case(false ; "local store")]
Expand Down Expand Up @@ -119,6 +119,119 @@ fn test_resolve_symbol_commit_id() {
tx.discard();
}

#[test]
fn test_resolve_symbol_change_id() {
let settings = testutils::user_settings();
// Test only with git so we can get predictable change ids
let (_temp_dir, repo) = testutils::init_repo(&settings, true);

let git_repo = repo.store().git_repo().unwrap();
// Add some commits that will end up having change ids with common prefixes
let empty_tree_id = git_repo.treebuilder(None).unwrap().write().unwrap();
let git_author = git2::Signature::new(
"git author",
"git.author@example.com",
&git2::Time::new(1000, 60),
)
.unwrap();
let git_committer = git2::Signature::new(
"git committer",
"git.committer@example.com",
&git2::Time::new(2000, -480),
)
.unwrap();
let git_tree = git_repo.find_tree(empty_tree_id).unwrap();
let mut git_commit_ids = vec![];
for i in &[133, 664, 840] {
let git_commit_id = git_repo
.commit(
Some(&format!("refs/heads/branch{}", i)),
&git_author,
&git_committer,
&format!("test {}", i),
&git_tree,
&[],
)
.unwrap();
git_commit_ids.push(git_commit_id);
}

let mut tx = repo.start_transaction("test");
git::import_refs(tx.mut_repo(), &git_repo).unwrap();
let repo = tx.commit();

// Test the test setup
assert_eq!(
hex::encode(git_commit_ids[0].as_bytes()),
// "04e12a5467bba790efb88a9870894ec208b16bf1" reversed
"8fd68d104372910e19511df709e5dde62a548720"
);
assert_eq!(
hex::encode(git_commit_ids[1].as_bytes()),
// "040b3ba3a51d8edbc4c5855cbd09de71d4c29cca" reversed
"5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"
);
assert_eq!(
hex::encode(git_commit_ids[2].as_bytes()),
// "04e1c7082e4e34f3f371d8a1a46770b861b9b547" reversed
"e2ad9d861d0ee625851b8ecfcf2c727410e38720"
);

// Test lookup by full change id
let repo_ref = repo.as_repo_ref();
assert_eq!(
resolve_symbol(repo_ref, "04e12a5467bba790efb88a9870894ec2"),
Ok(vec![CommitId::from_hex(
"8fd68d104372910e19511df709e5dde62a548720"
)])
);
assert_eq!(
resolve_symbol(repo_ref, "040b3ba3a51d8edbc4c5855cbd09de71"),
Ok(vec![CommitId::from_hex(
"5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"
)])
);
assert_eq!(
resolve_symbol(repo_ref, "04e1c7082e4e34f3f371d8a1a46770b8"),
Ok(vec![CommitId::from_hex(
"e2ad9d861d0ee625851b8ecfcf2c727410e38720"
)])
);

// Test change id prefix
assert_eq!(
resolve_symbol(repo_ref, "04e12"),
Ok(vec![CommitId::from_hex(
"8fd68d104372910e19511df709e5dde62a548720"
)])
);
assert_eq!(
resolve_symbol(repo_ref, "04e1c"),
Ok(vec![CommitId::from_hex(
"e2ad9d861d0ee625851b8ecfcf2c727410e38720"
)])
);
assert_eq!(
resolve_symbol(repo_ref, "04e1"),
Err(RevsetError::AmbiguousChangeIdPrefix("04e1".to_string()))
);
assert_eq!(
resolve_symbol(repo_ref, ""),
// Commit id is checked first, so this is considered an ambiguous commit id
Err(RevsetError::AmbiguousCommitIdPrefix("".to_string()))
);
assert_eq!(
resolve_symbol(repo_ref, "04e13"),
Err(RevsetError::NoSuchRevision("04e13".to_string()))
);

// Test non-hex string
assert_eq!(
resolve_symbol(repo_ref, "foo"),
Err(RevsetError::NoSuchRevision("foo".to_string()))
);
}

#[test_case(false ; "local store")]
#[test_case(true ; "git store")]
fn test_resolve_symbol_checkout(use_git: bool) {
Expand Down

0 comments on commit f6a9523

Please sign in to comment.