forked from mirrors/jj
revset: add tracked/untracked_remote_branches()
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.
This commit is contained in:
parent
35b2136c68
commit
2dd75b5c53
5 changed files with 137 additions and 28 deletions
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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<RemoteRefState>,
|
||||
},
|
||||
Tags,
|
||||
GitRefs,
|
||||
|
@ -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,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -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()?;
|
||||
|
@ -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,
|
||||
|
@ -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")]
|
||||
{
|
||||
|
@ -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),
|
||||
},
|
||||
)
|
||||
"###);
|
||||
|
@ -2566,6 +2598,7 @@ mod tests {
|
|||
RemoteBranches {
|
||||
branch_pattern: Substring(""),
|
||||
remote_pattern: Substring("foo"),
|
||||
remote_ref_state: None,
|
||||
},
|
||||
)
|
||||
"###);
|
||||
|
@ -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),
|
||||
},
|
||||
)
|
||||
"###);
|
||||
|
|
|
@ -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();
|
||||
|
@ -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()]
|
||||
|
@ -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)"),
|
||||
|
@ -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()]
|
||||
|
@ -2166,7 +2209,7 @@ 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()],
|
||||
)),
|
||||
|
@ -2174,7 +2217,7 @@ fn test_evaluate_expression_remote_branches() {
|
|||
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()],
|
||||
)),
|
||||
|
|
Loading…
Reference in a new issue