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)]
|
#[derive(clap::Args, Clone, Debug)]
|
||||||
pub struct BranchDeleteArgs {
|
pub struct BranchDeleteArgs {
|
||||||
/// The branches to delete.
|
/// The branches to delete.
|
||||||
#[arg(required = true)]
|
#[arg(required_unless_present_any(& ["glob"]))]
|
||||||
names: Vec<String>,
|
names: Vec<String>,
|
||||||
|
|
||||||
|
/// A glob pattern indicating branches to delete.
|
||||||
|
#[arg(long)]
|
||||||
|
pub glob: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List branches and their targets
|
/// List branches and their targets
|
||||||
|
@ -202,7 +206,11 @@ fn cmd_branch_set(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function may return the same branch more than once
|
/// 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 matching_branches: Vec<String> = vec![];
|
||||||
let mut failed_globs = vec![];
|
let mut failed_globs = vec![];
|
||||||
for glob_str in globs {
|
for glob_str in globs {
|
||||||
|
@ -210,8 +218,15 @@ fn find_globs(view: &View, globs: &[String]) -> Result<Vec<String>, CommandError
|
||||||
let names = view
|
let names = view
|
||||||
.branches()
|
.branches()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(branch_name, _branch_target)| branch_name)
|
.filter_map(|(branch_name, branch_target)| {
|
||||||
.filter(|branch_name| glob.matches(branch_name))
|
if glob.matches(branch_name)
|
||||||
|
&& (allow_deleted || branch_target.local_target.is_some())
|
||||||
|
{
|
||||||
|
Some(branch_name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
if names.is_empty() {
|
if names.is_empty() {
|
||||||
|
@ -242,6 +257,7 @@ fn cmd_branch_delete(
|
||||||
args: &BranchDeleteArgs,
|
args: &BranchDeleteArgs,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let mut workspace_command = command.workspace_helper(ui)?;
|
let mut workspace_command = command.workspace_helper(ui)?;
|
||||||
|
let view = workspace_command.repo().view();
|
||||||
for branch_name in &args.names {
|
for branch_name in &args.names {
|
||||||
if workspace_command
|
if workspace_command
|
||||||
.repo()
|
.repo()
|
||||||
|
@ -252,10 +268,12 @@ fn cmd_branch_delete(
|
||||||
return Err(user_error(format!("No such branch: {branch_name}")));
|
return Err(user_error(format!("No such branch: {branch_name}")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut tx =
|
let globbed_names = find_globs(view, &args.glob, false)?;
|
||||||
workspace_command.start_transaction(&format!("delete {}", make_branch_term(&args.names)));
|
let names: BTreeSet<String> = args.names.iter().cloned().chain(globbed_names).collect();
|
||||||
for branch_name in &args.names {
|
let branch_term = make_branch_term(names.iter().collect_vec().as_slice());
|
||||||
tx.mut_repo().remove_local_branch(branch_name);
|
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)?;
|
tx.finish(ui)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -273,7 +291,7 @@ fn cmd_branch_forget(
|
||||||
return Err(user_error(format!("No such branch: {branch_name}")));
|
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 names: BTreeSet<String> = args.names.iter().cloned().chain(globbed_names).collect();
|
||||||
let branch_term = make_branch_term(names.iter().collect_vec().as_slice());
|
let branch_term = make_branch_term(names.iter().collect_vec().as_slice());
|
||||||
let mut tx = workspace_command.start_transaction(&format!("forget {branch_term}"));
|
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]
|
#[test]
|
||||||
fn test_branch_forget_export() {
|
fn test_branch_forget_export() {
|
||||||
let test_env = TestEnvironment::default();
|
let test_env = TestEnvironment::default();
|
||||||
|
|
Loading…
Reference in a new issue