From 6a5457947068921532904bec73b09ebaa00c11dc Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Sun, 7 Jul 2024 18:38:29 -0500 Subject: [PATCH] revset: add tracked/untracked_remote_branches() Adds support for revsets `tracked_remote_branches()` and `untracked_remote_branches()`. I think this would be especially useful for configuring `immutable_heads()` because rewriting untracked remote branches usually wouldn't be desirable (since it wouldn't update the remote branch). It also makes it easy to hide branches that you don't care about from the log, since you could hide untracked branches and then only track branches that you care about. --- CHANGELOG.md | 3 +++ cli/src/commands/git/push.rs | 1 + docs/revsets.md | 4 ++++ lib/src/revset.rs | 43 ++++++++++++++++++++++++++++++++++-- lib/tests/test_revset.rs | 24 +++++++++++++++++--- 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ace2489aa8..03c113f404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). address unconditionally. Only ASCII case folding is currently implemented, but this will likely change in the future. +* New `tracked_remote_branches()` and `untracked_remote_branches()` revset + functions can be used to select tracked/untracked remote branches. + ### Fixed bugs ## [0.19.0] - 2024-07-03 diff --git a/cli/src/commands/git/push.rs b/cli/src/commands/git/push.rs index 7e317710b8..632a3f4e38 100644 --- a/cli/src/commands/git/push.rs +++ b/cli/src/commands/git/push.rs @@ -553,6 +553,7 @@ fn find_branches_targeted_by_revisions<'a>( let current_branches_expression = RevsetExpression::remote_branches( StringPattern::everything(), StringPattern::exact(remote_name), + None, ) .range(&RevsetExpression::commit(wc_commit_id)) .intersection(&RevsetExpression::branches(StringPattern::everything())); diff --git a/docs/revsets.md b/docs/revsets.md index 8bebbac8c9..542a4a984e 100644 --- a/docs/revsets.md +++ b/docs/revsets.md @@ -217,6 +217,10 @@ revsets (expressions) as arguments. While Git-tracking branches can be selected by `@git`, these branches aren't included in `remote_branches()`. +* `tracked_remote_branches()`: All targets of tracked remote branches. + +* `untracked_remote_branches()`: All targets of untracked remote branches. + * `tags()`: All tag targets. If a tag is in a conflicted state, all its possible targets are included. diff --git a/lib/src/revset.rs b/lib/src/revset.rs index 357cc48a55..b3ba07b6ce 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -34,7 +34,7 @@ use crate::graph::GraphEdge; use crate::hex_util::to_forward_hex; use crate::id_prefix::IdPrefixContext; use crate::object_id::{HexPrefix, PrefixResolution}; -use crate::op_store::WorkspaceId; +use crate::op_store::{RemoteRefState, WorkspaceId}; use crate::repo::Repo; use crate::repo_path::RepoPathUiConverter; pub use crate::revset_parser::{ @@ -107,6 +107,7 @@ pub enum RevsetCommitRef { RemoteBranches { branch_pattern: StringPattern, remote_pattern: StringPattern, + remote_ref_state: Option, }, Tags, GitRefs, @@ -239,11 +240,13 @@ impl RevsetExpression { pub fn remote_branches( branch_pattern: StringPattern, remote_pattern: StringPattern, + remote_ref_state: Option, ) -> Rc { Rc::new(RevsetExpression::CommitRef( RevsetCommitRef::RemoteBranches { branch_pattern, remote_pattern, + remote_ref_state, }, )) } @@ -641,6 +644,23 @@ static BUILTIN_FUNCTION_MAP: Lazy> = Lazy: Ok(RevsetExpression::remote_branches( branch_pattern, remote_pattern, + None, + )) + }); + map.insert("tracked_remote_branches", |function, _context| { + function.expect_no_arguments()?; + Ok(RevsetExpression::remote_branches( + StringPattern::everything(), + StringPattern::everything(), + Some(RemoteRefState::Tracking), + )) + }); + map.insert("untracked_remote_branches", |function, _context| { + function.expect_no_arguments()?; + Ok(RevsetExpression::remote_branches( + StringPattern::everything(), + StringPattern::everything(), + Some(RemoteRefState::New), )) }); map.insert("tags", |function, _context| { @@ -1609,11 +1629,17 @@ fn resolve_commit_ref( RevsetCommitRef::RemoteBranches { branch_pattern, remote_pattern, + remote_ref_state, } => { // TODO: should we allow to select @git branches explicitly? let commit_ids = repo .view() .remote_branches_matching(branch_pattern, remote_pattern) + .filter(|(_, remote_ref)| { + remote_ref_state + .map(|state| remote_ref.state == state) + .unwrap_or(true) + }) .filter(|&((_, remote_name), _)| { #[cfg(feature = "git")] { @@ -2313,14 +2339,25 @@ mod tests { RemoteBranches { branch_pattern: Substring(""), remote_pattern: Substring(""), + remote_ref_state: None, }, ) "###); - insta::assert_debug_snapshot!(parse("remote_branches()").unwrap(), @r###" + insta::assert_debug_snapshot!(parse("tracked_remote_branches()").unwrap(), @r###" + CommitRef( + RemoteBranches { + branch_pattern: Substring(""), + remote_pattern: Substring(""), + remote_ref_state: Some(Tracking), + }, + ) + "###); + insta::assert_debug_snapshot!(parse("untracked_remote_branches()").unwrap(), @r###" CommitRef( RemoteBranches { branch_pattern: Substring(""), remote_pattern: Substring(""), + remote_ref_state: Some(New), }, ) "###); @@ -2566,6 +2603,7 @@ mod tests { RemoteBranches { branch_pattern: Substring(""), remote_pattern: Substring("foo"), + remote_ref_state: None, }, ) "###); @@ -2575,6 +2613,7 @@ mod tests { RemoteBranches { branch_pattern: Substring("foo"), remote_pattern: Substring("bar"), + remote_ref_state: None, }, ) "###); diff --git a/lib/tests/test_revset.rs b/lib/tests/test_revset.rs index 91e091f6b6..67d5562d42 100644 --- a/lib/tests/test_revset.rs +++ b/lib/tests/test_revset.rs @@ -2061,7 +2061,7 @@ fn test_evaluate_expression_remote_branches() { let repo = &test_repo.repo; let remote_ref = |target| RemoteRef { target, - state: RemoteRefState::Tracking, // doesn't matter + state: RemoteRefState::Tracking, }; let normal_remote_ref = |id: &CommitId| remote_ref(RefTarget::normal(id.clone())); @@ -2076,8 +2076,16 @@ fn test_evaluate_expression_remote_branches() { // Can get branches when there are none assert_eq!(resolve_commit_ids(mut_repo, "remote_branches()"), vec![]); - // Can get a few branches - mut_repo.set_remote_branch("branch1", "origin", normal_remote_ref(commit1.id())); + // Branch 1 is untracked on remote origin + mut_repo.set_remote_branch( + "branch1", + "origin", + RemoteRef { + target: RefTarget::normal(commit1.id().clone()), + state: RemoteRefState::New, + }, + ); + // Branch 2 is tracked on remote private mut_repo.set_remote_branch("branch2", "private", normal_remote_ref(commit2.id())); // Git-tracking branches aren't included mut_repo.set_remote_branch( @@ -2085,6 +2093,7 @@ fn test_evaluate_expression_remote_branches() { git::REMOTE_NAME_FOR_LOCAL_GIT_REPO, normal_remote_ref(commit_git_remote.id()), ); + // Can get a few branches assert_eq!( resolve_commit_ids(mut_repo, "remote_branches()"), vec![commit2.id().clone(), commit1.id().clone()] @@ -2128,6 +2137,15 @@ fn test_evaluate_expression_remote_branches() { resolve_commit_ids(mut_repo, r#"remote_branches(exact:branch1, exact:origin)"#), vec![commit1.id().clone()] ); + // Can filter branches by tracked and untracked + assert_eq!( + resolve_commit_ids(mut_repo, "tracked_remote_branches()"), + vec![commit2.id().clone()] + ); + assert_eq!( + resolve_commit_ids(mut_repo, "untracked_remote_branches()"), + vec![commit1.id().clone()] + ); // Can silently resolve to an empty set if there's no matches assert_eq!( resolve_commit_ids(mut_repo, "remote_branches(branch3)"),