Skip to content

Commit

Permalink
next/prev: Implement next/prev --conflict
Browse files Browse the repository at this point in the history
This allows users to jump to the next conflict in the ancestors or children of 
the start commit.

Continues work on #2126

Co-Authored-By: Noah Mayr <[email protected]>
  • Loading branch information
PhilipMetzger and noahmayr committed May 12, 2024
1 parent 8239ad8 commit ce831db
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ No code changes (fixing Rust `Cargo.toml` stuff).
defined template names when no argument is given, assisting the user in making
a selection.

* `ui.default-command` now accepts multiple string arguments, for more complex
default `jj` commands.

* `jj prev` and `jj next` have gained a `--conflict` flag which moves you
to the next conflict in a child commit.

### Fixed bugs

* On Windows, symlinks in the repo are now supported when Developer Mode is enabled.
Expand Down
13 changes: 13 additions & 0 deletions cli/src/commands/next.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ pub(crate) struct NextArgs {
/// edit`).
#[arg(long, short)]
edit: bool,
/// Jump to the next conflicted descendant.
#[arg(long, conflicts_with = "offset")]
conflict: bool,
}

pub fn choose_commit<'a>(
Expand Down Expand Up @@ -114,6 +117,7 @@ pub(crate) fn cmd_next(
.view()
.heads()
.contains(current_wc_id);
let conflict = args.conflict;
let wc_revset = RevsetExpression::commit(current_wc_id.clone());
// If we're editing, start at the working-copy commit. Otherwise, start from
// its direct parent(s).
Expand All @@ -127,11 +131,20 @@ pub(crate) fn cmd_next(
// unnecessary now that --edit is implied if `@` has descendants.
.minus(&wc_revset)
};
let target_revset = if conflict {
target_revset
.descendants()
.conflicts()
.minus(&RevsetExpression::is_empty())
} else {
target_revset
};
let targets: Vec<Commit> = target_revset
.evaluate_programmatic(workspace_command.repo().as_ref())?
.iter()
.commits(workspace_command.repo().store())
.try_collect()?;

let target = match targets.as_slice() {
[target] => target,
[] => {
Expand Down
12 changes: 12 additions & 0 deletions cli/src/commands/prev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ pub(crate) struct PrevArgs {
/// Edit the parent directly, instead of moving the working-copy commit.
#[arg(long, short)]
edit: bool,
/// Jump to the next conflicted ancestor.
#[arg(long, conflicts_with = "offset")]
conflict: bool,
}

pub(crate) fn cmd_prev(
Expand All @@ -67,6 +70,7 @@ pub(crate) fn cmd_prev(
args: &PrevArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let conflict = args.conflict;
let current_wc_id = workspace_command
.get_wc_commit_id()
.ok_or_else(|| user_error("This command requires a working copy"))?;
Expand All @@ -85,6 +89,14 @@ pub(crate) fn cmd_prev(
.parents()
.ancestors_at(args.offset)
};
let target_revset = if conflict {
target_revset
.ancestors()
.conflicts()
.minus(&RevsetExpression::is_empty())
} else {
target_revset
};
let targets: Vec<_> = target_revset
.evaluate_programmatic(workspace_command.repo().as_ref())?
.iter()
Expand Down
8 changes: 8 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,10 @@ implied.
Possible values: `true`, `false`
* `--conflict` — Jump to the next conflicted descendant
Possible values: `true`, `false`
Expand Down Expand Up @@ -1444,6 +1448,10 @@ implied.
Possible values: `true`, `false`
* `--conflict` — Jump to the next conflicted ancestor
Possible values: `true`, `false`
Expand Down
121 changes: 121 additions & 0 deletions cli/tests/test_next_prev_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,127 @@ fn test_next_editing() {
"###);
}

#[test]
fn test_prev_conflict() {
// Make the third commit our new parent.
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 file_path = repo_path.join("content.txt");
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "first"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "second"]);
std::fs::write(&file_path, "Other text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "third"]);
// Create a conflict in the third commit, where we'll jump to.
test_env.jj_cmd_ok(&repo_path, &["edit", "description(second)"]);
std::fs::write(&file_path, "some text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new", "description(third)"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "fourth"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["prev", "--conflict"]);
// We now should be a child of `third`.
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Working copy now at: vruxwmqv 61428b9f (conflict) (empty) (no description set)
Parent commit : kkmpptxz 9de45eb7 (conflict) third
There are unresolved conflicts at these paths:
content.txt 2-sided conflict
"###);
}

#[test]
fn test_prev_conflict_editing() {
// Edit the third commit.
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 file_path = repo_path.join("content.txt");
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "first"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "second"]);
std::fs::write(&file_path, "Other text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "third"]);
// Create a conflict in the third commit, where we'll jump to.
test_env.jj_cmd_ok(&repo_path, &["edit", "description(second)"]);
std::fs::write(&file_path, "some text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new", "description(third)"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "fourth"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["prev", "--conflict", "--edit"]);
// We now should be editing the third commit.
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Working copy now at: kkmpptxz 9de45eb7 (conflict) third
Parent commit : rlvkpnrz 374aea34 second
There are unresolved conflicts at these paths:
content.txt 2-sided conflict
"###);
}

#[test]
fn test_next_conflict() {
// There is a conflict in the third commit, so after next it should be the new
// parent.
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 file_path = repo_path.join("content.txt");
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "first"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "second"]);
std::fs::write(&file_path, "Other text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "third"]);
// Create a conflict in the third commit.
test_env.jj_cmd_ok(&repo_path, &["edit", "description(second)"]);
std::fs::write(&file_path, "some text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new", "description(third)"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "fourth"]);
test_env.jj_cmd_ok(&repo_path, &["edit", "@---"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["next", "--conflict"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Working copy now at: kkmpptxz 9de45eb7 (conflict) third
Parent commit : rlvkpnrz 374aea34 second
Added 0 files, modified 1 files, removed 0 files
There are unresolved conflicts at these paths:
content.txt 2-sided conflict
"###);
// --edit is implied when already editing a non-head commit
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["next"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Working copy now at: royxmykx 311dd604 (conflict) (empty) fourth
Parent commit : kkmpptxz 9de45eb7 (conflict) third
There are unresolved conflicts at these paths:
content.txt 2-sided conflict
"###);
}

#[test]
fn test_next_conflict_editing() {
// There is a conflict in the third commit, so after next it should be our
// working copy.
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 file_path = repo_path.join("content.txt");
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "first"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "second"]);
std::fs::write(&file_path, "Other text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "third"]);
// Create a conflict in the third commit.
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "third"]);
std::fs::write(&file_path, "Sir, we've got a problem").unwrap();
test_env.jj_cmd_ok(&repo_path, &["edit", "description(second)"]);
std::fs::write(&file_path, "some text").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new", "@+ ~ @"]);
test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "fourth"]);
test_env.jj_cmd_ok(&repo_path, &["edit", "@---"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["next", "--conflict", "--edit"]);
// We now should be editing the third commit.
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Working copy now at: kkmpptxz 9de45eb7 (empty) third
Parent commit : rlvkpnrz 374aea34 (empty) second
"###);
}

fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
let template = r#"separate(" ", change_id.short(), local_branches, description)"#;
test_env.jj_cmd_success(cwd, &["log", "-T", template])
Expand Down

0 comments on commit ce831db

Please sign in to comment.