diff --git a/CHANGELOG.md b/CHANGELOG.md index 27aea287c..131355fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### New features + +* The new `jj git remote rename` command allows git remotes to be renamed + in-place. + ## [0.5.1] - 2022-10-17 No changes (just trying to get automated GitHub release to work). diff --git a/lib/src/repo.rs b/lib/src/repo.rs index 36b633919..f39baa37f 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -754,6 +754,10 @@ impl MutableRepo { self.view_mut().remove_remote_branch(name, remote_name); } + pub fn rename_remote(&mut self, old: &str, new: &str) { + self.view_mut().rename_remote(old, new); + } + pub fn get_tag(&self, name: &str) -> Option { self.view.borrow().get_tag(name) } diff --git a/lib/src/view.rs b/lib/src/view.rs index a55f775f9..e9f222595 100644 --- a/lib/src/view.rs +++ b/lib/src/view.rs @@ -212,6 +212,14 @@ impl View { } } + pub fn rename_remote(&mut self, old: &str, new: &str) { + for branch in self.data.branches.values_mut() { + if let Some(target) = branch.remote_targets.remove(old) { + branch.remote_targets.insert(new.to_owned(), target); + } + } + } + pub fn get_tag(&self, name: &str) -> Option { self.data.tags.get(name).cloned() } diff --git a/lib/tests/test_mut_repo.rs b/lib/tests/test_mut_repo.rs index f9d5f3997..043977959 100644 --- a/lib/tests/test_mut_repo.rs +++ b/lib/tests/test_mut_repo.rs @@ -612,3 +612,19 @@ fn test_rebase_descendants_conflicting_rewrite(use_git: bool) { .unwrap() .is_none()); } + +#[test_case(false ; "local backend")] +#[test_case(true ; "git backend")] +fn test_rename_remote(use_git: bool) { + let settings = testutils::user_settings(); + let test_repo = TestRepo::init(use_git); + let repo = &test_repo.repo; + let mut tx = repo.start_transaction("test"); + let mut_repo = tx.mut_repo(); + let commit = testutils::create_random_commit(&settings, repo).write_to_repo(mut_repo); + let target = RefTarget::Normal(commit.id().clone()); + mut_repo.set_remote_branch("main".to_string(), "origin".to_string(), target.clone()); + mut_repo.rename_remote("origin", "upstream"); + assert_eq!(mut_repo.get_remote_branch("main", "upstream"), Some(target)); + assert_eq!(mut_repo.get_remote_branch("main", "origin"), None); +} diff --git a/src/commands.rs b/src/commands.rs index 804f7efa7..cc7cd3eb5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -843,6 +843,7 @@ enum GitCommands { enum GitRemoteCommands { Add(GitRemoteAddArgs), Remove(GitRemoteRemoveArgs), + Rename(GitRemoteRenameArgs), List(GitRemoteListArgs), } @@ -862,6 +863,15 @@ struct GitRemoteRemoveArgs { remote: String, } +/// Rename a Git remote +#[derive(clap::Args, Clone, Debug)] +struct GitRemoteRenameArgs { + /// The name of an existing remote + old: String, + /// The desired name for `old` + new: String, +} + /// List Git remotes #[derive(clap::Args, Clone, Debug)] struct GitRemoteListArgs {} @@ -3967,6 +3977,29 @@ fn cmd_git_remote_remove( Ok(()) } +fn cmd_git_remote_rename( + ui: &mut Ui, + command: &CommandHelper, + args: &GitRemoteRenameArgs, +) -> Result<(), CommandError> { + let mut workspace_command = command.workspace_helper(ui)?; + let repo = workspace_command.repo(); + let git_repo = get_git_repo(repo.store())?; + if git_repo.find_remote(&args.old).is_err() { + return Err(CommandError::UserError("Remote doesn't exist".to_string())); + } + git_repo + .remote_rename(&args.old, &args.new) + .map_err(|err| CommandError::UserError(err.to_string()))?; + let mut tx = workspace_command + .start_transaction(&format!("rename git remote {} to {}", &args.old, &args.new)); + tx.mut_repo().rename_remote(&args.old, &args.new); + if tx.mut_repo().has_changes() { + workspace_command.finish_transaction(ui, tx)?; + } + Ok(()) +} + fn cmd_git_remote_list( ui: &mut Ui, command: &CommandHelper, @@ -4419,6 +4452,9 @@ fn cmd_git( GitCommands::Remote(GitRemoteCommands::Remove(command_matches)) => { cmd_git_remote_remove(ui, command, command_matches) } + GitCommands::Remote(GitRemoteCommands::Rename(command_matches)) => { + cmd_git_remote_rename(ui, command, command_matches) + } GitCommands::Remote(GitRemoteCommands::List(command_matches)) => { cmd_git_remote_list(ui, command, command_matches) } diff --git a/tests/test_git_remotes.rs b/tests/test_git_remotes.rs index 85a09b67c..792eacbd4 100644 --- a/tests/test_git_remotes.rs +++ b/tests/test_git_remotes.rs @@ -49,3 +49,21 @@ fn test_git_remotes() { insta::assert_snapshot!(stderr, @"Error: Remote doesn't exist "); } + +#[test] +fn test_git_remote_rename() { + let test_env = TestEnvironment::default(); + + test_env.jj_cmd_success(test_env.env_root(), &["init", "--git", "repo"]); + let repo_path = test_env.env_root().join("repo"); + test_env.jj_cmd_success( + &repo_path, + &["git", "remote", "add", "foo", "http://example.com/repo/foo"], + ); + let stderr = test_env.jj_cmd_failure(&repo_path, &["git", "remote", "rename", "bar", "foo"]); + insta::assert_snapshot!(stderr, @"Error: Remote doesn't exist\n"); + let stdout = test_env.jj_cmd_success(&repo_path, &["git", "remote", "rename", "foo", "bar"]); + insta::assert_snapshot!(stdout, @""); + let stdout = test_env.jj_cmd_success(&repo_path, &["git", "remote", "list"]); + insta::assert_snapshot!(stdout, @"bar http://example.com/repo/foo"); +}