mirror of
https://github.com/martinvonz/jj.git
synced 2024-11-28 17:41:14 +00:00
branch delete
: allow deleting globs of branches
This commit is contained in:
parent
97815851f7
commit
f6ddd775b9
2 changed files with 112 additions and 9 deletions
|
@ -49,8 +49,12 @@ pub struct BranchCreateArgs {
|
|||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub struct BranchDeleteArgs {
|
||||
/// The branches to delete.
|
||||
#[arg(required = true)]
|
||||
#[arg(required_unless_present_any(& ["glob"]))]
|
||||
names: Vec<String>,
|
||||
|
||||
/// A glob pattern indicating branches to delete.
|
||||
#[arg(long)]
|
||||
pub glob: Vec<String>,
|
||||
}
|
||||
|
||||
/// List branches and their targets
|
||||
|
@ -202,7 +206,11 @@ fn cmd_branch_set(
|
|||
}
|
||||
|
||||
/// This function may return the same branch more than once
|
||||
fn find_globs(view: &View, globs: &[String]) -> Result<Vec<String>, CommandError> {
|
||||
fn find_globs(
|
||||
view: &View,
|
||||
globs: &[String],
|
||||
allow_deleted: bool,
|
||||
) -> Result<Vec<String>, CommandError> {
|
||||
let mut matching_branches: Vec<String> = vec![];
|
||||
let mut failed_globs = vec![];
|
||||
for glob_str in globs {
|
||||
|
@ -210,8 +218,15 @@ fn find_globs(view: &View, globs: &[String]) -> Result<Vec<String>, CommandError
|
|||
let names = view
|
||||
.branches()
|
||||
.iter()
|
||||
.map(|(branch_name, _branch_target)| branch_name)
|
||||
.filter(|branch_name| glob.matches(branch_name))
|
||||
.filter_map(|(branch_name, branch_target)| {
|
||||
if glob.matches(branch_name)
|
||||
&& (allow_deleted || branch_target.local_target.is_some())
|
||||
{
|
||||
Some(branch_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect_vec();
|
||||
if names.is_empty() {
|
||||
|
@ -242,6 +257,7 @@ fn cmd_branch_delete(
|
|||
args: &BranchDeleteArgs,
|
||||
) -> Result<(), CommandError> {
|
||||
let mut workspace_command = command.workspace_helper(ui)?;
|
||||
let view = workspace_command.repo().view();
|
||||
for branch_name in &args.names {
|
||||
if workspace_command
|
||||
.repo()
|
||||
|
@ -252,10 +268,12 @@ fn cmd_branch_delete(
|
|||
return Err(user_error(format!("No such branch: {branch_name}")));
|
||||
}
|
||||
}
|
||||
let mut tx =
|
||||
workspace_command.start_transaction(&format!("delete {}", make_branch_term(&args.names)));
|
||||
for branch_name in &args.names {
|
||||
tx.mut_repo().remove_local_branch(branch_name);
|
||||
let globbed_names = find_globs(view, &args.glob, false)?;
|
||||
let names: BTreeSet<String> = args.names.iter().cloned().chain(globbed_names).collect();
|
||||
let branch_term = make_branch_term(names.iter().collect_vec().as_slice());
|
||||
let mut tx = workspace_command.start_transaction(&format!("delete {branch_term}"));
|
||||
for branch_name in names {
|
||||
tx.mut_repo().remove_local_branch(&branch_name);
|
||||
}
|
||||
tx.finish(ui)?;
|
||||
Ok(())
|
||||
|
@ -273,7 +291,7 @@ fn cmd_branch_forget(
|
|||
return Err(user_error(format!("No such branch: {branch_name}")));
|
||||
}
|
||||
}
|
||||
let globbed_names = find_globs(view, &args.glob)?;
|
||||
let globbed_names = find_globs(view, &args.glob, true)?;
|
||||
let names: BTreeSet<String> = args.names.iter().cloned().chain(globbed_names).collect();
|
||||
let branch_term = make_branch_term(names.iter().collect_vec().as_slice());
|
||||
let mut tx = workspace_command.start_transaction(&format!("forget {branch_term}"));
|
||||
|
|
|
@ -129,6 +129,91 @@ fn test_branch_forget_glob() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_branch_delete_glob() {
|
||||
// Set up a git repo with a branch and a jj repo that has it as a remote.
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
|
||||
let repo_path = test_env.env_root().join("repo");
|
||||
let git_repo_path = test_env.env_root().join("git-repo");
|
||||
let git_repo = git2::Repository::init_bare(git_repo_path).unwrap();
|
||||
let mut tree_builder = git_repo.treebuilder(None).unwrap();
|
||||
let file_oid = git_repo.blob(b"content").unwrap();
|
||||
tree_builder
|
||||
.insert("file", file_oid, git2::FileMode::Blob.into())
|
||||
.unwrap();
|
||||
test_env.jj_cmd_success(
|
||||
&repo_path,
|
||||
&["git", "remote", "add", "origin", "../git-repo"],
|
||||
);
|
||||
|
||||
test_env.jj_cmd_success(&repo_path, &["describe", "-m=commit"]);
|
||||
test_env.jj_cmd_success(&repo_path, &["branch", "set", "foo-1"]);
|
||||
test_env.jj_cmd_success(&repo_path, &["branch", "set", "bar-2"]);
|
||||
test_env.jj_cmd_success(&repo_path, &["branch", "set", "foo-3"]);
|
||||
test_env.jj_cmd_success(&repo_path, &["branch", "set", "foo-4"]);
|
||||
// Push to create remote-tracking branches
|
||||
test_env.jj_cmd_success(&repo_path, &["git", "push", "--all"]);
|
||||
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ bar-2 foo-1 foo-3 foo-4 6fbf398c2d59
|
||||
│
|
||||
~
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "delete", "--glob", "foo-[1-3]"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ bar-2 foo-1@origin foo-3@origin foo-4 6fbf398c2d59
|
||||
│
|
||||
~
|
||||
"###);
|
||||
|
||||
// We get an error if none of the globs match live branches. Unlike `jj branch
|
||||
// forget`, it's not allowed to delete already deleted branches.
|
||||
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "delete", "--glob=foo-[1-3]"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Error: The provided glob 'foo-[1-3]' did not match any branches
|
||||
"###);
|
||||
|
||||
// Deleting a branch via both explicit name and glob pattern, or with
|
||||
// multiple glob patterns, shouldn't produce an error.
|
||||
let stdout = test_env.jj_cmd_success(
|
||||
&repo_path,
|
||||
&[
|
||||
"branch", "delete", "foo-4", "--glob", "foo-*", "--glob", "foo-*",
|
||||
],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ bar-2 foo-1@origin foo-3@origin foo-4@origin 6fbf398c2d59
|
||||
│
|
||||
~
|
||||
"###);
|
||||
|
||||
// The deleted branches are still there
|
||||
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
|
||||
bar-2: 6fbf398c2d59 commit
|
||||
foo-1 (deleted)
|
||||
@origin: 6fbf398c2d59 commit
|
||||
(this branch will be *deleted permanently* on the remote on the
|
||||
next `jj git push`. Use `jj branch forget` to prevent this)
|
||||
foo-3 (deleted)
|
||||
@origin: 6fbf398c2d59 commit
|
||||
(this branch will be *deleted permanently* on the remote on the
|
||||
next `jj git push`. Use `jj branch forget` to prevent this)
|
||||
foo-4 (deleted)
|
||||
@origin: 6fbf398c2d59 commit
|
||||
(this branch will be *deleted permanently* on the remote on the
|
||||
next `jj git push`. Use `jj branch forget` to prevent this)
|
||||
"###);
|
||||
|
||||
// Malformed glob
|
||||
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "delete", "--glob", "foo-[1-3"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Error: Failed to compile glob: Pattern syntax error near position 4: invalid range pattern
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_branch_forget_export() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
Loading…
Reference in a new issue