forked from mirrors/jj
feat(branch): support jj branch forget --glob
This commit is contained in:
parent
cd1e27a037
commit
91ead75e43
5 changed files with 85 additions and 8 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -600,6 +600,12 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
|
@ -778,6 +784,7 @@ dependencies = [
|
|||
"crossterm",
|
||||
"dirs",
|
||||
"git2",
|
||||
"glob",
|
||||
"hex",
|
||||
"insta",
|
||||
"itertools",
|
||||
|
|
|
@ -42,6 +42,7 @@ config = { version = "0.13.3", default-features = false, features = ["toml"] }
|
|||
crossterm = { version = "0.25", default-features = false }
|
||||
dirs = "4.0.0"
|
||||
git2 = "0.15.0"
|
||||
glob = "0.3.0"
|
||||
hex = "0.4.3"
|
||||
itertools = "0.10.5"
|
||||
jujutsu-lib = { version = "=0.6.1", path = "lib", default-features = false }
|
||||
|
|
|
@ -202,6 +202,12 @@ impl From<FsPathParseError> for CommandError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<glob::PatternError> for CommandError {
|
||||
fn from(err: glob::PatternError) -> Self {
|
||||
user_error(format!("Failed to compile glob: {err}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<clap::Error> for CommandError {
|
||||
fn from(err: clap::Error) -> Self {
|
||||
CommandError::ClapCliError(err)
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::collections::{BTreeSet, HashSet, VecDeque};
|
||||
use std::fmt::Debug;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
@ -25,7 +25,7 @@ use std::{fs, io};
|
|||
|
||||
use chrono::{FixedOffset, LocalResult, TimeZone, Utc};
|
||||
use clap::builder::NonEmptyStringValueParser;
|
||||
use clap::{ArgGroup, ArgMatches, CommandFactory, FromArgMatches, Subcommand};
|
||||
use clap::{ArgAction, ArgGroup, ArgMatches, CommandFactory, FromArgMatches, Subcommand};
|
||||
use itertools::Itertools;
|
||||
use jujutsu_lib::backend::{BackendError, CommitId, Timestamp, TreeValue};
|
||||
use jujutsu_lib::commit::Commit;
|
||||
|
@ -725,9 +725,13 @@ enum BranchSubcommand {
|
|||
/// recreated on future pulls if it still exists in the remote.
|
||||
#[command(visible_alias("f"))]
|
||||
Forget {
|
||||
/// The branches to delete.
|
||||
#[arg(required = true)]
|
||||
/// The branches to forget.
|
||||
#[arg(required_unless_present_any(&["glob"]))]
|
||||
names: Vec<String>,
|
||||
|
||||
/// A glob pattern indicating branches to forget.
|
||||
#[arg(action(ArgAction::Append), long = "glob")]
|
||||
glob: Vec<String>,
|
||||
},
|
||||
|
||||
/// List branches and their targets
|
||||
|
@ -3401,6 +3405,21 @@ fn cmd_branch(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn find_globs(view: &View, globs: &[String]) -> Result<Vec<String>, CommandError> {
|
||||
let globs: Vec<glob::Pattern> = globs
|
||||
.iter()
|
||||
.map(|glob| glob::Pattern::new(glob))
|
||||
.try_collect()?;
|
||||
let matching_branches = view
|
||||
.branches()
|
||||
.iter()
|
||||
.map(|(branch_name, _branch_target)| branch_name)
|
||||
.filter(|branch_name| globs.iter().any(|glob| glob.matches(branch_name)))
|
||||
.cloned()
|
||||
.collect();
|
||||
Ok(matching_branches)
|
||||
}
|
||||
|
||||
fn make_branch_term(branch_names: &[impl AsRef<str>]) -> String {
|
||||
match branch_names {
|
||||
[branch_name] => format!("branch {}", branch_name.as_ref()),
|
||||
|
@ -3501,12 +3520,14 @@ fn cmd_branch(
|
|||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
||||
BranchSubcommand::Forget { names } => {
|
||||
BranchSubcommand::Forget { names, glob } => {
|
||||
validate_branch_names_exist(view, names)?;
|
||||
let mut tx =
|
||||
workspace_command.start_transaction(&format!("forget {}", make_branch_term(names)));
|
||||
let globbed_names = find_globs(view, glob)?;
|
||||
let names: BTreeSet<String> = 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));
|
||||
for branch_name in names {
|
||||
tx.mut_repo().remove_branch(branch_name);
|
||||
tx.mut_repo().remove_branch(&branch_name);
|
||||
}
|
||||
workspace_command.finish_transaction(ui, tx)?;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,48 @@ fn test_branch_empty_name() {
|
|||
For more information try '--help'
|
||||
"###);
|
||||
}
|
||||
#[test]
|
||||
fn test_branch_forget_glob() {
|
||||
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");
|
||||
|
||||
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"]);
|
||||
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ bar-2 foo-1 foo-3 foo-4 230dd059e1b0
|
||||
o 000000000000
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "forget", "--glob", "foo-[1-3]"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ bar-2 foo-4 230dd059e1b0
|
||||
o 000000000000
|
||||
"###);
|
||||
|
||||
// Forgetting 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", "forget", "foo-4", "--glob", "foo-*", "--glob", "foo-*",
|
||||
],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ bar-2 230dd059e1b0
|
||||
o 000000000000
|
||||
"###);
|
||||
|
||||
// Malformed glob
|
||||
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "forget", "--glob", "foo-[1-3"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Error: Failed to compile glob: Pattern syntax error near position 4: invalid range pattern
|
||||
"###);
|
||||
}
|
||||
|
||||
fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
|
||||
test_env.jj_cmd_success(cwd, &["log", "-T", r#"branches " " commit_id.short()"#])
|
||||
|
|
Loading…
Reference in a new issue