diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b48d6c378..04c5bf7af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/cli/src/commands/branch.rs b/cli/src/commands/branch.rs index 35a5a91448..9469ec1a9b 100644 --- a/cli/src/commands/branch.rs +++ b/cli/src/commands/branch.rs @@ -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, } +/// 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,41 @@ 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 repo = workspace_command.repo().as_ref(); + let old_branch = &args.old; + // call clone because .set_local_branch_target takes ownership + // of the target_commit, so we need to create a copy that is no + // longer owned by the workspace. + let target_commit = repo.view().get_local_branch(old_branch).clone(); + if target_commit.is_absent() { + return Err(user_error_with_hint(format!( + "No such branch: {old_branch}" + ))); + } + + let new_branch = &args.new; + let mut tx = workspace_command.start_transaction(); + tx.mut_repo() + .set_local_branch_target(new_branch, target_commit); + 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,