diff --git a/lib/src/revset.rs b/lib/src/revset.rs index 88cdfb741..147316f84 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -27,6 +27,7 @@ use thiserror::Error; use crate::backend::{BackendError, BackendResult, CommitId}; use crate::commit::Commit; use crate::index::{HexPrefix, IndexEntry, IndexPosition, PrefixResolution, RevWalk}; +use crate::op_store::WorkspaceId; use crate::repo::RepoRef; use crate::revset_graph_iterator::RevsetGraphIterator; use crate::store::Store; @@ -102,7 +103,7 @@ fn resolve_change_id(repo: RepoRef, change_id_prefix: &str) -> Result Result Result, RevsetError> { - if symbol == "@" { - Ok(vec![repo.view().checkout().clone()]) +pub fn resolve_symbol( + repo: RepoRef, + symbol: &str, + workspace_id: Option<&WorkspaceId>, +) -> Result, RevsetError> { + if symbol.ends_with('@') { + let target_workspace = if symbol == "@" { + if let Some(workspace_id) = workspace_id { + workspace_id.clone() + } else { + return Err(RevsetError::NoSuchRevision(symbol.to_owned())); + } + } else { + WorkspaceId::new(symbol.strip_suffix('@').unwrap().to_string()) + }; + if let Some(commit_id) = repo.view().get_checkout(&target_workspace) { + Ok(vec![commit_id.clone()]) + } else { + Err(RevsetError::NoSuchRevision(symbol.to_owned())) + } } else if symbol == "root" { Ok(vec![repo.store().root_commit_id().clone()]) } else { @@ -387,8 +405,9 @@ impl RevsetExpression { pub fn evaluate<'repo>( &self, repo: RepoRef<'repo>, + workspace_id: Option<&WorkspaceId>, ) -> Result + 'repo>, RevsetError> { - evaluate_expression(repo, self) + evaluate_expression(repo, self, workspace_id) } } @@ -1069,6 +1088,7 @@ impl<'revset, 'repo> Iterator for DifferenceRevsetIterator<'revset, 'repo> { pub fn evaluate_expression<'repo>( repo: RepoRef<'repo>, expression: &RevsetExpression, + workspace_id: Option<&WorkspaceId>, ) -> Result + 'repo>, RevsetError> { match expression { RevsetExpression::None => Ok(Box::new(EagerRevset { @@ -1076,12 +1096,12 @@ pub fn evaluate_expression<'repo>( })), RevsetExpression::Commits(commit_ids) => Ok(revset_for_commit_ids(repo, commit_ids)), RevsetExpression::Symbol(symbol) => { - let commit_ids = resolve_symbol(repo, symbol)?; - evaluate_expression(repo, &RevsetExpression::Commits(commit_ids)) + let commit_ids = resolve_symbol(repo, symbol, workspace_id)?; + evaluate_expression(repo, &RevsetExpression::Commits(commit_ids), workspace_id) } RevsetExpression::Parents(base_expression) => { // TODO: Make this lazy - let base_set = base_expression.evaluate(repo)?; + let base_set = base_expression.evaluate(repo, workspace_id)?; let mut parent_entries = base_set .iter() .flat_map(|entry| entry.parents()) @@ -1093,9 +1113,9 @@ pub fn evaluate_expression<'repo>( })) } RevsetExpression::Children(roots) => { - let root_set = roots.evaluate(repo)?; + let root_set = roots.evaluate(repo, workspace_id)?; let candidates_expression = roots.descendants(); - let candidate_set = candidates_expression.evaluate(repo)?; + let candidate_set = candidates_expression.evaluate(repo, workspace_id)?; Ok(Box::new(ChildrenRevset { root_set, candidate_set, @@ -1103,11 +1123,11 @@ pub fn evaluate_expression<'repo>( } RevsetExpression::Ancestors(base_expression) => RevsetExpression::none() .range(base_expression) - .evaluate(repo), + .evaluate(repo, workspace_id), RevsetExpression::Range { roots, heads } => { - let root_set = roots.evaluate(repo)?; + let root_set = roots.evaluate(repo, workspace_id)?; let root_ids = root_set.iter().commit_ids().collect_vec(); - let head_set = heads.evaluate(repo)?; + let head_set = heads.evaluate(repo, workspace_id)?; let head_ids = head_set.iter().commit_ids().collect_vec(); let walk = repo.index().walk_revs(&head_ids, &root_ids); Ok(Box::new(RevWalkRevset { walk })) @@ -1116,8 +1136,8 @@ pub fn evaluate_expression<'repo>( // reverse #[allow(clippy::needless_collect)] RevsetExpression::DagRange { roots, heads } => { - let root_set = roots.evaluate(repo)?; - let candidate_set = heads.ancestors().evaluate(repo)?; + let root_set = roots.evaluate(repo, workspace_id)?; + let candidate_set = heads.ancestors().evaluate(repo, workspace_id)?; let mut reachable: HashSet<_> = root_set.iter().map(|entry| entry.position()).collect(); let mut result = vec![]; let candidates = candidate_set.iter().collect_vec(); @@ -1142,7 +1162,7 @@ pub fn evaluate_expression<'repo>( &repo.view().heads().iter().cloned().collect_vec(), )), RevsetExpression::HeadsOf(candidates) => { - let candidate_set = candidates.evaluate(repo)?; + let candidate_set = candidates.evaluate(repo, workspace_id)?; let candidate_ids = candidate_set.iter().commit_ids().collect_vec(); Ok(revset_for_commit_ids( repo, @@ -1153,7 +1173,7 @@ pub fn evaluate_expression<'repo>( candidates, parent_count_range, } => { - let candidates = candidates.evaluate(repo)?; + let candidates = candidates.evaluate(repo, workspace_id)?; let parent_count_range = parent_count_range.clone(); Ok(Box::new(FilterRevset { candidates, @@ -1201,7 +1221,7 @@ pub fn evaluate_expression<'repo>( Ok(revset_for_commit_ids(repo, &commit_ids)) } RevsetExpression::Description { needle, candidates } => { - let candidates = candidates.evaluate(repo)?; + let candidates = candidates.evaluate(repo, workspace_id)?; let repo = repo; let needle = needle.clone(); Ok(Box::new(FilterRevset { @@ -1216,7 +1236,7 @@ pub fn evaluate_expression<'repo>( })) } RevsetExpression::Author { needle, candidates } => { - let candidates = candidates.evaluate(repo)?; + let candidates = candidates.evaluate(repo, workspace_id)?; let repo = repo; let needle = needle.clone(); // TODO: Make these functions that take a needle to search for accept some @@ -1232,7 +1252,7 @@ pub fn evaluate_expression<'repo>( })) } RevsetExpression::Committer { needle, candidates } => { - let candidates = candidates.evaluate(repo)?; + let candidates = candidates.evaluate(repo, workspace_id)?; let repo = repo; let needle = needle.clone(); Ok(Box::new(FilterRevset { @@ -1245,18 +1265,18 @@ pub fn evaluate_expression<'repo>( })) } RevsetExpression::Union(expression1, expression2) => { - let set1 = expression1.evaluate(repo)?; - let set2 = expression2.evaluate(repo)?; + let set1 = expression1.evaluate(repo, workspace_id)?; + let set2 = expression2.evaluate(repo, workspace_id)?; Ok(Box::new(UnionRevset { set1, set2 })) } RevsetExpression::Intersection(expression1, expression2) => { - let set1 = expression1.evaluate(repo)?; - let set2 = expression2.evaluate(repo)?; + let set1 = expression1.evaluate(repo, workspace_id)?; + let set2 = expression2.evaluate(repo, workspace_id)?; Ok(Box::new(IntersectionRevset { set1, set2 })) } RevsetExpression::Difference(expression1, expression2) => { - let set1 = expression1.evaluate(repo)?; - let set2 = expression2.evaluate(repo)?; + let set1 = expression1.evaluate(repo, workspace_id)?; + let set2 = expression2.evaluate(repo, workspace_id)?; Ok(Box::new(DifferenceRevset { set1, set2 })) } } diff --git a/lib/src/rewrite.rs b/lib/src/rewrite.rs index 2f6c26aba..a6aaccf5e 100644 --- a/lib/src/rewrite.rs +++ b/lib/src/rewrite.rs @@ -156,7 +156,7 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> { .parents() .minus(&old_commits_expression); let heads_to_add = heads_to_add_expression - .evaluate(mut_repo.as_repo_ref()) + .evaluate(mut_repo.as_repo_ref(), None) .unwrap() .iter() .commit_ids() @@ -164,7 +164,7 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> { let to_visit_expression = old_commits_expression.descendants(); let to_visit_revset = to_visit_expression - .evaluate(mut_repo.as_repo_ref()) + .evaluate(mut_repo.as_repo_ref(), None) .unwrap(); let to_visit_entries = to_visit_revset.iter().collect_vec(); drop(to_visit_revset); diff --git a/lib/tests/test_revset.rs b/lib/tests/test_revset.rs index 5b4688bdb..66a2ab830 100644 --- a/lib/tests/test_revset.rs +++ b/lib/tests/test_revset.rs @@ -29,7 +29,7 @@ fn test_resolve_symbol_root(use_git: bool) { let repo = &test_workspace.repo; assert_eq!( - resolve_symbol(repo.as_repo_ref(), "root"), + resolve_symbol(repo.as_repo_ref(), "root", None), Ok(vec![repo.store().root_commit_id().clone()]) ); } @@ -84,39 +84,39 @@ fn test_resolve_symbol_commit_id() { // Test lookup by full commit id let repo_ref = repo.as_repo_ref(); assert_eq!( - resolve_symbol(repo_ref, "0454de3cae04c46cda37ba2e8873b4c17ff51dcb"), + resolve_symbol(repo_ref, "0454de3cae04c46cda37ba2e8873b4c17ff51dcb", None), Ok(vec![commits[0].id().clone()]) ); assert_eq!( - resolve_symbol(repo_ref, "045f56cd1b17e8abde86771e2705395dcde6a957"), + resolve_symbol(repo_ref, "045f56cd1b17e8abde86771e2705395dcde6a957", None), Ok(vec![commits[1].id().clone()]) ); assert_eq!( - resolve_symbol(repo_ref, "0468f7da8de2ce442f512aacf83411d26cd2e0cf"), + resolve_symbol(repo_ref, "0468f7da8de2ce442f512aacf83411d26cd2e0cf", None), Ok(vec![commits[2].id().clone()]) ); // Test commit id prefix assert_eq!( - resolve_symbol(repo_ref, "046"), + resolve_symbol(repo_ref, "046", None), Ok(vec![commits[2].id().clone()]) ); assert_eq!( - resolve_symbol(repo_ref, "04"), + resolve_symbol(repo_ref, "04", None), Err(RevsetError::AmbiguousCommitIdPrefix("04".to_string())) ); assert_eq!( - resolve_symbol(repo_ref, ""), + resolve_symbol(repo_ref, "", None), Err(RevsetError::AmbiguousCommitIdPrefix("".to_string())) ); assert_eq!( - resolve_symbol(repo_ref, "040"), + resolve_symbol(repo_ref, "040", None), Err(RevsetError::NoSuchRevision("040".to_string())) ); // Test non-hex string assert_eq!( - resolve_symbol(repo_ref, "foo"), + resolve_symbol(repo_ref, "foo", None), Err(RevsetError::NoSuchRevision("foo".to_string())) ); } @@ -183,19 +183,19 @@ fn test_resolve_symbol_change_id() { // Test lookup by full change id let repo_ref = repo.as_repo_ref(); assert_eq!( - resolve_symbol(repo_ref, "04e12a5467bba790efb88a9870894ec2"), + resolve_symbol(repo_ref, "04e12a5467bba790efb88a9870894ec2", None), Ok(vec![CommitId::from_hex( "8fd68d104372910e19511df709e5dde62a548720" )]) ); assert_eq!( - resolve_symbol(repo_ref, "040b3ba3a51d8edbc4c5855cbd09de71"), + resolve_symbol(repo_ref, "040b3ba3a51d8edbc4c5855cbd09de71", None), Ok(vec![CommitId::from_hex( "5339432b8e7b90bd3aa1a323db71b8a5c5dcd020" )]) ); assert_eq!( - resolve_symbol(repo_ref, "04e1c7082e4e34f3f371d8a1a46770b8"), + resolve_symbol(repo_ref, "04e1c7082e4e34f3f371d8a1a46770b8", None), Ok(vec![CommitId::from_hex( "e2ad9d861d0ee625851b8ecfcf2c727410e38720" )]) @@ -203,34 +203,34 @@ fn test_resolve_symbol_change_id() { // Test change id prefix assert_eq!( - resolve_symbol(repo_ref, "04e12"), + resolve_symbol(repo_ref, "04e12", None), Ok(vec![CommitId::from_hex( "8fd68d104372910e19511df709e5dde62a548720" )]) ); assert_eq!( - resolve_symbol(repo_ref, "04e1c"), + resolve_symbol(repo_ref, "04e1c", None), Ok(vec![CommitId::from_hex( "e2ad9d861d0ee625851b8ecfcf2c727410e38720" )]) ); assert_eq!( - resolve_symbol(repo_ref, "04e1"), + resolve_symbol(repo_ref, "04e1", None), Err(RevsetError::AmbiguousChangeIdPrefix("04e1".to_string())) ); assert_eq!( - resolve_symbol(repo_ref, ""), + resolve_symbol(repo_ref, "", None), // 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"), + resolve_symbol(repo_ref, "04e13", None), Err(RevsetError::NoSuchRevision("04e13".to_string())) ); // Test non-hex string assert_eq!( - resolve_symbol(repo_ref, "foo"), + resolve_symbol(repo_ref, "foo", None), Err(RevsetError::NoSuchRevision("foo".to_string())) ); } @@ -248,14 +248,40 @@ fn test_resolve_symbol_checkout(use_git: bool) { let commit1 = testutils::create_random_commit(&settings, repo).write_to_repo(mut_repo); let commit2 = testutils::create_random_commit(&settings, repo).write_to_repo(mut_repo); - mut_repo.set_checkout(WorkspaceId::default(), commit1.id().clone()); + let ws1 = WorkspaceId::new("ws1".to_string()); + let ws2 = WorkspaceId::new("ws2".to_string()); + mut_repo.remove_checkout(&WorkspaceId::default()); + + // With no workspaces, no variation can be resolved assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "@"), + resolve_symbol(mut_repo.as_repo_ref(), "@", None), + Err(RevsetError::NoSuchRevision("@".to_string())) + ); + assert_eq!( + resolve_symbol(mut_repo.as_repo_ref(), "@", Some(&ws1)), + Err(RevsetError::NoSuchRevision("@".to_string())) + ); + assert_eq!( + resolve_symbol(mut_repo.as_repo_ref(), "ws1@", Some(&ws1)), + Err(RevsetError::NoSuchRevision("ws1@".to_string())) + ); + + // Add some workspaces + mut_repo.set_checkout(ws1.clone(), commit1.id().clone()); + mut_repo.set_checkout(ws2, commit2.id().clone()); + // @ cannot be resolved without a default workspace ID + assert_eq!( + resolve_symbol(mut_repo.as_repo_ref(), "@", None), + Err(RevsetError::NoSuchRevision("@".to_string())) + ); + // Can resolve "@" shorthand with a default workspace ID + assert_eq!( + resolve_symbol(mut_repo.as_repo_ref(), "@", Some(&ws1)), Ok(vec![commit1.id().clone()]) ); - mut_repo.set_checkout(WorkspaceId::default(), commit2.id().clone()); + // Can resolve an explicit checkout assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "@"), + resolve_symbol(mut_repo.as_repo_ref(), "ws2@", Some(&ws1)), Ok(vec![commit2.id().clone()]) ); } @@ -301,7 +327,7 @@ fn test_resolve_symbol_git_refs() { // Non-existent ref assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "non-existent"), + resolve_symbol(mut_repo.as_repo_ref(), "non-existent", None), Err(RevsetError::NoSuchRevision("non-existent".to_string())) ); @@ -311,7 +337,7 @@ fn test_resolve_symbol_git_refs() { RefTarget::Normal(commit4.id().clone()), ); assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "refs/heads/branch"), + resolve_symbol(mut_repo.as_repo_ref(), "refs/heads/branch", None), Ok(vec![commit4.id().clone()]) ); @@ -325,7 +351,7 @@ fn test_resolve_symbol_git_refs() { RefTarget::Normal(commit4.id().clone()), ); assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "heads/branch"), + resolve_symbol(mut_repo.as_repo_ref(), "heads/branch", None), Ok(vec![commit5.id().clone()]) ); @@ -339,7 +365,7 @@ fn test_resolve_symbol_git_refs() { RefTarget::Normal(commit4.id().clone()), ); assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "branch"), + resolve_symbol(mut_repo.as_repo_ref(), "branch", None), Ok(vec![commit3.id().clone()]) ); @@ -349,7 +375,7 @@ fn test_resolve_symbol_git_refs() { RefTarget::Normal(commit4.id().clone()), ); assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "tag"), + resolve_symbol(mut_repo.as_repo_ref(), "tag", None), Ok(vec![commit4.id().clone()]) ); @@ -359,7 +385,7 @@ fn test_resolve_symbol_git_refs() { RefTarget::Normal(commit2.id().clone()), ); assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "origin/remote-branch"), + resolve_symbol(mut_repo.as_repo_ref(), "origin/remote-branch", None), Ok(vec![commit2.id().clone()]) ); @@ -367,17 +393,21 @@ fn test_resolve_symbol_git_refs() { mut_repo.set_git_ref("@".to_string(), RefTarget::Normal(commit2.id().clone())); mut_repo.set_git_ref("root".to_string(), RefTarget::Normal(commit3.id().clone())); assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "@"), - Ok(vec![mut_repo.view().checkout().clone()]) + resolve_symbol(mut_repo.as_repo_ref(), "@", Some(&WorkspaceId::default())), + Ok(vec![mut_repo + .view() + .get_checkout(&WorkspaceId::default()) + .unwrap() + .clone()]) ); assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "root"), + resolve_symbol(mut_repo.as_repo_ref(), "root", None), Ok(vec![mut_repo.store().root_commit().id().clone()]) ); // Conflicted ref resolves to its "adds" assert_eq!( - resolve_symbol(mut_repo.as_repo_ref(), "refs/heads/conflicted"), + resolve_symbol(mut_repo.as_repo_ref(), "refs/heads/conflicted", None), Ok(vec![commit1.id().clone(), commit3.id().clone()]) ); } @@ -385,7 +415,21 @@ fn test_resolve_symbol_git_refs() { fn resolve_commit_ids(repo: RepoRef, revset_str: &str) -> Vec { let expression = parse(revset_str).unwrap(); expression - .evaluate(repo) + .evaluate(repo, None) + .unwrap() + .iter() + .commit_ids() + .collect() +} + +fn resolve_commit_ids_in_workspace( + repo: RepoRef, + revset_str: &str, + workspace_id: &WorkspaceId, +) -> Vec { + let expression = parse(revset_str).unwrap(); + expression + .evaluate(repo, Some(workspace_id)) .unwrap() .iter() .commit_ids() @@ -414,7 +458,7 @@ fn test_evaluate_expression_root_and_checkout(use_git: bool) { // Can find the current checkout mut_repo.set_checkout(WorkspaceId::default(), commit1.id().clone()); assert_eq!( - resolve_commit_ids(mut_repo.as_repo_ref(), "@"), + resolve_commit_ids_in_workspace(mut_repo.as_repo_ref(), "@", &WorkspaceId::default()), vec![commit1.id().clone()] ); } @@ -504,7 +548,7 @@ fn test_evaluate_expression_parents(use_git: bool) { // Can find parents of the current checkout mut_repo.set_checkout(WorkspaceId::default(), commit2.id().clone()); assert_eq!( - resolve_commit_ids(mut_repo.as_repo_ref(), "@-"), + resolve_commit_ids_in_workspace(mut_repo.as_repo_ref(), "@-", &WorkspaceId::default()), vec![commit1.id().clone()] ); diff --git a/src/commands.rs b/src/commands.rs index 4eb340846..6d2897e9a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -386,7 +386,8 @@ impl WorkspaceCommandHelper { revision_str: &str, ) -> Result { let revset_expression = self.parse_revset(ui, revision_str)?; - let revset = revset_expression.evaluate(self.repo.as_repo_ref())?; + let revset = + revset_expression.evaluate(self.repo.as_repo_ref(), Some(&self.workspace_id()))?; let mut iter = revset.iter().commits(self.repo.store()); match iter.next() { None => Err(CommandError::UserError(format!( @@ -412,7 +413,8 @@ impl WorkspaceCommandHelper { revision_str: &str, ) -> Result, CommandError> { let revset_expression = self.parse_revset(ui, revision_str)?; - let revset = revset_expression.evaluate(self.repo.as_repo_ref())?; + let revset = + revset_expression.evaluate(self.repo.as_repo_ref(), Some(&self.workspace_id()))?; Ok(revset .iter() .commits(self.repo.store()) @@ -2541,7 +2543,8 @@ fn cmd_log(ui: &mut Ui, command: &CommandHelper, args: &ArgMatches) -> Result<() workspace_command.parse_revset(ui, args.value_of("revisions").unwrap())?; let repo = workspace_command.repo(); let checkout_id = repo.view().checkout().clone(); - let revset = revset_expression.evaluate(repo.as_repo_ref())?; + let revset = + revset_expression.evaluate(repo.as_repo_ref(), Some(&workspace_command.workspace_id()))?; let store = repo.store(); let template_string = match args.value_of("template") { @@ -3344,7 +3347,10 @@ fn cmd_rebase(ui: &mut Ui, command: &CommandHelper, args: &ArgMatches) -> Result let mut num_rebased_descendants = 0; let store = workspace_command.repo.store(); for child_commit in children_expression - .evaluate(workspace_command.repo().as_repo_ref()) + .evaluate( + workspace_command.repo().as_repo_ref(), + Some(&workspace_command.workspace_id()), + ) .unwrap() .iter() .commits(store) diff --git a/src/templater.rs b/src/templater.rs index b1f79116a..88b3805ad 100644 --- a/src/templater.rs +++ b/src/templater.rs @@ -312,7 +312,7 @@ impl DivergentProperty { pub fn new(repo: RepoRef) -> Self { // TODO: Create a persistent index from change id to commit ids. let mut commit_count_by_change: HashMap = HashMap::new(); - for index_entry in RevsetExpression::all().evaluate(repo).unwrap().iter() { + for index_entry in RevsetExpression::all().evaluate(repo, None).unwrap().iter() { let change_id = index_entry.change_id(); commit_count_by_change .entry(change_id)