Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revset: add tracked/untracked_remote_branches() #4068

Merged
merged 1 commit into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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