Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ should not be broken.
* `jj log` now supports a `--count` flag to print the number of commits instead
of displaying them.

* `Commit` type now has a `conflicted_files()` method that returns a list of
files with merge conflicts.

* `TreeEntry` type now has a `num_conflict_sides()` method that returns the number
of sides in a merge conflict (1 for non-conflicted files, 2 or more for
conflicts).

### Fixed bugs

* `jj fix` now prints a warning if a tool failed to run on a file.
Expand Down
25 changes: 25 additions & 0 deletions cli/src/commit_templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,22 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
Ok(out_property.into_dyn_wrapped())
},
);
map.insert(
"conflicted_files",
|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property.and_then(|commit| {
let tree = commit.tree();
let entries: Vec<_> = tree
.conflicts()
.map(|(path, value)| value.map(|value| (path, value)))
.map_ok(|(path, value)| TreeEntry { path, value })
.try_collect()?;
Ok(entries)
});
Ok(out_property.into_dyn_wrapped())
},
);
map.insert(
"root",
|language, _diagnostics, _build_ctx, self_property, function| {
Expand Down Expand Up @@ -2446,6 +2462,15 @@ fn builtin_tree_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
Ok(out_property.into_dyn_wrapped())
},
);
map.insert(
"num_conflict_sides",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I slightly prefer conflict_sides or conflict_side_count, but I don't feel strongly.

Another idea is to add added/removed_entries() -> List<TreeEntry>.

|_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?;
let out_property = self_property
.and_then(|entry| Ok(i64::try_from(entry.value.simplify().num_sides())?));
Ok(out_property.into_dyn_wrapped())
},
);
map.insert(
"file_type",
|_language, _diagnostics, _build_ctx, self_property, function| {
Expand Down
84 changes: 84 additions & 0 deletions cli/tests/test_commit_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,90 @@ fn test_file_list_entries() {
");
}

#[test]
fn test_conflicted_files() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");

// Base commit with files.
work_dir.write_file("normal-file", "base content");
work_dir.write_file("conflict-file1", "base content");
work_dir.write_file("conflict-file2", "base content");
work_dir.run_jj(["commit", "-m", "base"]).success();

// Sibling branches with conflicting changes.
work_dir.write_file("conflict-file1", "left content");
work_dir.write_file("conflict-file2", "left content");
work_dir.run_jj(["commit", "-m", "left"]).success();

work_dir.run_jj(["new", "description(base)"]).success();
work_dir.write_file("conflict-file1", "right content");
work_dir.write_file("conflict-file2", "right content");
work_dir.run_jj(["commit", "-m", "right"]).success();

// Merge with conflicts.
work_dir
.run_jj(["new", "description(left)", "description(right)"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use subject(glob:left) (it's equivalent to exact:left, but I'll remove glob: when it becomes the default.)

.success();

// Test basic conflicted_files() listing.
let template = r#"self.conflicted_files().map(|e| e.path()) ++ "\n""#;
let output = work_dir.run_jj(["log", "-r", "@", "-T", template, "--no-graph"]);
insta::assert_snapshot!(output, @r"
conflict-file1 conflict-file2
[EOF]
");

// Test that non-conflicted commit returns empty list.
let template = r#"self.conflicted_files().len() ++ "\n""#;
let output = work_dir.run_jj([
"log",
"-r",
"description(substring:\"left\")",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: same here. subject(glob:left)

"-T",
template,
"--no-graph",
]);
insta::assert_snapshot!(output, @r"
0
[EOF]
");

// Metadata for conflicted_files.
let template = indoc! {r#"
self.conflicted_files().map(|e| separate(" ",
e.path(),
"conflict=" ++ e.conflict(),
"type=" ++ e.file_type(),
"sides=" ++ e.num_conflict_sides(),
)).join("\n") ++ "\n"
"#};
let output = work_dir.run_jj(["log", "-r", "@", "-T", template, "--no-graph"]);
insta::assert_snapshot!(output, @r"
conflict-file1 conflict=true type=conflict sides=2
conflict-file2 conflict=true type=conflict sides=2
[EOF]
");

// Metadata for all files.
let template = indoc! {r#"
self.files().map(|e| separate(" ",
e.path(),
"conflict=" ++ e.conflict(),
"type=" ++ e.file_type(),
"sides=" ++ e.num_conflict_sides(),
)).join("\n") ++ "\n"
"#};
let output = work_dir.run_jj(["log", "-r", "@", "-T", template, "--no-graph"]);
insta::assert_snapshot!(output, @r"
conflict-file1 conflict=true type=conflict sides=2
conflict-file2 conflict=true type=conflict sides=2
normal-file conflict=false type=file sides=1
[EOF]
");
}

#[cfg(unix)]
#[test]
fn test_file_list_symlink() {
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/test_templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn test_templater_parse_error() {
| ^-------^
|
= Keyword `conflicts` doesn't exist
Hint: Did you mean `conflict`, `conflicting`?
Hint: Did you mean `conflict`, `conflicted_files`, `conflicting`?
[EOF]
[exit status: 1]
");
Expand Down
3 changes: 3 additions & 0 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ This type cannot be printed. The following methods are defined.
* `.files([files: String]) -> List<TreeEntry>`: Files that exist in this commit,
matching [the `files` expression](filesets.md). Use `.diff().files()` to list
changed files.
* `.conflicted_files() -> List<TreeEntry>`: Conflicted files in this commit.
* `.root() -> Boolean`: True if the commit is the root commit.

### `CommitEvolutionEntry` type
Expand Down Expand Up @@ -614,6 +615,8 @@ This type cannot be printed. The following methods are defined.

* `.path() -> RepoPath`: Path to the entry.
* `.conflict() -> Boolean`: True if the entry is a merge conflict.
* `.num_conflict_sides() -> Integer`: Number of sides in the merge conflict (1 if not
conflicted, 2 or more for multi-way merges).
* `.file_type() -> String`: One of `"file"`, `"symlink"`, `"tree"`,
`"git-submodule"`, or `"conflict"`.
* `.executable() -> Boolean`: True if the entry is an executable file.
Expand Down