cli: make jj branch take subcommands, not flags

As per https://github.com/martinvonz/jj/issues/330.
This commit is contained in:
Waleed Khan 2022-06-05 20:23:01 -07:00 committed by Martin von Zweigbergk
parent 92b1ae8006
commit de1c8f0f37
11 changed files with 222 additions and 150 deletions

View file

@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `jj new` now always checks out the new commit (used to be only if the parent
was `@`).
* (#330) `jj branch` now uses subcommands like `jj branch create` and
`jj branch forget` instead of options like `jj branch --forget`.
### New features
* `jj rebase` now accepts a `--branch/-b <revision>` argument, which can be used

View file

@ -57,6 +57,7 @@ use jujutsu_lib::settings::UserSettings;
use jujutsu_lib::store::Store;
use jujutsu_lib::transaction::Transaction;
use jujutsu_lib::tree::{merge_trees, Tree, TreeDiffIterator, TreeMergeError};
use jujutsu_lib::view::View;
use jujutsu_lib::working_copy::{
CheckoutStats, LockedWorkingCopy, ResetError, SnapshotError, WorkingCopy,
};
@ -1129,8 +1130,8 @@ enum Commands {
Merge(MergeArgs),
Rebase(RebaseArgs),
Backout(BackoutArgs),
Branch(BranchArgs),
Branches(BranchesArgs),
#[clap(subcommand)]
Branch(BranchSubcommand),
/// Undo an operation (shortcut for `jj op undo`)
Undo(OperationUndoArgs),
Operation(OperationArgs),
@ -1614,44 +1615,69 @@ struct BackoutArgs {
destination: Vec<String>,
}
/// Create, update, or delete a branch
/// Manage branches.
///
/// For information about branches, see
/// https://github.com/martinvonz/jj/blob/main/docs/branches.md.
#[derive(clap::Args, Clone, Debug)]
struct BranchArgs {
/// The branch's target revision
#[clap(long, short, group = "action")]
revision: Option<String>,
#[derive(clap::Subcommand, Clone, Debug)]
enum BranchSubcommand {
/// Create a new branch.
#[clap(visible_alias("c"))]
Create {
/// The branch's target revision.
#[clap(long, short)]
revision: Option<String>,
/// Allow moving the branch backwards or sideways
#[clap(long, requires = "revision")]
allow_backwards: bool,
/// The branches to create.
#[clap(required = true)]
names: Vec<String>,
},
/// Delete the branch locally
/// Delete an existing branch and propagate the deletion to remotes on the
/// next push.
#[clap(visible_alias("d"))]
Delete {
/// The branches to delete.
#[clap(required = true)]
names: Vec<String>,
},
/// Delete the local version of an existing branch, without propagating the
/// deletion to remotes.
#[clap(visible_alias("f"))]
Forget {
/// The branches to delete.
#[clap(required = true)]
names: Vec<String>,
},
/// List branches and their targets
///
/// The deletion will be propagated to remotes on push.
#[clap(long, group = "action")]
delete: bool,
/// A remote branch will be included only if its target is different from
/// the local target. For a conflicted branch (both local and remote), old
/// target revisions are preceded by a "-" and new target revisions are
/// preceded by a "+". For information about branches, see
/// https://github.com/martinvonz/jj/blob/main/docs/branches.md.
#[clap(visible_alias("l"))]
List,
/// The name of the branch to move or delete
#[clap(long, group = "action")]
forget: bool,
/// Update a given branch to point to a certain commit.
#[clap(visible_alias("s"))]
Set {
/// The branch's target revision.
#[clap(long, short)]
revision: Option<String>,
/// The branches to update.
names: Vec<String>,
/// Allow moving the branch backwards or sideways.
#[clap(long)]
allow_backwards: bool,
/// The branches to update.
#[clap(required = true)]
names: Vec<String>,
},
}
/// List branches and their targets
///
/// A remote branch will be included only if its target is different from the
/// local target. For a conflicted branch (both local and remote), old target
/// revisions are preceded by a "-" and new target revisions are preceded by a
/// "+". For information about branches, see
/// https://github.com/martinvonz/jj/blob/main/docs/branches.md.
#[derive(clap::Args, Clone, Debug)]
struct BranchesArgs {}
/// Commands for working with the operation log
///
/// Commands for working with the operation log. For information about the
@ -4006,91 +4032,150 @@ 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> {
fn cmd_branch(
ui: &mut Ui,
command: &CommandHelper,
subcommand: &BranchSubcommand,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
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!(
let view = workspace_command.repo().view();
fn validate_branch_names_exist<'a>(
view: &'a View,
names: &'a [String],
) -> Result<(), CommandError> {
for branch_name in names {
if view.get_local_branch(branch_name).is_none() {
return 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")?;
)));
}
}
Ok(())
}
let branch_term = if branch_names.len() == 1 {
"branch"
} else {
"branches"
};
let branch_term = format!("{branch_term} {}", branch_names.join(", "));
if args.delete {
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);
}
workspace_command.finish_transaction(ui, tx)?;
} else if args.forget {
let mut tx = workspace_command.start_transaction(&format!("forget {branch_term}"));
for branch_name in branch_names {
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.as_deref().unwrap_or("@"))?;
if !args.allow_backwards
&& !branch_names.iter().all(|branch_name| {
is_fast_forward(
workspace_command.repo().as_repo_ref(),
branch_name,
target_commit.id(),
fn make_branch_term(branch_names: &[impl AsRef<str>]) -> String {
match branch_names {
[branch_name] => format!("branch {}", branch_name.as_ref()),
branch_names => {
format!(
"branches {}",
branch_names.iter().map(AsRef::as_ref).join(", ")
)
})
{
return Err(CommandError::UserError(
"Use --allow-backwards to allow moving a branch backwards or sideways".to_string(),
}
}
}
match subcommand {
BranchSubcommand::Create { revision, names } => {
let branch_names: Vec<&str> = names
.iter()
.map(|branch_name| match view.get_local_branch(branch_name) {
Some(_) => Err(CommandError::UserError(format!(
"Branch already exists: {} (use `jj branch set` to update it)",
branch_name
))),
None => Ok(branch_name.as_str()),
})
.try_collect()?;
if branch_names.len() > 1 {
ui.write_warn(format!(
"warning: Creating multiple branches ({}).\n",
branch_names.len()
))?;
}
let target_commit =
workspace_command.resolve_single_rev(ui, revision.as_deref().unwrap_or("@"))?;
let mut tx = workspace_command.start_transaction(&format!(
"create {} pointing to commit {}",
make_branch_term(&branch_names),
target_commit.id().hex()
));
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)?;
}
let mut tx = workspace_command.start_transaction(&format!(
"point {branch_term} to commit {}",
target_commit.id().hex()
));
for branch_name in branch_names {
tx.mut_repo().set_local_branch(
branch_name.to_string(),
RefTarget::Normal(target_commit.id().clone()),
);
BranchSubcommand::Set {
revision,
allow_backwards,
names: branch_names,
} => {
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, revision.as_deref().unwrap_or("@"))?;
if !allow_backwards
&& !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 {} to commit {}",
make_branch_term(branch_names),
target_commit.id().hex()
));
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)?;
}
BranchSubcommand::Delete { names } => {
validate_branch_names_exist(view, names)?;
let mut tx =
workspace_command.start_transaction(&format!("delete {}", make_branch_term(names)));
for branch_name in names {
tx.mut_repo().remove_local_branch(branch_name);
}
workspace_command.finish_transaction(ui, tx)?;
}
BranchSubcommand::Forget { names } => {
validate_branch_names_exist(view, names)?;
let mut tx =
workspace_command.start_transaction(&format!("forget {}", make_branch_term(names)));
for branch_name in names {
tx.mut_repo().remove_branch(branch_name);
}
workspace_command.finish_transaction(ui, tx)?;
}
BranchSubcommand::List => {
list_branches(ui, &workspace_command)?;
}
workspace_command.finish_transaction(ui, tx)?;
}
Ok(())
}
fn cmd_branches(
fn list_branches(
ui: &mut Ui,
command: &CommandHelper,
_args: &BranchesArgs,
workspace_command: &WorkspaceCommandHelper,
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let repo = workspace_command.repo();
let workspace_id = workspace_command.workspace_id();
@ -5153,7 +5238,6 @@ where
Commands::Rebase(sub_args) => cmd_rebase(ui, &command_helper, sub_args),
Commands::Backout(sub_args) => cmd_backout(ui, &command_helper, sub_args),
Commands::Branch(sub_args) => cmd_branch(ui, &command_helper, sub_args),
Commands::Branches(sub_args) => cmd_branches(ui, &command_helper, sub_args),
Commands::Undo(sub_args) => cmd_op_undo(ui, &command_helper, sub_args),
Commands::Operation(sub_args) => cmd_operation(ui, &command_helper, sub_args),
Commands::Workspace(sub_args) => cmd_workspace(ui, &command_helper, sub_args),

View file

@ -27,11 +27,11 @@ fn test_alias_basic() {
b = ["log", "-r", "@", "-T", "branches"]
"#,
);
test_env.jj_cmd_success(&repo_path, &["branch", "my-branch"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "my-branch"]);
let stdout = test_env.jj_cmd_success(&repo_path, &["b"]);
insta::assert_snapshot!(stdout, @r###"
@ my-branch
~
~
"###);
}

View file

@ -35,7 +35,7 @@ fn test_branch_multiple_names() {
let repo_path = test_env.env_root().join("repo");
let assert = test_env
.jj_cmd(&repo_path, &["branch", "foo", "bar"])
.jj_cmd(&repo_path, &["branch", "set", "foo", "bar"])
.assert()
.success();
insta::assert_snapshot!(get_stdout_string(&assert), @"");
@ -47,7 +47,7 @@ fn test_branch_multiple_names() {
o 000000000000
"###);
let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "--delete", "foo", "bar"]);
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
@ -55,21 +55,6 @@ fn test_branch_multiple_names() {
"###);
}
#[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()"#])
}

View file

@ -97,7 +97,7 @@ fn test_edit_merge() {
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
std::fs::write(repo_path.join("file2"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "b"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
std::fs::write(repo_path.join("file1"), "b\n").unwrap();
std::fs::write(repo_path.join("file2"), "b\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["co", "@-"]);

View file

@ -51,7 +51,7 @@ fn test_git_colocated_rebase_on_import() {
std::fs::write(workspace_root.join("file"), "contents").unwrap();
test_env.jj_cmd_success(&workspace_root, &["close", "-m", "add a file"]);
std::fs::write(workspace_root.join("file"), "modified").unwrap();
test_env.jj_cmd_success(&workspace_root, &["branch", "master"]);
test_env.jj_cmd_success(&workspace_root, &["branch", "set", "master"]);
test_env.jj_cmd_success(&workspace_root, &["close", "-m", "modify a file"]);
// TODO: We shouldn't need this command here to trigger an import of the
// refs/heads/master we just exported

View file

@ -36,7 +36,7 @@ fn test_git_push() {
// When pushing everything, won't push an open commit even if there's a branch
// on it
test_env.jj_cmd_success(&workspace_root, &["branch", "my-branch"]);
test_env.jj_cmd_success(&workspace_root, &["branch", "create", "my-branch"]);
let stdout = test_env.jj_cmd_success(&workspace_root, &["git", "push"]);
insta::assert_snapshot!(stdout, @r###"
Skipping branch 'my-branch' since it points to an open commit.
@ -63,7 +63,7 @@ fn test_git_push() {
test_env.jj_cmd_success(&workspace_root, &["close", "-m", "second"]);
std::fs::write(workspace_root.join("file"), "third").unwrap();
test_env.jj_cmd_success(&workspace_root, &["rebase", "-r", "@", "-d", "@--"]);
test_env.jj_cmd_success(&workspace_root, &["branch", "my-branch"]);
test_env.jj_cmd_success(&workspace_root, &["branch", "set", "my-branch"]);
test_env.jj_cmd_success(&workspace_root, &["close", "-m", "third"]);
let stderr = test_env.jj_cmd_failure(&workspace_root, &["git", "push"]);
insta::assert_snapshot!(stderr, @r###"

View file

@ -35,25 +35,25 @@ fn test_move() {
//
// When moving changes between e.g. C and F, we should not get unrelated changes
// from B and D.
test_env.jj_cmd_success(&repo_path, &["branch", "a"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
std::fs::write(repo_path.join("file2"), "a\n").unwrap();
std::fs::write(repo_path.join("file3"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "b"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
std::fs::write(repo_path.join("file3"), "b\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "c"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
std::fs::write(repo_path.join("file1"), "c\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["co", "a"]);
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "d"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "d"]);
std::fs::write(repo_path.join("file3"), "d\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "e"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "e"]);
std::fs::write(repo_path.join("file2"), "e\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "f"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "f"]);
std::fs::write(repo_path.join("file2"), "f\n").unwrap();
// Test the setup
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ -162,20 +162,20 @@ fn test_move_partial() {
// D B
// |/
// A
test_env.jj_cmd_success(&repo_path, &["branch", "a"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
std::fs::write(repo_path.join("file2"), "a\n").unwrap();
std::fs::write(repo_path.join("file3"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "b"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
std::fs::write(repo_path.join("file3"), "b\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "c"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
std::fs::write(repo_path.join("file1"), "c\n").unwrap();
std::fs::write(repo_path.join("file2"), "c\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["co", "a"]);
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "d"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "d"]);
std::fs::write(repo_path.join("file3"), "d\n").unwrap();
// Test the setup
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"

View file

@ -32,7 +32,7 @@ fn create_commit(test_env: &TestEnvironment, repo_path: &Path, name: &str, paren
test_env.jj_cmd_success(repo_path, &["co", "@-"]);
}
std::fs::write(repo_path.join(name), &format!("{name}\n")).unwrap();
test_env.jj_cmd_success(repo_path, &["branch", name]);
test_env.jj_cmd_success(repo_path, &["branch", "create", name]);
test_env.jj_cmd_success(repo_path, &["close", "-m", name]);
}

View file

@ -24,13 +24,13 @@ fn test_squash() {
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_success(&repo_path, &["branch", "a"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "b"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
std::fs::write(repo_path.join("file1"), "b\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "c"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
std::fs::write(repo_path.join("file1"), "c\n").unwrap();
// Test the setup
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ -82,10 +82,10 @@ fn test_squash() {
test_env.jj_cmd_success(&repo_path, &["undo"]);
test_env.jj_cmd_success(&repo_path, &["co", "b"]);
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "d"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "d"]);
std::fs::write(repo_path.join("file2"), "d\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["merge", "-m", "merge", "c", "d"]);
test_env.jj_cmd_success(&repo_path, &["branch", "e", "-r", "@+"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "e", "-r", "@+"]);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
o b9ad3fdfc2c4 e
|\
@ -131,15 +131,15 @@ fn test_squash_partial() {
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_success(&repo_path, &["branch", "a"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
std::fs::write(repo_path.join("file2"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "b"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
std::fs::write(repo_path.join("file1"), "b\n").unwrap();
std::fs::write(repo_path.join("file2"), "b\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "c"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
std::fs::write(repo_path.join("file1"), "c\n").unwrap();
std::fs::write(repo_path.join("file2"), "c\n").unwrap();
// Test the setup

View file

@ -24,13 +24,13 @@ fn test_unsquash() {
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_success(&repo_path, &["branch", "a"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "b"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
std::fs::write(repo_path.join("file1"), "b\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "c"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
std::fs::write(repo_path.join("file1"), "c\n").unwrap();
// Test the setup
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ -81,10 +81,10 @@ fn test_unsquash() {
test_env.jj_cmd_success(&repo_path, &["undo"]);
test_env.jj_cmd_success(&repo_path, &["co", "b"]);
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "d"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "d"]);
std::fs::write(repo_path.join("file2"), "d\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["merge", "-m", "merge", "c", "d"]);
test_env.jj_cmd_success(&repo_path, &["branch", "e", "-r", "@+"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "e", "-r", "@+"]);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
o b9ad3fdfc2c4 e
|\
@ -129,15 +129,15 @@ fn test_unsquash_partial() {
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_success(&repo_path, &["branch", "a"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "a"]);
std::fs::write(repo_path.join("file1"), "a\n").unwrap();
std::fs::write(repo_path.join("file2"), "a\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "b"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "b"]);
std::fs::write(repo_path.join("file1"), "b\n").unwrap();
std::fs::write(repo_path.join("file2"), "b\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
test_env.jj_cmd_success(&repo_path, &["branch", "c"]);
test_env.jj_cmd_success(&repo_path, &["branch", "create", "c"]);
std::fs::write(repo_path.join("file1"), "c\n").unwrap();
std::fs::write(repo_path.join("file2"), "c\n").unwrap();
// Test the setup