Skip to content

Commit

Permalink
revset: add tracked/untracked_remote_branches()
Browse files Browse the repository at this point in the history
Adds support for revset functions `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.
  • Loading branch information
scott2000 committed Jul 13, 2024
1 parent 35b2136 commit 5e46a9f
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 28 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/git/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
8 changes: 8 additions & 0 deletions docs/revsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ revsets (expressions) as arguments.
While Git-tracking branches can be selected by `<name>@git`, these branches
aren't included in `remote_branches()`.

* `tracked_remote_branches([branch_pattern[, [remote=]remote_pattern]])`: All
targets of tracked remote branches. Supports the same optional arguments as
`remote_branches()`.

* `untracked_remote_branches([branch_pattern[, [remote=]remote_pattern]])`:
All targets of untracked remote branches. Supports the same optional arguments
as `remote_branches()`.

* `tags()`: All tag targets. If a tag is in a conflicted state, all its
possible targets are included.

Expand Down
90 changes: 72 additions & 18 deletions lib/src/revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -107,6 +107,7 @@ pub enum RevsetCommitRef {
RemoteBranches {
branch_pattern: StringPattern,
remote_pattern: StringPattern,
remote_ref_state: Option<RemoteRefState>,
},
Tags,
GitRefs,
Expand Down Expand Up @@ -239,11 +240,13 @@ impl RevsetExpression {
pub fn remote_branches(
branch_pattern: StringPattern,
remote_pattern: StringPattern,
remote_ref_state: Option<RemoteRefState>,
) -> Rc<RevsetExpression> {
Rc::new(RevsetExpression::CommitRef(
RevsetCommitRef::RemoteBranches {
branch_pattern,
remote_pattern,
remote_ref_state,
},
))
}
Expand Down Expand Up @@ -626,22 +629,13 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
Ok(RevsetExpression::branches(pattern))
});
map.insert("remote_branches", |function, _context| {
let ([], [branch_opt_arg, remote_opt_arg]) =
function.expect_named_arguments(&["", "remote"])?;
let branch_pattern = if let Some(branch_arg) = branch_opt_arg {
expect_string_pattern(branch_arg)?
} else {
StringPattern::everything()
};
let remote_pattern = if let Some(remote_arg) = remote_opt_arg {
expect_string_pattern(remote_arg)?
} else {
StringPattern::everything()
};
Ok(RevsetExpression::remote_branches(
branch_pattern,
remote_pattern,
))
parse_remote_branches_arguments(function, None)
});
map.insert("tracked_remote_branches", |function, _context| {
parse_remote_branches_arguments(function, Some(RemoteRefState::Tracking))
});
map.insert("untracked_remote_branches", |function, _context| {
parse_remote_branches_arguments(function, Some(RemoteRefState::New))
});
map.insert("tags", |function, _context| {
function.expect_no_arguments()?;
Expand Down Expand Up @@ -752,6 +746,29 @@ pub fn expect_string_pattern(node: &ExpressionNode) -> Result<StringPattern, Rev
revset_parser::expect_pattern_with("string pattern", node, parse_pattern)
}

fn parse_remote_branches_arguments(
function: &FunctionCallNode,
remote_ref_state: Option<RemoteRefState>,
) -> Result<Rc<RevsetExpression>, RevsetParseError> {
let ([], [branch_opt_arg, remote_opt_arg]) =
function.expect_named_arguments(&["", "remote"])?;
let branch_pattern = if let Some(branch_arg) = branch_opt_arg {
expect_string_pattern(branch_arg)?
} else {
StringPattern::everything()
};
let remote_pattern = if let Some(remote_arg) = remote_opt_arg {
expect_string_pattern(remote_arg)?
} else {
StringPattern::everything()
};
Ok(RevsetExpression::remote_branches(
branch_pattern,
remote_pattern,
remote_ref_state,
))
}

/// Resolves function call by using the given function map.
fn lower_function_call(
function: &FunctionCallNode,
Expand Down Expand Up @@ -1609,11 +1626,15 @@ 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_or(true, |state| remote_ref.state == state)
})
.filter(|&((_, remote_name), _)| {
#[cfg(feature = "git")]
{
Expand Down Expand Up @@ -2313,14 +2334,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),
},
)
"###);
Expand Down Expand Up @@ -2566,6 +2598,7 @@ mod tests {
RemoteBranches {
branch_pattern: Substring(""),
remote_pattern: Substring("foo"),
remote_ref_state: None,
},
)
"###);
Expand All @@ -2575,6 +2608,27 @@ mod tests {
RemoteBranches {
branch_pattern: Substring("foo"),
remote_pattern: Substring("bar"),
remote_ref_state: None,
},
)
"###);
insta::assert_debug_snapshot!(
parse("tracked_remote_branches(foo, remote=bar)").unwrap(), @r###"
CommitRef(
RemoteBranches {
branch_pattern: Substring("foo"),
remote_pattern: Substring("bar"),
remote_ref_state: Some(Tracking),
},
)
"###);
insta::assert_debug_snapshot!(
parse("untracked_remote_branches(foo, remote=bar)").unwrap(), @r###"
CommitRef(
RemoteBranches {
branch_pattern: Substring("foo"),
remote_pattern: Substring("bar"),
remote_ref_state: Some(New),
},
)
"###);
Expand Down
63 changes: 53 additions & 10 deletions lib/tests/test_revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2059,11 +2059,12 @@ fn test_evaluate_expression_remote_branches() {
let settings = testutils::user_settings();
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let remote_ref = |target| RemoteRef {
let tracking_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()));
let normal_tracking_remote_ref =
|id: &CommitId| tracking_remote_ref(RefTarget::normal(id.clone()));

let mut tx = repo.start_transaction(&settings);
let mut_repo = tx.mut_repo();
Expand All @@ -2076,15 +2077,28 @@ 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()));
mut_repo.set_remote_branch("branch2", "private", normal_remote_ref(commit2.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_tracking_remote_ref(commit2.id()),
);
// Git-tracking branches aren't included
mut_repo.set_remote_branch(
"branch",
git::REMOTE_NAME_FOR_LOCAL_GIT_REPO,
normal_remote_ref(commit_git_remote.id()),
normal_tracking_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()]
Expand Down Expand Up @@ -2128,6 +2142,23 @@ 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()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_branches(branch1, origin)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_branches(branch2, private)"),
vec![commit2.id().clone()]
);
// Can silently resolve to an empty set if there's no matches
assert_eq!(
resolve_commit_ids(mut_repo, "remote_branches(branch3)"),
Expand All @@ -2149,9 +2180,21 @@ fn test_evaluate_expression_remote_branches() {
resolve_commit_ids(mut_repo, r#"remote_branches(exact:branch1, exact:orig)"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_branches(branch1)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_branches(branch2)"),
vec![]
);
// Two branches pointing to the same commit does not result in a duplicate in
// the revset
mut_repo.set_remote_branch("branch3", "origin", normal_remote_ref(commit2.id()));
mut_repo.set_remote_branch(
"branch3",
"origin",
normal_tracking_remote_ref(commit2.id()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_branches()"),
vec![commit2.id().clone(), commit1.id().clone()]
Expand All @@ -2166,15 +2209,15 @@ fn test_evaluate_expression_remote_branches() {
mut_repo.set_remote_branch(
"branch1",
"origin",
remote_ref(RefTarget::from_legacy_form(
tracking_remote_ref(RefTarget::from_legacy_form(
[commit1.id().clone()],
[commit2.id().clone(), commit3.id().clone()],
)),
);
mut_repo.set_remote_branch(
"branch2",
"private",
remote_ref(RefTarget::from_legacy_form(
tracking_remote_ref(RefTarget::from_legacy_form(
[commit2.id().clone()],
[commit3.id().clone(), commit4.id().clone()],
)),
Expand Down

0 comments on commit 5e46a9f

Please sign in to comment.