cli: jj branch can accept any number of branches

This commit is contained in:
Waleed Khan 2022-05-02 12:58:32 -07:00
parent f6acee41df
commit 7a551e584f
5 changed files with 118 additions and 35 deletions

View file

@ -35,7 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Sparse checkouts are now supported. In fact, all working copies are now
"sparse", only to different degrees. Use the `jj sparse` command to manage
the paths included in the sparse checkout.
the paths included in the sparse checkout.
* The `$JJ_CONFIG` environment variable can now point to a directory. If it
does, all files in the directory will be read, in alphabetical order.
@ -73,6 +73,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
program = "kdiff3"
edit-args = ["--merge", "--cs", "CreateBakFiles=0"]
* `jj branch` can accept any number of branches to update, rather than just one.
### Fixed bugs
* When rebasing a conflict where one side modified a file and the other side
@ -83,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Updating the working copy to a commit where a file's executable bit changed
but the contents was the same used to lead to a crash. That has now been
fixed.
fixed.
* If one side of a merge modified a directory and the other side deleted it, it
used to be considered a conflict. The same was true if both sides added a

View file

@ -1604,7 +1604,8 @@ struct BranchArgs {
#[clap(long, group = "action")]
forget: bool,
name: String,
/// The branches to update.
names: Vec<String>,
}
/// List branches and their targets
@ -3966,53 +3967,76 @@ fn is_fast_forward(repo: RepoRef, branch_name: &str, new_target_id: &CommitId) -
fn cmd_branch(ui: &mut Ui, command: &CommandHelper, args: &BranchArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let branch_name = &args.name;
let branch_names: Vec<&str> = if args.delete || args.forget {
let view = workspace_command.repo().view();
args.names
.iter()
.map(|branch_name| match view.get_local_branch(branch_name) {
Some(_) => Ok(branch_name.as_str()),
None => Err(CommandError::UserError(format!(
"No such branch: {}",
branch_name
))),
})
.try_collect()?
} else {
args.names.iter().map(|name| name.as_str()).collect()
};
if branch_names.is_empty() {
ui.write_warn("warning: No branches provided.\n")?;
}
let branch_term = if branch_names.len() == 1 {
"branch"
} else {
"branches"
};
let branch_term = format!("{branch_term} {}", branch_names.join(", "));
if args.delete {
if workspace_command
.repo()
.view()
.get_local_branch(branch_name)
.is_none()
{
return Err(CommandError::UserError("No such branch".to_string()));
let mut tx = workspace_command.start_transaction(&format!("delete {branch_term}"));
for branch_name in branch_names {
tx.mut_repo().remove_local_branch(branch_name);
}
let mut tx = workspace_command.start_transaction(&format!("delete branch {}", branch_name));
tx.mut_repo().remove_local_branch(branch_name);
workspace_command.finish_transaction(ui, tx)?;
} else if args.forget {
if workspace_command
.repo()
.view()
.get_local_branch(branch_name)
.is_none()
{
return Err(CommandError::UserError("No such branch".to_string()));
let mut tx = workspace_command.start_transaction(&format!("forget {branch_term}"));
for branch_name in branch_names {
tx.mut_repo().remove_branch(branch_name);
}
let mut tx = workspace_command.start_transaction(&format!("forget branch {}", branch_name));
tx.mut_repo().remove_branch(branch_name);
workspace_command.finish_transaction(ui, tx)?;
} else {
if branch_names.len() > 1 {
ui.write_warn(format!(
"warning: Updating multiple branches ({}).\n",
branch_names.len()
))?;
}
let target_commit = workspace_command.resolve_single_rev(ui, &args.revision)?;
if !args.allow_backwards
&& !is_fast_forward(
workspace_command.repo().as_repo_ref(),
branch_name,
target_commit.id(),
)
&& !branch_names.iter().all(|branch_name| {
is_fast_forward(
workspace_command.repo().as_repo_ref(),
branch_name,
target_commit.id(),
)
})
{
return Err(CommandError::UserError(
"Use --allow-backwards to allow moving a branch backwards or sideways".to_string(),
));
}
let mut tx = workspace_command.start_transaction(&format!(
"point branch {} to commit {}",
branch_name,
"point {branch_term} to commit {}",
target_commit.id().hex()
));
tx.mut_repo().set_local_branch(
branch_name.to_string(),
RefTarget::Normal(target_commit.id().clone()),
);
for branch_name in branch_names {
tx.mut_repo().set_local_branch(
branch_name.to_string(),
RefTarget::Normal(target_commit.id().clone()),
);
}
workspace_command.finish_transaction(ui, tx)?;
}

View file

@ -80,7 +80,8 @@ pub struct ColorFormatter<'output> {
fn config_colors(user_settings: &UserSettings) -> HashMap<String, String> {
let mut result = HashMap::new();
result.insert(String::from("error"), String::from("red"));
result.insert(String::from("hint"), String::from("yellow"));
result.insert(String::from("warning"), String::from("yellow"));
result.insert(String::from("hint"), String::from("blue"));
result.insert(String::from("commit_id"), String::from("blue"));
result.insert(String::from("commit_id open"), String::from("green"));

View file

@ -128,6 +128,14 @@ impl<'stdout> Ui<'stdout> {
Ok(())
}
pub fn write_warn(&mut self, text: impl AsRef<str>) -> io::Result<()> {
let mut formatter = self.stderr_formatter();
formatter.add_label(String::from("warning"))?;
formatter.write_str(text.as_ref())?;
formatter.remove_label()?;
Ok(())
}
pub fn write_error(&mut self, text: &str) -> io::Result<()> {
let mut formatter = self.stderr_formatter();
formatter.add_label(String::from("error"))?;

View file

@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::common::TestEnvironment;
use std::path::Path;
use crate::common::{get_stderr_string, get_stdout_string, TestEnvironment};
pub mod common;
@ -28,3 +30,49 @@ fn test_branch_mutually_exclusive_actions() {
&["branch", "--delete", "--allow-backwards", "foo"],
);
}
#[test]
fn test_branch_multiple_names() {
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 assert = test_env
.jj_cmd(&repo_path, &["branch", "foo", "bar"])
.assert()
.success();
insta::assert_snapshot!(get_stdout_string(&assert), @"");
insta::assert_snapshot!(get_stderr_string(&assert), @"warning: Updating multiple branches (2).
");
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar foo 230dd059e1b0
o 000000000000
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "--delete", "foo", "bar"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ 230dd059e1b0
o 000000000000
"###);
}
#[test]
fn test_branch_hint_no_branches() {
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 assert = test_env
.jj_cmd(&repo_path, &["branch", "--delete"])
.assert()
.success();
let stderr = get_stderr_string(&assert);
insta::assert_snapshot!(stderr, @"warning: No branches provided.
");
}
fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
test_env.jj_cmd_success(cwd, &["log", "-T", r#"branches " " commit_id.short()"#])
}