Implement a rename subcommand for the branch command.

This is really a simple change that does the following in a transaction:
* Set the new branch name to point to the same commit as the old branch name.
* Set the old branch name to point to no commit (hence deleting the old name).

Before it starts, it confirms that the new branch name is not already in use.
This commit is contained in:
Essien Ita Essien 2023-10-18 21:00:10 +01:00 committed by Essien Ita Essien
parent d39843bd82
commit 35b8dad890
3 changed files with 75 additions and 0 deletions

View file

@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### New features
* Information about new and resolved conflicts is now printed by every command.
* `jj branch` has gained a new `rename` subcommand that allows changing a branch
name atomically. `jj branch help rename` for details.
### Fixed bugs

View file

@ -34,6 +34,8 @@ pub enum BranchSubcommand {
Forget(BranchForgetArgs),
#[command(visible_alias("l"))]
List(BranchListArgs),
#[command(visible_alias("r"))]
Rename(BranchRenameArgs),
#[command(visible_alias("s"))]
Set(BranchSetArgs),
Track(BranchTrackArgs),
@ -122,6 +124,19 @@ pub struct BranchForgetArgs {
pub glob: Vec<StringPattern>,
}
/// Rename `old` branch name to `new` branch name.
///
/// The new branch name points at the same commit as the old
/// branch name.
#[derive(clap::Args, Clone, Debug)]
pub struct BranchRenameArgs {
/// The old name of the branch.
pub old: String,
/// The new name of the branch.
pub new: String,
}
/// Update an existing branch to point to a certain commit.
#[derive(clap::Args, Clone, Debug)]
pub struct BranchSetArgs {
@ -248,6 +263,7 @@ pub fn cmd_branch(
) -> Result<(), CommandError> {
match subcommand {
BranchSubcommand::Create(sub_args) => cmd_branch_create(ui, command, sub_args),
BranchSubcommand::Rename(sub_args) => cmd_branch_rename(ui, command, sub_args),
BranchSubcommand::Set(sub_args) => cmd_branch_set(ui, command, sub_args),
BranchSubcommand::Delete(sub_args) => cmd_branch_delete(ui, command, sub_args),
BranchSubcommand::Forget(sub_args) => cmd_branch_forget(ui, command, sub_args),
@ -301,6 +317,40 @@ fn cmd_branch_create(
Ok(())
}
fn cmd_branch_rename(
ui: &mut Ui,
command: &CommandHelper,
args: &BranchRenameArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let view = workspace_command.repo().view();
let old_branch = &args.old;
let ref_target = view.get_local_branch(old_branch).clone();
if ref_target.is_absent() {
return Err(user_error(format!("No such branch: {old_branch}")));
}
let new_branch = &args.new;
if view.get_local_branch(new_branch).is_present() {
return Err(user_error(format!("Branch already exists: {new_branch}")));
}
let mut tx = workspace_command.start_transaction();
tx.mut_repo()
.set_local_branch_target(new_branch, ref_target);
tx.mut_repo()
.set_local_branch_target(old_branch, RefTarget::absent());
tx.finish(
ui,
format!(
"rename {} to {}",
make_branch_term(&[old_branch]),
make_branch_term(&[new_branch]),
),
)?;
Ok(())
}
fn cmd_branch_set(
ui: &mut Ui,
command: &CommandHelper,

View file

@ -189,6 +189,29 @@ fn test_branch_move_conflicting() {
"###);
}
#[test]
fn test_branch_rename() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "rename", "foo", "bar"]);
insta::assert_snapshot!(stderr, @r###"
Error: No such branch: foo
"###);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "rename", "foo", "bar"]);
insta::assert_snapshot!(stderr, @"");
test_env.jj_cmd_ok(&repo_path, &["new"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "conflictfoo"]);
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "rename", "bar", "conflictfoo"]);
insta::assert_snapshot!(stderr, @r###"
Error: Branch already exists: conflictfoo
"###);
}
#[test]
fn test_branch_forget_glob() {
let test_env = TestEnvironment::default();