Skip to content

Commit

Permalink
Implement a rename subcommand for the branch command.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
essiene committed Dec 16, 2023
1 parent d39843b commit f76c549
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
54 changes: 54 additions & 0 deletions cli/src/commands/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -301,6 +317,44 @@ 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 view = repo.view();
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 = view.get_local_branch(old_branch).clone();
if target_commit.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, 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,
Expand Down
25 changes: 25 additions & 0 deletions cli/tests/test_branch_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,31 @@ 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
"###);

// we will conflict with this in a later test.
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "conflictfoo"]);

test_env.jj_cmd_ok(&repo_path, &["new"]);
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, @"");

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();
Expand Down

0 comments on commit f76c549

Please sign in to comment.