ok/jj
1
0
Fork 0
forked from mirrors/jj

feat(branch): support jj branch forget --glob

This commit is contained in:
Waleed Khan 2022-11-22 15:58:04 -08:00
parent cd1e27a037
commit 91ead75e43
5 changed files with 85 additions and 8 deletions

7
Cargo.lock generated
View file

@ -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",

View file

@ -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 }

View file

@ -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)

View file

@ -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)?;
}

View file

@ -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()"#])