forked from mirrors/jj
cli: jj branch
can accept any number of branches
This commit is contained in:
parent
f6acee41df
commit
7a551e584f
5 changed files with 118 additions and 35 deletions
|
@ -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
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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"))?;
|
||||
|
|
|
@ -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()"#])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue