ok/jj
1
0
Fork 0
forked from mirrors/jj
jj/cli/tests/test_branch_command.rs
Yuya Nishihara ddc601fbf9 str_util: add regex pattern
This patch adds minimal support for the regex pattern. We might have to add
"regex-i:" for completeness, but it can be achieved by "regex:'(?i)..'".
2024-07-22 12:00:52 +09:00

1819 lines
70 KiB
Rust

// Copyright 2022 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::path::Path;
use crate::common::TestEnvironment;
#[test]
fn test_branch_multiple_names() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo", "bar"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Created 2 branches pointing to qpvuntsm 230dd059 bar foo | (empty) (no description set)
Hint: Use -r to specify the target revision.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar foo 230dd059e1b0
◆ 000000000000
"###);
test_env.jj_cmd_ok(&repo_path, &["new"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "set", "foo", "bar"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Moved 2 branches to zsuskuln 8bb159bc bar foo | (empty) (no description set)
Hint: Use -r to specify the target revision.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar foo 8bb159bc30a9
○ 230dd059e1b0
◆ 000000000000
"###);
let (stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "foo", "bar", "foo"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Deleted 2 branches.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ 8bb159bc30a9
○ 230dd059e1b0
◆ 000000000000
"###);
// Hint should be omitted if -r is specified
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "-r@-", "foo", "bar"]);
insta::assert_snapshot!(stderr, @r###"
Created 2 branches pointing to qpvuntsm 230dd059 bar foo | (empty) (no description set)
"###);
// Create and move with explicit -r
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "set", "-r@", "bar", "baz"]);
insta::assert_snapshot!(stderr, @r###"
Created 1 branches pointing to zsuskuln 8bb159bc bar baz | (empty) (no description set)
Moved 1 branches to zsuskuln 8bb159bc bar baz | (empty) (no description set)
Hint: Consider using `jj branch move` if your intention was to move existing branches.
"###);
// Noop changes should not be included in the stats
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "set", "-r@", "foo", "bar", "baz"]);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to zsuskuln 8bb159bc bar baz foo | (empty) (no description set)
"###);
}
#[test]
fn test_branch_at_root() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let (stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "fred", "-r=root()"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Created 1 branches pointing to zzzzzzzz 00000000 fred | (empty) (no description set)
"###);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Nothing changed.
Warning: Failed to export some branches:
fred: Ref cannot point to the root commit in Git
"###);
}
#[test]
fn test_branch_empty_name() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["branch", "create", ""]);
insta::assert_snapshot!(stderr, @r###"
error: a value is required for '<NAMES>...' but none was supplied
For more information, try '--help'.
"###);
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["branch", "set", ""]);
insta::assert_snapshot!(stderr, @r###"
error: a value is required for '<NAMES>...' but none was supplied
For more information, try '--help'.
"###);
}
#[test]
fn test_branch_move() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
git2::Repository::init_bare(git_repo_path).unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "move", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Error: No such branch: foo
"###);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "set", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Created 1 branches pointing to qpvuntsm 230dd059 foo | (empty) (no description set)
Hint: Consider using `jj branch move` if your intention was to move existing branches.
"###);
test_env.jj_cmd_ok(&repo_path, &["new"]);
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "create", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Error: Branch already exists: foo
Hint: Use `jj branch set` to update it.
"###);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "set", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to mzvwutvl 167f90e7 foo | (empty) (no description set)
"###);
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "set", "-r@-", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Error: Refusing to move branch backwards or sideways: foo
Hint: Use --allow-backwards to allow it.
"###);
let (_stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["branch", "set", "-r@-", "--allow-backwards", "foo"],
);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to qpvuntsm 230dd059 foo | (empty) (no description set)
"###);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "move", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to mzvwutvl 167f90e7 foo | (empty) (no description set)
"###);
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "move", "--to=@-", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Error: Refusing to move branch backwards or sideways: foo
Hint: Use --allow-backwards to allow it.
"###);
let (_stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["branch", "move", "--to=@-", "--allow-backwards", "foo"],
);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to qpvuntsm 230dd059 foo | (empty) (no description set)
"###);
// Delete branch locally, but is still tracking remote
test_env.jj_cmd_ok(&repo_path, &["describe", "@-", "-mcommit"]);
test_env.jj_cmd_ok(&repo_path, &["git", "push", "-r@-"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "foo"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
foo (deleted)
@origin: qpvuntsm 1eb845f3 (empty) commit
"###);
// Deleted tracking branch name should still be allocated
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "create", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Error: Tracked remote branches exist for deleted branch: foo
Hint: Use `jj branch set` to recreate the local branch. Run `jj branch untrack 'glob:foo@*'` to disassociate them.
"###);
// Restoring local target shouldn't invalidate tracking state
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "set", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to mzvwutvl 66d48752 foo* | (empty) (no description set)
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
foo: mzvwutvl 66d48752 (empty) (no description set)
@origin (behind by 1 commits): qpvuntsm 1eb845f3 (empty) commit
"###);
// Untracked remote branch shouldn't block creation of local branch
test_env.jj_cmd_ok(&repo_path, &["branch", "untrack", "foo@origin"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "foo"]);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Created 1 branches pointing to mzvwutvl 66d48752 foo | (empty) (no description set)
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
foo: mzvwutvl 66d48752 (empty) (no description set)
foo@origin: qpvuntsm 1eb845f3 (empty) commit
"###);
}
#[test]
fn test_branch_move_matching() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a1", "a2"]);
test_env.jj_cmd_ok(&repo_path, &["new", "-mhead1"]);
test_env.jj_cmd_ok(&repo_path, &["new", "root()"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b1"]);
test_env.jj_cmd_ok(&repo_path, &["new"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c1"]);
test_env.jj_cmd_ok(&repo_path, &["new", "-mhead2"]);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ a2781dd9ee37
○ c1 f4f38657a3dd
○ b1 f652c32197cf
│ ○ 6b5e840ea72b
│ ○ a1 a2 230dd059e1b0
├─╯
◆ 000000000000
"###);
// The default could be considered "--from=all() glob:*", but is disabled
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["branch", "move"]);
insta::assert_snapshot!(stderr, @r###"
error: the following required arguments were not provided:
<--from <REVISIONS>|NAMES>
Usage: jj branch move <--from <REVISIONS>|NAMES>
For more information, try '--help'.
"###);
// No branches pointing to the source revisions
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "move", "--from=none()"]);
insta::assert_snapshot!(stderr, @r###"
No branches to update.
"###);
// No matching branches within the source revisions
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "move", "--from=::@", "glob:a?"]);
insta::assert_snapshot!(stderr, @r###"
Error: No matching branches for patterns: a?
"###);
// Noop move
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "move", "--to=a1", "a2"]);
insta::assert_snapshot!(stderr, @r###"
No branches to update.
"###);
// Move from multiple revisions
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "move", "--from=::@"]);
insta::assert_snapshot!(stderr, @r###"
Moved 2 branches to vruxwmqv a2781dd9 b1 c1 | (empty) head2
Hint: Specify branch by name to update just one of the branches.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ b1 c1 a2781dd9ee37
○ f4f38657a3dd
○ f652c32197cf
│ ○ 6b5e840ea72b
│ ○ a1 a2 230dd059e1b0
├─╯
◆ 000000000000
"###);
test_env.jj_cmd_ok(&repo_path, &["undo"]);
// Try to move multiple branches, but one of them isn't fast-forward
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "move", "glob:?1"]);
insta::assert_snapshot!(stderr, @r###"
Error: Refusing to move branch backwards or sideways: a1
Hint: Use --allow-backwards to allow it.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ a2781dd9ee37
○ c1 f4f38657a3dd
○ b1 f652c32197cf
│ ○ 6b5e840ea72b
│ ○ a1 a2 230dd059e1b0
├─╯
◆ 000000000000
"###);
// Select by revision and name
let (_stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["branch", "move", "--from=::a1+", "--to=a1+", "glob:?1"],
);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to kkmpptxz 6b5e840e a1 | (empty) head1
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ a2781dd9ee37
○ c1 f4f38657a3dd
○ b1 f652c32197cf
│ ○ a1 6b5e840ea72b
│ ○ a2 230dd059e1b0
├─╯
◆ 000000000000
"###);
}
#[test]
fn test_branch_move_conflicting() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let get_log = || {
let template = r#"separate(" ", description.first_line(), branches)"#;
let (stdout, _stderr) = test_env.jj_cmd_ok(&repo_path, &["log", "-T", template]);
stdout
};
test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-mA0"]);
test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-mB0"]);
test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-mC0"]);
test_env.jj_cmd_ok(&repo_path, &["new", "description(A0)", "-mA1"]);
// Set up conflicting branch.
test_env.jj_cmd_ok(
&repo_path,
&["branch", "create", "-rdescription(A0)", "foo"],
);
test_env.jj_cmd_ok(
&repo_path,
&["branch", "create", "--at-op=@-", "-rdescription(B0)", "foo"],
);
insta::assert_snapshot!(get_log(), @r###"
@ A1
○ A0 foo??
│ ○ C0
├─╯
│ ○ B0 foo??
├─╯
"###);
// Can't move the branch to C0 since it's sibling.
let stderr =
test_env.jj_cmd_failure(&repo_path, &["branch", "set", "-rdescription(C0)", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Error: Refusing to move branch backwards or sideways: foo
Hint: Use --allow-backwards to allow it.
"###);
// Can move the branch to A1 since it's descendant of A0. It's not
// descendant of B0, though.
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "set", "-rdescription(A1)", "foo"]);
insta::assert_snapshot!(stderr, @r###"
Moved 1 branches to mzvwutvl 9328d344 foo | (empty) A1
"###);
insta::assert_snapshot!(get_log(), @r###"
@ A1 foo
○ A0
│ ○ C0
├─╯
│ ○ B0
├─╯
"###);
}
#[test]
fn test_branch_rename() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
git2::Repository::init_bare(git_repo_path).unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "rename", "bnoexist", "blocal"]);
insta::assert_snapshot!(stderr, @r###"
Error: No such branch: bnoexist
"###);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=commit-0"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "blocal"]);
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "rename", "blocal", "blocal1"]);
insta::assert_snapshot!(stderr, @"");
test_env.jj_cmd_ok(&repo_path, &["new"]);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=commit-1"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "bexist"]);
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "rename", "blocal1", "bexist"]);
insta::assert_snapshot!(stderr, @r###"
Error: Branch already exists: bexist
"###);
test_env.jj_cmd_ok(&repo_path, &["new"]);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=commit-2"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "bremote"]);
test_env.jj_cmd_ok(&repo_path, &["git", "push", "-b=bremote"]);
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "rename", "bremote", "bremote2"]);
insta::assert_snapshot!(stderr, @r###"
Warning: Tracked remote branches for branch bremote were not renamed.
Hint: To rename the branch on the remote, you can `jj git push --branch bremote` first (to delete it on the remote), and then `jj git push --branch bremote2`. `jj git push --all` would also be sufficient.
"###);
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "rename", "bremote2", "bremote"]);
insta::assert_snapshot!(stderr, @r###"
Warning: Tracked remote branches for branch bremote exist.
Hint: Run `jj branch untrack 'glob:bremote@*'` to disassociate them.
"###);
}
#[test]
fn test_branch_rename_colocated() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo", "--colocate"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=commit-0"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "blocal"]);
// Make sure that git tracking branches don't cause a warning
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["branch", "rename", "blocal", "blocal1"]);
insta::assert_snapshot!(stderr, @"");
}
#[test]
fn test_branch_forget_glob() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo-1"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "bar-2"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo-3"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo-4"]);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar-2 foo-1 foo-3 foo-4 230dd059e1b0
◆ 000000000000
"###);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "forget", "glob:foo-[1-3]"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Forgot 2 branches.
"###);
test_env.jj_cmd_ok(&repo_path, &["undo"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "forget", "glob:foo-[1-3]"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Forgot 2 branches.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar-2 foo-4 230dd059e1b0
◆ 000000000000
"###);
// Forgetting a branch via both explicit name and glob pattern, or with
// multiple glob patterns, shouldn't produce an error.
let (stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["branch", "forget", "foo-4", "glob:foo-*", "glob:foo-*"],
);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Forgot 1 branches.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar-2 230dd059e1b0
◆ 000000000000
"###);
// Malformed glob
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["branch", "forget", "glob:foo-[1-3"]);
insta::assert_snapshot!(stderr, @r###"
error: invalid value 'glob:foo-[1-3' for '<NAMES>...': Pattern syntax error near position 4: invalid range pattern
For more information, try '--help'.
"###);
// We get an error if none of the globs match anything
let stderr = test_env.jj_cmd_failure(
&repo_path,
&["branch", "forget", "glob:bar*", "glob:baz*", "glob:boom*"],
);
insta::assert_snapshot!(stderr, @r###"
Error: No matching branches for patterns: baz*, boom*
"###);
}
#[test]
fn test_branch_delete_glob() {
// Set up a git repo with a branch and a jj repo that has it as a remote.
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git2::Repository::init_bare(git_repo_path).unwrap();
let mut tree_builder = git_repo.treebuilder(None).unwrap();
let file_oid = git_repo.blob(b"content").unwrap();
tree_builder
.insert("file", file_oid, git2::FileMode::Blob.into())
.unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m=commit"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo-1"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "bar-2"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo-3"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo-4"]);
// Push to create remote-tracking branches
test_env.jj_cmd_ok(&repo_path, &["git", "push", "--all"]);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar-2 foo-1 foo-3 foo-4 312a98d6f27b
◆ 000000000000
"###);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "glob:foo-[1-3]"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Deleted 2 branches.
"###);
test_env.jj_cmd_ok(&repo_path, &["undo"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "glob:foo-[1-3]"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Deleted 2 branches.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar-2 foo-1@origin foo-3@origin foo-4 312a98d6f27b
◆ 000000000000
"###);
// We get an error if none of the globs match live branches. Unlike `jj branch
// forget`, it's not allowed to delete already deleted branches.
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "delete", "glob:foo-[1-3]"]);
insta::assert_snapshot!(stderr, @r###"
Error: No matching branches for patterns: foo-[1-3]
"###);
// Deleting a branch via both explicit name and glob pattern, or with
// multiple glob patterns, shouldn't produce an error.
let (stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["branch", "delete", "foo-4", "glob:foo-*", "glob:foo-*"],
);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Deleted 1 branches.
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
@ bar-2 foo-1@origin foo-3@origin foo-4@origin 312a98d6f27b
◆ 000000000000
"###);
// The deleted branches are still there
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
bar-2: qpvuntsm 312a98d6 (empty) commit
@origin: qpvuntsm 312a98d6 (empty) commit
foo-1 (deleted)
@origin: qpvuntsm 312a98d6 (empty) commit
foo-3 (deleted)
@origin: qpvuntsm 312a98d6 (empty) commit
foo-4 (deleted)
@origin: qpvuntsm 312a98d6 (empty) commit
"###);
// Malformed glob
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["branch", "delete", "glob:foo-[1-3"]);
insta::assert_snapshot!(stderr, @r###"
error: invalid value 'glob:foo-[1-3' for '<NAMES>...': Pattern syntax error near position 4: invalid range pattern
For more information, try '--help'.
"###);
// Unknown pattern kind
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["branch", "forget", "whatever:branch"]);
insta::assert_snapshot!(stderr, @r###"
error: invalid value 'whatever:branch' for '<NAMES>...': Invalid string pattern kind "whatever:"
For more information, try '--help'.
Hint: Try prefixing with one of `exact:`, `glob:`, `regex:`, or `substring:`
"###);
}
#[test]
fn test_branch_delete_export() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["new"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]);
test_env.jj_cmd_ok(&repo_path, &["git", "export"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "foo"]);
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "list", "--all-remotes"]);
insta::assert_snapshot!(stdout, @r###"
foo (deleted)
@git: rlvkpnrz 65b6b74e (empty) (no description set)
"###);
insta::assert_snapshot!(stderr, @r###"
Hint: Branches marked as deleted will be deleted from the underlying Git repo on the next `jj git export`.
"###);
test_env.jj_cmd_ok(&repo_path, &["git", "export"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
"###);
}
#[test]
fn test_branch_forget_export() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
test_env.jj_cmd_ok(&repo_path, &["new"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
foo: rlvkpnrz 65b6b74e (empty) (no description set)
"###);
// Exporting the branch to git creates a local-git tracking branch
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "forget", "foo"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Forgot 1 branches.
"###);
// Forgetting a branch deletes local and remote-tracking branches including
// the corresponding git-tracking branch.
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "-r=foo", "--no-graph"]);
insta::assert_snapshot!(stderr, @r###"
Error: Revision "foo" doesn't exist
"###);
// `jj git export` will delete the branch from git. In a colocated repo,
// this will happen automatically immediately after a `jj branch forget`.
// This is demonstrated in `test_git_colocated_branch_forget` in
// test_git_colocated.rs
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
}
#[test]
fn test_branch_forget_fetched_branch() {
// Much of this test is borrowed from `test_git_fetch_remote_only_branch` in
// test_git_fetch.rs
// Set up a git repo with a branch and a jj repo that has it as a remote.
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-branch = true");
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git2::Repository::init_bare(git_repo_path).unwrap();
let signature =
git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0)).unwrap();
let mut tree_builder = git_repo.treebuilder(None).unwrap();
let file_oid = git_repo.blob(b"content").unwrap();
tree_builder
.insert("file", file_oid, git2::FileMode::Blob.into())
.unwrap();
let tree_oid = tree_builder.write().unwrap();
let tree = git_repo.find_tree(tree_oid).unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
// Create a commit and a branch in the git repo
let first_git_repo_commit = git_repo
.commit(
Some("refs/heads/feature1"),
&signature,
&signature,
"message",
&tree,
&[],
)
.unwrap();
// Fetch normally
test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: mzyxwzks 9f01a0e0 message
@origin: mzyxwzks 9f01a0e0 message
"###);
// TEST 1: with export-import
// Forget the branch
test_env.jj_cmd_ok(&repo_path, &["branch", "forget", "feature1"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
// At this point `jj git export && jj git import` does *not* recreate the
// branch. This behavior is important in colocated repos, as otherwise a
// forgotten branch would be immediately resurrected.
//
// Technically, this is because `jj branch forget` preserved
// the ref in jj view's `git_refs` tracking the local git repo's remote-tracking
// branch.
// TODO: Show that jj git push is also a no-op
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "import"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Nothing changed.
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
// We can fetch feature1 again.
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: mzyxwzks 9f01a0e0 message
@origin: mzyxwzks 9f01a0e0 message
"###);
// TEST 2: No export/import (otherwise the same as test 1)
test_env.jj_cmd_ok(&repo_path, &["branch", "forget", "feature1"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
// Fetch works even without the export-import
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: mzyxwzks 9f01a0e0 message
@origin: mzyxwzks 9f01a0e0 message
"###);
// TEST 3: fetch branch that was moved & forgotten
// Move the branch in the git repo.
git_repo
.commit(
Some("refs/heads/feature1"),
&signature,
&signature,
"another message",
&tree,
&[&git_repo.find_commit(first_git_repo_commit).unwrap()],
)
.unwrap();
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "forget", "feature1"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Forgot 1 branches.
"###);
// Fetching a moved branch does not create a conflict
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: ooosovrs 38aefb17 (empty) another message
@origin: ooosovrs 38aefb17 (empty) another message
"###);
}
#[test]
fn test_branch_forget_deleted_or_nonexistent_branch() {
// Much of this test is borrowed from `test_git_fetch_remote_only_branch` in
// test_git_fetch.rs
// ======== Beginning of test setup ========
// Set up a git repo with a branch and a jj repo that has it as a remote.
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-branch = true");
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git2::Repository::init_bare(git_repo_path).unwrap();
let signature =
git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0)).unwrap();
let mut tree_builder = git_repo.treebuilder(None).unwrap();
let file_oid = git_repo.blob(b"content").unwrap();
tree_builder
.insert("file", file_oid, git2::FileMode::Blob.into())
.unwrap();
let tree_oid = tree_builder.write().unwrap();
let tree = git_repo.find_tree(tree_oid).unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
// Create a commit and a branch in the git repo
git_repo
.commit(
Some("refs/heads/feature1"),
&signature,
&signature,
"message",
&tree,
&[],
)
.unwrap();
// Fetch and then delete the branch
test_env.jj_cmd_ok(&repo_path, &["git", "fetch", "--remote=origin"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "feature1"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1 (deleted)
@origin: mzyxwzks 9f01a0e0 message
"###);
// ============ End of test setup ============
// We can forget a deleted branch
test_env.jj_cmd_ok(&repo_path, &["branch", "forget", "feature1"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @"");
// Can't forget a non-existent branch
let stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "forget", "i_do_not_exist"]);
insta::assert_snapshot!(stderr, @r###"
Error: No such branch: i_do_not_exist
"###);
}
#[test]
fn test_branch_track_untrack() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git2::Repository::init(git_repo_path).unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
let create_remote_commit = |message: &str, data: &[u8], ref_names: &[&str]| {
let signature =
git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0))
.unwrap();
let mut tree_builder = git_repo.treebuilder(None).unwrap();
let file_oid = git_repo.blob(data).unwrap();
tree_builder
.insert("file", file_oid, git2::FileMode::Blob.into())
.unwrap();
let tree_oid = tree_builder.write().unwrap();
let tree = git_repo.find_tree(tree_oid).unwrap();
// Create commit and branches in the remote
let git_commit_oid = git_repo
.commit(None, &signature, &signature, message, &tree, &[])
.unwrap();
for name in ref_names {
git_repo.reference(name, git_commit_oid, true, "").unwrap();
}
};
// Fetch new commit without auto tracking. No local branches should be
// created.
create_remote_commit(
"commit 1",
b"content 1",
&[
"refs/heads/main",
"refs/heads/feature1",
"refs/heads/feature2",
],
);
test_env.add_config("git.auto-local-branch = false");
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] untracked
branch: feature2@origin [new] untracked
branch: main@origin [new] untracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1@origin: sptzoqmo 7b33f629 commit 1
feature2@origin: sptzoqmo 7b33f629 commit 1
main@origin: sptzoqmo 7b33f629 commit 1
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
◆ feature1@origin feature2@origin main@origin 7b33f6295eda
│ @ 230dd059e1b0
├─╯
◆ 000000000000
"###);
// Track new branch. Local branch should be created.
test_env.jj_cmd_ok(
&repo_path,
&["branch", "track", "feature1@origin", "main@origin"],
);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: sptzoqmo 7b33f629 commit 1
@origin: sptzoqmo 7b33f629 commit 1
feature2@origin: sptzoqmo 7b33f629 commit 1
main: sptzoqmo 7b33f629 commit 1
@origin: sptzoqmo 7b33f629 commit 1
"###);
// Track existing branch. Local branch should result in conflict.
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "feature2"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "track", "feature2@origin"]);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: sptzoqmo 7b33f629 commit 1
@origin: sptzoqmo 7b33f629 commit 1
feature2 (conflicted):
+ qpvuntsm 230dd059 (empty) (no description set)
+ sptzoqmo 7b33f629 commit 1
@origin (behind by 1 commits): sptzoqmo 7b33f629 commit 1
main: sptzoqmo 7b33f629 commit 1
@origin: sptzoqmo 7b33f629 commit 1
"###);
// Untrack existing and locally-deleted branches. Branch targets should be
// unchanged
test_env.jj_cmd_ok(&repo_path, &["branch", "delete", "feature2"]);
test_env.jj_cmd_ok(
&repo_path,
&["branch", "untrack", "feature1@origin", "feature2@origin"],
);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: sptzoqmo 7b33f629 commit 1
feature1@origin: sptzoqmo 7b33f629 commit 1
feature2@origin: sptzoqmo 7b33f629 commit 1
main: sptzoqmo 7b33f629 commit 1
@origin: sptzoqmo 7b33f629 commit 1
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
◆ feature1 feature1@origin feature2@origin main 7b33f6295eda
│ @ 230dd059e1b0
├─╯
◆ 000000000000
"###);
// Fetch new commit. Only tracking branch "main" should be merged.
create_remote_commit(
"commit 2",
b"content 2",
&[
"refs/heads/main",
"refs/heads/feature1",
"refs/heads/feature2",
],
);
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [updated] untracked
branch: feature2@origin [updated] untracked
branch: main@origin [updated] tracked
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: sptzoqmo 7b33f629 commit 1
feature1@origin: mmqqkyyt 40dabdaf commit 2
feature2@origin: mmqqkyyt 40dabdaf commit 2
main: mmqqkyyt 40dabdaf commit 2
@origin: mmqqkyyt 40dabdaf commit 2
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
◆ feature1@origin feature2@origin main 40dabdaf4abe
│ ○ feature1 7b33f6295eda
├─╯
│ @ 230dd059e1b0
├─╯
◆ 000000000000
"###);
// Fetch new commit with auto tracking. Tracking branch "main" and new
// branch "feature3" should be merged.
create_remote_commit(
"commit 3",
b"content 3",
&[
"refs/heads/main",
"refs/heads/feature1",
"refs/heads/feature2",
"refs/heads/feature3",
],
);
test_env.add_config("git.auto-local-branch = true");
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [updated] untracked
branch: feature2@origin [updated] untracked
branch: feature3@origin [new] tracked
branch: main@origin [updated] tracked
Abandoned 1 commits that are no longer reachable.
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: sptzoqmo 7b33f629 commit 1
feature1@origin: wwnpyzpo 3f0f86fa commit 3
feature2@origin: wwnpyzpo 3f0f86fa commit 3
feature3: wwnpyzpo 3f0f86fa commit 3
@origin: wwnpyzpo 3f0f86fa commit 3
main: wwnpyzpo 3f0f86fa commit 3
@origin: wwnpyzpo 3f0f86fa commit 3
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
◆ feature1@origin feature2@origin feature3 main 3f0f86fa0e57
│ ○ feature1 7b33f6295eda
├─╯
│ @ 230dd059e1b0
├─╯
◆ 000000000000
"###);
}
#[test]
fn test_branch_track_conflict() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
let git_repo_path = test_env.env_root().join("git-repo");
git2::Repository::init_bare(git_repo_path).unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "main"]);
test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "a"]);
test_env.jj_cmd_ok(&repo_path, &["git", "push", "-b", "main"]);
test_env.jj_cmd_ok(&repo_path, &["branch", "untrack", "main@origin"]);
test_env.jj_cmd_ok(
&repo_path,
&["describe", "-m", "b", "-r", "main", "--ignore-immutable"],
);
let (_, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "track", "main@origin"]);
insta::assert_snapshot!(stderr, @r###"
Started tracking 1 remote branches.
main (conflicted):
+ qpvuntsm e802c4f8 (empty) b
+ qpvuntsm hidden 427890ea (empty) a
@origin (behind by 1 commits): qpvuntsm hidden 427890ea (empty) a
"###);
}
#[test]
fn test_branch_track_untrack_patterns() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git2::Repository::init(git_repo_path).unwrap();
test_env.jj_cmd_ok(
&repo_path,
&["git", "remote", "add", "origin", "../git-repo"],
);
// Create remote commit
let signature =
git2::Signature::new("Some One", "some.one@example.com", &git2::Time::new(0, 0)).unwrap();
let mut tree_builder = git_repo.treebuilder(None).unwrap();
let file_oid = git_repo.blob(b"content").unwrap();
tree_builder
.insert("file", file_oid, git2::FileMode::Blob.into())
.unwrap();
let tree_oid = tree_builder.write().unwrap();
let tree = git_repo.find_tree(tree_oid).unwrap();
// Create commit and branches in the remote
let git_commit_oid = git_repo
.commit(None, &signature, &signature, "commit", &tree, &[])
.unwrap();
for name in ["refs/heads/feature1", "refs/heads/feature2"] {
git_repo.reference(name, git_commit_oid, true, "").unwrap();
}
// Fetch new commit without auto tracking
test_env.add_config("git.auto-local-branch = false");
let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]);
insta::assert_snapshot!(stderr, @r###"
branch: feature1@origin [new] untracked
branch: feature2@origin [new] untracked
"###);
// Track local branch
test_env.jj_cmd_ok(&repo_path, &["branch", "create", "main"]);
insta::assert_snapshot!(
test_env.jj_cmd_cli_error(&repo_path, &["branch", "track", "main"]), @r###"
error: invalid value 'main' for '<BRANCH@REMOTE>...': remote branch must be specified in branch@remote form
For more information, try '--help'.
"###);
// Track/untrack unknown branch
insta::assert_snapshot!(
test_env.jj_cmd_failure(&repo_path, &["branch", "track", "main@origin"]), @r###"
Error: No such remote branch: main@origin
"###);
insta::assert_snapshot!(
test_env.jj_cmd_failure(&repo_path, &["branch", "untrack", "main@origin"]), @r###"
Error: No such remote branch: main@origin
"###);
insta::assert_snapshot!(
test_env.jj_cmd_failure(&repo_path, &["branch", "track", "glob:maine@*"]), @r###"
Error: No matching remote branches for patterns: maine@*
"###);
insta::assert_snapshot!(
test_env.jj_cmd_failure(
&repo_path,
&["branch", "untrack", "main@origin", "glob:main@o*"],
), @r###"
Error: No matching remote branches for patterns: main@origin, main@o*
"###);
// Track already tracked branch
test_env.jj_cmd_ok(&repo_path, &["branch", "track", "feature1@origin"]);
let (_, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "track", "feature1@origin"]);
insta::assert_snapshot!(stderr, @r###"
Warning: Remote branch already tracked: feature1@origin
Nothing changed.
"###);
// Untrack non-tracking branch
let (_, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "untrack", "feature2@origin"]);
insta::assert_snapshot!(stderr, @r###"
Warning: Remote branch not tracked yet: feature2@origin
Nothing changed.
"###);
// Untrack Git-tracking branch
test_env.jj_cmd_ok(&repo_path, &["git", "export"]);
let (_, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "untrack", "main@git"]);
insta::assert_snapshot!(stderr, @r###"
Warning: Git-tracking branch cannot be untracked: main@git
Nothing changed.
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: omvolwpu 1336caed commit
@git: omvolwpu 1336caed commit
@origin: omvolwpu 1336caed commit
feature2@origin: omvolwpu 1336caed commit
main: qpvuntsm 230dd059 (empty) (no description set)
@git: qpvuntsm 230dd059 (empty) (no description set)
"###);
// Untrack by pattern
let (_, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "untrack", "glob:*@*"]);
insta::assert_snapshot!(stderr, @r###"
Warning: Git-tracking branch cannot be untracked: feature1@git
Warning: Remote branch not tracked yet: feature2@origin
Warning: Git-tracking branch cannot be untracked: main@git
Stopped tracking 1 remote branches.
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: omvolwpu 1336caed commit
@git: omvolwpu 1336caed commit
feature1@origin: omvolwpu 1336caed commit
feature2@origin: omvolwpu 1336caed commit
main: qpvuntsm 230dd059 (empty) (no description set)
@git: qpvuntsm 230dd059 (empty) (no description set)
"###);
// Track by pattern
let (_, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "track", "glob:feature?@origin"]);
insta::assert_snapshot!(stderr, @r###"
Started tracking 2 remote branches.
"###);
insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###"
feature1: omvolwpu 1336caed commit
@git: omvolwpu 1336caed commit
@origin: omvolwpu 1336caed commit
feature2: omvolwpu 1336caed commit
@origin: omvolwpu 1336caed commit
main: qpvuntsm 230dd059 (empty) (no description set)
@git: qpvuntsm 230dd059 (empty) (no description set)
"###);
}
#[test]
fn test_branch_list() {
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(), &["git", "init", "remote"]);
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 = remote_path;
remote_git_path.extend([".jj", "repo", "store", "git"]);
test_env.jj_cmd_ok(
test_env.env_root(),
&["git", "clone", remote_git_path.to_str().unwrap(), "local"],
);
let local_path = test_env.env_root().join("local");
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,
&["branch", "set", "--allow-backwards", "remote-unsync"],
);
// Synchronized tracking remotes and non-tracking remotes aren't listed by
// default
let (stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["branch", "list"]);
insta::assert_snapshot!(stdout, @r###"
local-only: wqnwkozp 4e887f78 (empty) local-only
remote-delete (deleted)
@origin: mnmymoky 203e60eb (empty) remote-delete
remote-sync: zwtyzrop c761c7ea (empty) remote-sync
remote-unsync: wqnwkozp 4e887f78 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
"###);
insta::assert_snapshot!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
let (stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["branch", "list", "--all-remotes"]);
insta::assert_snapshot!(stdout, @r###"
local-only: wqnwkozp 4e887f78 (empty) local-only
remote-delete (deleted)
@origin: mnmymoky 203e60eb (empty) remote-delete
remote-sync: zwtyzrop c761c7ea (empty) remote-sync
@origin: zwtyzrop c761c7ea (empty) remote-sync
remote-unsync: wqnwkozp 4e887f78 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
remote-untrack@origin: vmortlor 71a16b05 (empty) remote-untrack
"###);
insta::assert_snapshot!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
let template = r#"
concat(
"[" ++ name ++ if(remote, "@" ++ remote) ++ "]\n",
separate(" ", "present:", present) ++ "\n",
separate(" ", "conflict:", conflict) ++ "\n",
separate(" ", "normal_target:", normal_target.description().first_line()) ++ "\n",
separate(" ", "removed_targets:", removed_targets.map(|c| c.description().first_line())) ++ "\n",
separate(" ", "added_targets:", added_targets.map(|c| c.description().first_line())) ++ "\n",
separate(" ", "tracked:", tracked) ++ "\n",
separate(" ", "tracking_present:", tracking_present) ++ "\n",
separate(" ", "tracking_ahead_count:", tracking_ahead_count.lower()) ++ "\n",
separate(" ", "tracking_behind_count:", tracking_behind_count.lower()) ++ "\n",
)
"#;
let (stdout, stderr) = test_env.jj_cmd_ok(
&local_path,
&["branch", "list", "--all-remotes", "-T", template],
);
insta::assert_snapshot!(stdout, @r###"
[local-only]
present: true
conflict: false
normal_target: local-only
removed_targets:
added_targets: local-only
tracked: false
tracking_present: false
tracking_ahead_count: <Error: Not a tracked remote ref>
tracking_behind_count: <Error: Not a tracked remote ref>
[remote-delete]
present: false
conflict: false
normal_target: <Error: No Commit available>
removed_targets:
added_targets:
tracked: false
tracking_present: false
tracking_ahead_count: <Error: Not a tracked remote ref>
tracking_behind_count: <Error: Not a tracked remote ref>
[remote-delete@origin]
present: true
conflict: false
normal_target: remote-delete
removed_targets:
added_targets: remote-delete
tracked: true
tracking_present: false
tracking_ahead_count: 2
tracking_behind_count: 0
[remote-sync]
present: true
conflict: false
normal_target: remote-sync
removed_targets:
added_targets: remote-sync
tracked: false
tracking_present: false
tracking_ahead_count: <Error: Not a tracked remote ref>
tracking_behind_count: <Error: Not a tracked remote ref>
[remote-sync@origin]
present: true
conflict: false
normal_target: remote-sync
removed_targets:
added_targets: remote-sync
tracked: true
tracking_present: true
tracking_ahead_count: 0
tracking_behind_count: 0
[remote-unsync]
present: true
conflict: false
normal_target: local-only
removed_targets:
added_targets: local-only
tracked: false
tracking_present: false
tracking_ahead_count: <Error: Not a tracked remote ref>
tracking_behind_count: <Error: Not a tracked remote ref>
[remote-unsync@origin]
present: true
conflict: false
normal_target: remote-unsync
removed_targets:
added_targets: remote-unsync
tracked: true
tracking_present: true
tracking_ahead_count: 1
tracking_behind_count: 1
[remote-untrack@origin]
present: true
conflict: false
normal_target: remote-untrack
removed_targets:
added_targets: remote-untrack
tracked: false
tracking_present: false
tracking_ahead_count: <Error: Not a tracked remote ref>
tracking_behind_count: <Error: Not a tracked remote ref>
"###);
insta::assert_snapshot!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
}
#[test]
fn test_branch_list_filtered() {
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-branch = true");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
// Initialize remote refs
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "remote"]);
let remote_path = test_env.env_root().join("remote");
for branch in ["remote-keep", "remote-delete", "remote-rewrite"] {
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 = remote_path;
remote_git_path.extend([".jj", "repo", "store", "git"]);
test_env.jj_cmd_ok(
test_env.env_root(),
&["git", "clone", remote_git_path.to_str().unwrap(), "local"],
);
let local_path = test_env.env_root().join("local");
test_env.jj_cmd_ok(&local_path, &["new", "root()", "-m", "local-keep"]);
test_env.jj_cmd_ok(&local_path, &["branch", "create", "local-keep"]);
// Mutate refs in local repository
test_env.jj_cmd_ok(&local_path, &["branch", "delete", "remote-delete"]);
test_env.jj_cmd_ok(&local_path, &["describe", "-mrewritten", "remote-rewrite"]);
let template = r#"separate(" ", commit_id.short(), branches, if(hidden, "(hidden)"))"#;
insta::assert_snapshot!(
test_env.jj_cmd_success(
&local_path,
&["log", "-r::(branches() | remote_branches())", "-T", template],
),
@r###"
○ e31634b64294 remote-rewrite*
│ @ c7b4c09cd77c local-keep
├─╯
│ ○ 3e9a5af6ef15 remote-rewrite@origin (hidden)
├─╯
│ ○ dad5f298ca57 remote-delete@origin
├─╯
│ ○ 911e912015fb remote-keep
├─╯
◆ 000000000000
"###);
// All branches are listed by default.
let (stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["branch", "list"]);
insta::assert_snapshot!(stdout, @r###"
local-keep: kpqxywon c7b4c09c (empty) local-keep
remote-delete (deleted)
@origin: yxusvupt dad5f298 (empty) remote-delete
remote-keep: nlwprzpn 911e9120 (empty) remote-keep
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
let query =
|args: &[&str]| test_env.jj_cmd_ok(&local_path, &[&["branch", "list"], args].concat());
let query_error =
|args: &[&str]| test_env.jj_cmd_failure(&local_path, &[&["branch", "list"], args].concat());
// "all()" doesn't include deleted branches since they have no local targets.
// So "all()" is identical to "branches()".
let (stdout, stderr) = query(&["-rall()"]);
insta::assert_snapshot!(stdout, @r###"
local-keep: kpqxywon c7b4c09c (empty) local-keep
remote-keep: nlwprzpn 911e9120 (empty) remote-keep
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @"");
// Exclude remote-only branches. "remote-rewrite@origin" is included since
// local "remote-rewrite" target matches.
let (stdout, stderr) = query(&["-rbranches()"]);
insta::assert_snapshot!(stdout, @r###"
local-keep: kpqxywon c7b4c09c (empty) local-keep
remote-keep: nlwprzpn 911e9120 (empty) remote-keep
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @"");
// Select branches by name.
let (stdout, stderr) = query(&["remote-rewrite"]);
insta::assert_snapshot!(stdout, @r###"
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @"");
let (stdout, stderr) = query(&["-rbranches(remote-rewrite)"]);
insta::assert_snapshot!(stdout, @r###"
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @"");
// Select branches by name, combined with --all-remotes
test_env.jj_cmd_ok(&local_path, &["git", "export"]);
let (stdout, stderr) = query(&["--all-remotes", "remote-rewrite"]);
insta::assert_snapshot!(stdout, @r###"
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@git: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @"");
let (stdout, stderr) = query(&["--all-remotes", "-rbranches(remote-rewrite)"]);
insta::assert_snapshot!(stdout, @r###"
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@git: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @"");
// Can select deleted branch by name pattern, but not by revset.
let (stdout, stderr) = query(&["remote-delete"]);
insta::assert_snapshot!(stdout, @r###"
remote-delete (deleted)
@origin: yxusvupt dad5f298 (empty) remote-delete
"###);
insta::assert_snapshot!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
let (stdout, stderr) = query(&["-rbranches(remote-delete)"]);
insta::assert_snapshot!(stdout, @r###"
"###);
insta::assert_snapshot!(query_error(&["-rremote-delete"]), @r###"
Error: Revision "remote-delete" doesn't exist
Hint: Did you mean "remote-delete@origin", "remote-keep", "remote-rewrite", "remote-rewrite@origin"?
"###);
insta::assert_snapshot!(stderr, @"");
// Name patterns are OR-ed.
let (stdout, stderr) = query(&["glob:*-keep", "remote-delete"]);
insta::assert_snapshot!(stdout, @r###"
local-keep: kpqxywon c7b4c09c (empty) local-keep
remote-delete (deleted)
@origin: yxusvupt dad5f298 (empty) remote-delete
remote-keep: nlwprzpn 911e9120 (empty) remote-keep
"###);
insta::assert_snapshot!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
// Unmatched name pattern shouldn't be an error. A warning can be added later.
let (stdout, stderr) = query(&["local-keep", "glob:push-*"]);
insta::assert_snapshot!(stdout, @r###"
local-keep: kpqxywon c7b4c09c (empty) local-keep
"###);
insta::assert_snapshot!(stderr, @"");
// Name pattern and revset are OR-ed.
let (stdout, stderr) = query(&["local-keep", "-rbranches(remote-rewrite)"]);
insta::assert_snapshot!(stdout, @r###"
local-keep: kpqxywon c7b4c09c (empty) local-keep
remote-rewrite: xyxluytn e31634b6 (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
"###);
insta::assert_snapshot!(stderr, @"");
}
#[test]
fn test_branch_list_much_remote_divergence() {
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(), &["git", "init", "remote"]);
let remote_path = test_env.env_root().join("remote");
test_env.jj_cmd_ok(&remote_path, &["new", "root()", "-m", "remote-unsync"]);
for _ in 0..15 {
test_env.jj_cmd_ok(&remote_path, &["new", "-m", "remote-unsync"]);
}
test_env.jj_cmd_ok(&remote_path, &["branch", "create", "remote-unsync"]);
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 = remote_path;
remote_git_path.extend([".jj", "repo", "store", "git"]);
test_env.jj_cmd_ok(
test_env.env_root(),
&["git", "clone", remote_git_path.to_str().unwrap(), "local"],
);
let local_path = test_env.env_root().join("local");
test_env.jj_cmd_ok(&local_path, &["new", "root()", "-m", "local-only"]);
for _ in 0..15 {
test_env.jj_cmd_ok(&local_path, &["new", "-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", "set", "--allow-backwards", "remote-unsync"],
);
let (stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["branch", "list"]);
insta::assert_snapshot!(stdout, @r###"
local-only: zkyosouw 4ab3f751 (empty) local-only
remote-unsync: zkyosouw 4ab3f751 (empty) local-only
@origin (ahead by at least 10 commits, behind by at least 10 commits): lxyktnks 19582022 (empty) remote-unsync
"###);
insta::assert_snapshot!(stderr, @"");
}
#[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(), &["git", "init", "remote"]);
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(), &["git", "init", "upstream"]);
// 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"],
);
let (stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["branch", "list", "--all-remotes"]);
insta::assert_snapshot!(stdout, @r###"
local-only: nmzmmopx e1da745b (empty) local-only
@git: nmzmmopx e1da745b (empty) local-only
remote-delete (deleted)
@origin: mnmymoky 203e60eb (empty) remote-delete
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!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
let (stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["branch", "list", "--tracked"]);
insta::assert_snapshot!(stdout, @r###"
remote-delete (deleted)
@origin: mnmymoky 203e60eb (empty) remote-delete
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!(stderr, @r###"
Hint: Branches marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this.
"###);
let (stdout, stderr) = test_env.jj_cmd_ok(
&local_path,
&["branch", "list", "--tracked", "remote-unsync"],
);
insta::assert_snapshot!(stdout, @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!(stderr, @"");
let (stdout, stderr) = test_env.jj_cmd_ok(
&local_path,
&["branch", "list", "--tracked", "remote-untrack"],
);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
test_env.jj_cmd_ok(
&local_path,
&["branch", "untrack", "remote-unsync@upstream"],
);
let (stdout, stderr) = test_env.jj_cmd_ok(
&local_path,
&["branch", "list", "--tracked", "remote-unsync"],
);
insta::assert_snapshot!(stdout, @r###"
remote-unsync: nmzmmopx e1da745b (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
"###);
insta::assert_snapshot!(stderr, @"");
}
#[test]
fn test_branch_list_conflicted() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
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])
}
fn get_branch_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
// --quiet to suppress deleted branches hint
test_env.jj_cmd_success(repo_path, &["branch", "list", "--all-remotes", "--quiet"])
}