diff --git a/CHANGELOG.md b/CHANGELOG.md index ac78133347..d61bec8b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added completions for [Nushell](https://nushell.sh) to `jj util completion` +* `jj branch list` now supports a `--tracked/-t` option which can be used to + show tracked branches only. Omits local Git-tracking branches by default. + ### Fixed bugs * On Windows, symlinks in the repo are now materialized as regular files in the diff --git a/cli/src/commands/branch.rs b/cli/src/commands/branch.rs index 570da5d78a..b4b5e82c44 100644 --- a/cli/src/commands/branch.rs +++ b/cli/src/commands/branch.rs @@ -100,9 +100,14 @@ pub struct BranchDeleteArgs { pub struct BranchListArgs { /// Show all tracking and non-tracking remote branches including the ones /// whose targets are synchronized with the local branches. - #[arg(long, short, conflicts_with_all = ["names", "revisions"])] + #[arg(long, short, conflicts_with_all = ["names", "revisions", "tracked"])] all: bool, + /// Show remote tracked branches only. Omits local Git-tracking branches by + /// default. + #[arg(long, short, conflicts_with_all = ["all"])] + tracked: bool, + /// Show branches whose local name matches /// /// By default, the specified name matches exactly. Use `glob:` prefix to @@ -679,13 +684,19 @@ fn cmd_branch_list( .map_or(true, |branch_names| branch_names.contains(name)) }); for (name, branch_target) in branches_to_list { - let (tracking_remote_refs, untracked_remote_refs) = - branch_target - .remote_refs - .into_iter() - .partition::, _>(|&(_, remote_ref)| remote_ref.is_tracking()); + let (mut tracking_remote_refs, untracked_remote_refs) = branch_target + .remote_refs + .into_iter() + .partition::, _>(|&(_, remote_ref)| remote_ref.is_tracking()); + + if args.tracked { + tracking_remote_refs + .retain(|&(remote, _)| remote != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO); + } - if branch_target.local_target.is_present() || !tracking_remote_refs.is_empty() { + if !args.tracked && branch_target.local_target.is_present() + || !tracking_remote_refs.is_empty() + { write!(formatter.labeled("branch"), "{name}")?; if branch_target.local_target.is_present() { print_branch_target(formatter, branch_target.local_target)?; @@ -696,7 +707,8 @@ fn cmd_branch_list( for &(remote, remote_ref) in &tracking_remote_refs { let synced = remote_ref.target == *branch_target.local_target; - if !args.all && synced { + + if !args.all && !args.tracked && synced { continue; } write!(formatter, " ")?; diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index 3201a151db..8b76fb0193 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -2,7 +2,6 @@ source: cli/tests/test_generate_md_cli_help.rs description: "AUTO-GENERATED FILE, DO NOT EDIT. This cli reference is generated as an `insta` snapshot. MkDocs follows they symlink from docs/cli-reference.md to the snap. Unfortunately, `insta` unavoidably creates this header. Luckily, MkDocs ignores the header since it has the same format as Markdown headers. TODO: MkDocs may fail on Windows if symlinks are not enabled in the OS settings" --- - # Command-Line Help for `jj` @@ -302,6 +301,10 @@ For information about branches, see https://github.com/martinvonz/jj/blob/main/d Possible values: `true`, `false` +* `-t`, `--tracked` — Show remote tracked branches only. Omits local Git-tracking branches by default + + Possible values: `true`, `false` + * `-r`, `--revisions ` — Show branches whose local targets are in the given revisions @@ -1972,4 +1975,3 @@ For information about stale working copies, see https://github.com/martinvonz/jj This document was generated automatically by clap-markdown. - diff --git a/cli/tests/test_branch_command.rs b/cli/tests/test_branch_command.rs index 3795ae1893..a37ef165b0 100644 --- a/cli/tests/test_branch_command.rs +++ b/cli/tests/test_branch_command.rs @@ -1206,6 +1206,148 @@ fn test_branch_list_much_remote_divergence() { "###); } +#[test] +fn test_branch_list_tracked() { + let test_env = TestEnvironment::default(); + test_env.add_config("git.auto-local-branch = true"); + + // Initialize remote refs + test_env.jj_cmd_ok(test_env.env_root(), &["init", "remote", "--git"]); + let remote_path = test_env.env_root().join("remote"); + for branch in [ + "remote-sync", + "remote-unsync", + "remote-untrack", + "remote-delete", + ] { + test_env.jj_cmd_ok(&remote_path, &["new", "root()", "-m", branch]); + test_env.jj_cmd_ok(&remote_path, &["branch", "create", branch]); + } + test_env.jj_cmd_ok(&remote_path, &["new"]); + test_env.jj_cmd_ok(&remote_path, &["git", "export"]); + + // Initialize local refs + let mut remote_git_path = test_env.env_root().join("remote"); + remote_git_path.extend([".jj", "repo", "store", "git"]); + test_env.jj_cmd_ok( + test_env.env_root(), + &[ + "git", + "clone", + "--colocate", + remote_git_path.to_str().unwrap(), + "local", + ], + ); + + test_env.jj_cmd_ok(test_env.env_root(), &["init", "upstream", "--git"]); + + // Initialize a second remote + let mut upstream_git_path = test_env.env_root().join("upstream"); + test_env.jj_cmd_ok( + &upstream_git_path, + &["new", "root()", "-m", "upstream-sync"], + ); + test_env.jj_cmd_ok(&upstream_git_path, &["branch", "create", "upstream-sync"]); + test_env.jj_cmd_ok(&upstream_git_path, &["new"]); + test_env.jj_cmd_ok(&upstream_git_path, &["git", "export"]); + + upstream_git_path.extend([".jj", "repo", "store", "git"]); + + let local_path = test_env.env_root().join("local"); + + test_env.jj_cmd_ok( + &local_path, + &[ + "git", + "remote", + "add", + "upstream", + upstream_git_path.to_str().unwrap(), + ], + ); + test_env.jj_cmd_ok(&local_path, &["git", "fetch", "--all-remotes"]); + + test_env.jj_cmd_ok(&local_path, &["new", "root()", "-m", "local-only"]); + test_env.jj_cmd_ok(&local_path, &["branch", "create", "local-only"]); + + // Mutate refs in local repository + test_env.jj_cmd_ok(&local_path, &["branch", "delete", "remote-delete"]); + test_env.jj_cmd_ok(&local_path, &["branch", "delete", "remote-untrack"]); + test_env.jj_cmd_ok(&local_path, &["branch", "untrack", "remote-untrack@origin"]); + test_env.jj_cmd_ok( + &local_path, + &[ + "git", + "push", + "--remote", + "upstream", + "--branch", + "remote-unsync", + ], + ); + test_env.jj_cmd_ok( + &local_path, + &["branch", "set", "--allow-backwards", "remote-unsync"], + ); + + insta::assert_snapshot!( + test_env.jj_cmd_success(&local_path, &["branch", "list", "--all"]), @r###" + local-only: nmzmmopx e1da745b (empty) local-only + @git: nmzmmopx e1da745b (empty) local-only + remote-delete (deleted) + @origin: mnmymoky 203e60eb (empty) remote-delete + (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) + remote-sync: zwtyzrop c761c7ea (empty) remote-sync + @git: zwtyzrop c761c7ea (empty) remote-sync + @origin: zwtyzrop c761c7ea (empty) remote-sync + remote-unsync: nmzmmopx e1da745b (empty) local-only + @git: nmzmmopx e1da745b (empty) local-only + @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync + @upstream (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync + remote-untrack@origin: vmortlor 71a16b05 (empty) remote-untrack + upstream-sync: lolpmnqw 32fa6da0 (empty) upstream-sync + @git: lolpmnqw 32fa6da0 (empty) upstream-sync + @upstream: lolpmnqw 32fa6da0 (empty) upstream-sync + "###); + + insta::assert_snapshot!( + test_env.jj_cmd_success(&local_path, &["branch", "list", "--tracked"]), @r###" + remote-delete (deleted) + @origin: mnmymoky 203e60eb (empty) remote-delete + (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) + remote-sync: zwtyzrop c761c7ea (empty) remote-sync + @origin: zwtyzrop c761c7ea (empty) remote-sync + remote-unsync: nmzmmopx e1da745b (empty) local-only + @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync + @upstream (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync + upstream-sync: lolpmnqw 32fa6da0 (empty) upstream-sync + @upstream: lolpmnqw 32fa6da0 (empty) upstream-sync + "### + ); + + insta::assert_snapshot!( + test_env.jj_cmd_success(&local_path, &["branch", "list", "--tracked", "remote-unsync"]), @r###" + remote-unsync: nmzmmopx e1da745b (empty) local-only + @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync + @upstream (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync + "###); + + insta::assert_snapshot!( + test_env.jj_cmd_success(&local_path, &["branch", "list", "--tracked", "remote-untrack"]), @""); + + test_env.jj_cmd_ok( + &local_path, + &["branch", "untrack", "remote-unsync@upstream"], + ); + + insta::assert_snapshot!( + test_env.jj_cmd_success(&local_path, &["branch", "list", "--tracked", "remote-unsync"]), @r###" + remote-unsync: nmzmmopx e1da745b (empty) local-only + @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync + "###); +} + 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]) diff --git a/docs/branches.md b/docs/branches.md index aec2b04142..0173b52664 100644 --- a/docs/branches.md +++ b/docs/branches.md @@ -113,6 +113,14 @@ $ # The local branch (e.g. stuff) is unaffected. It may or may not still $ # be tracking branches on other remotes (e.g. stuff@upstream). ``` +### Listing tracked branches + +To list tracked branches, you can `jj branch list --tracked` or `jj branch list -t`. +This command omits local Git-tracking branches by default. + +You can see if a specific branch is tracked with `jj branch list --tracked `. + + ### Automatic tracking of branches & `git.auto-local-branch` option There are two situations where `jj` tracks branches automatically. `jj git