Repository: jj-vcs/jj (Rust)

Issue:
FR: Flag to only show conflicted branches
My main repository at work is a large-ish monorepo, so it has hundreds of branches at any given time. This is mostly fine, but when there is a conflict in some of them, filtering the list produced by `jj branch list` can be somewhat tedious (both because it is hard to find the specific branch, and because the conflict results in multiple lines of output that don't all contain the branch name). A workaround for this I've been using is `jj branch list | grep -A 6 conflicted` but it would be nice if jj could have a flag that only shows the conflicted branches and the conflict information.


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b579afc83e..394a3878576 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 * `jj git push` now prints messages from the remote.
 
+* `jj branch list` now supports a `--conflicted/-c` option to show only conflicted branches.
+
 ### Fixed bugs
 
 ## [0.15.1] - 2024-03-06
diff --git a/cli/src/commands/branch.rs b/cli/src/commands/branch.rs
index 21f260e2869..9dc4a6bac5e 100644
--- a/cli/src/commands/branch.rs
+++ b/cli/src/commands/branch.rs
@@ -109,6 +109,10 @@ pub struct BranchListArgs {
     #[arg(long, short, conflicts_with_all = ["all"])]
     tracked: bool,
 
+    /// Show conflicted branches only.
+    #[arg(long, short, conflicts_with_all = ["all"])]
+    conflicted: bool,
+
     /// Show branches whose local name matches
     ///
     /// By default, the specified name matches exactly. Use `glob:` prefix to
@@ -673,10 +677,11 @@ fn cmd_branch_list(
     let mut formatter = ui.stdout_formatter();
     let formatter = formatter.as_mut();
 
-    let branches_to_list = view.branches().filter(|&(name, _)| {
+    let branches_to_list = view.branches().filter(|(name, target)| {
         branch_names_to_list
             .as_ref()
             .map_or(true, |branch_names| branch_names.contains(name))
+            && (!args.conflicted || target.local_target.has_conflict())
     });
     for (name, branch_target) in branches_to_list {
         let (mut tracking_remote_refs, untracked_remote_refs) = branch_target


Test patch:
diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap
index f47d16c4906..420f3a616da 100644
--- a/cli/tests/cli-reference@.md.snap
+++ b/cli/tests/cli-reference@.md.snap
@@ -306,6 +306,10 @@ For information about branches, see https://github.com/martinvonz/jj/blob/main/d
 
   Possible values: `true`, `false`
 
+* `-c`, `--conflicted` — Show conflicted branches only
+
+  Possible values: `true`, `false`
+
 * `-r`, `--revisions <REVISIONS>` — Show branches whose local targets are in the given revisions
 
 
diff --git a/cli/tests/test_branch_command.rs b/cli/tests/test_branch_command.rs
index 4fcd6eaf026..e6b55a1fef7 100644
--- a/cli/tests/test_branch_command.rs
+++ b/cli/tests/test_branch_command.rs
@@ -1348,6 +1348,45 @@ fn test_branch_list_tracked() {
     "###);
 }
 
+#[test]
+fn test_branch_list_conflicted() {
+    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");
+
+    // Track existing branch. Local branch should result in conflict.
+    test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-m", "a"]);
+    test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-m", "b"]);
+    test_env.jj_cmd_ok(&repo_path, &["branch", "create", "bar"]);
+    test_env.jj_cmd_ok(
+        &repo_path,
+        &["branch", "create", "foo", "-r", "description(a)"],
+    );
+    test_env.jj_cmd_ok(
+        &repo_path,
+        &[
+            "branch",
+            "create",
+            "foo",
+            "-r",
+            "description(b)",
+            "--at-op=@-",
+        ],
+    );
+    test_env.jj_cmd_ok(&repo_path, &["status"]);
+    insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
+    bar: kkmpptxz 06a973bc (empty) b
+    foo (conflicted):
+      + rlvkpnrz d8d5f980 (empty) a
+      + kkmpptxz 06a973bc (empty) b
+    "###);
+    insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["branch", "list", "--conflicted"]), @r###"
+    foo (conflicted):
+      + rlvkpnrz d8d5f980 (empty) a
+      + kkmpptxz 06a973bc (empty) b
+    "###);
+}
+
 fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
     let template = r#"branches ++ " " ++ commit_id.short()"#;
     test_env.jj_cmd_success(cwd, &["log", "-T", template])