// 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(), &["init", "repo", "--git"]); 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###" warning: Creating multiple branches: foo, bar "###); 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###" warning: Updating multiple branches: foo, bar "###); 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 "###);} #[test]fn test_branch_at_root() { 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 (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "create", "fred", "-r=root()"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @""); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Nothing changed. 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(), &["init", "repo", "--git"]); 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'. "###);} #[test]fn test_branch_move() { 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 stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "set", "foo"]); insta::assert_snapshot!(stderr, @r###" Error: No such branch: foo Hint: Use `jj branch create` to create it. "###); let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]); insta::assert_snapshot!(stderr, @""); 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, @""); 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, @"");} #[test]fn test_branch_move_conflicting() { 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 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, @""); 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(), &["init", "repo", "--git"]); 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: Branch bremote has tracking remote branches which 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. "###);} #[test]fn test_branch_forget_glob() { 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"); 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. "###); 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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(), &["init", "repo", "--git"]); 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 6fbf398c2d59 ◉ 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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 6fbf398c2d59 ◉ 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. "###); insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" @ bar-2 foo-1@origin foo-3@origin foo-4@origin 6fbf398c2d59 ◉ 000000000000 "###); // The deleted branches are still there insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" bar-2: qpvuntsm 6fbf398c (empty) commit @origin: qpvuntsm 6fbf398c (empty) commit foo-1 (deleted) @origin: qpvuntsm 6fbf398c (empty) commit (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) foo-3 (deleted) @origin: qpvuntsm 6fbf398c (empty) commit (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) foo-4 (deleted) @origin: qpvuntsm 6fbf398c (empty) commit (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) "###); // 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'. "###);} #[test]fn test_branch_delete_export() { 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"); 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 = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @r###" foo (deleted) @git: rlvkpnrz 65b6b74e (empty) (no description set) (this branch will be deleted from the underlying Git repo on the next `jj git export`) "###); test_env.jj_cmd_ok(&repo_path, &["git", "export"]); let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @r###" "###);} #[test]fn test_branch_forget_export() { 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"); test_env.jj_cmd_ok(&repo_path, &["new"]); test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]); let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @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, @""); // Forgetting a branch deletes local and remote-tracking branches including // the corresponding git-tracking branch. let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @""); 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, @""); let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @"");} #[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(), &["init", "repo", "--git"]); 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",Lines 1–500 of 1358. Load more
// 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(), &["init", "repo", "--git"]); 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###" warning: Creating multiple branches: foo, bar "###); 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###" warning: Updating multiple branches: foo, bar "###); 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 "###); } #[test] fn test_branch_at_root() { 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 (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "create", "fred", "-r=root()"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @""); let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Nothing changed. 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(), &["init", "repo", "--git"]); 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'. "###); } #[test] fn test_branch_move() { 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 stderr = test_env.jj_cmd_failure(&repo_path, &["branch", "set", "foo"]); insta::assert_snapshot!(stderr, @r###" Error: No such branch: foo Hint: Use `jj branch create` to create it. "###); let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]); insta::assert_snapshot!(stderr, @""); 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, @""); 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, @""); } #[test] fn test_branch_move_conflicting() { 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 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, @""); 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(), &["init", "repo", "--git"]); 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: Branch bremote has tracking remote branches which 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. "###); } #[test] fn test_branch_forget_glob() { 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"); 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. "###); 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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(), &["init", "repo", "--git"]); 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 6fbf398c2d59 ◉ 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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 6fbf398c2d59 ◉ 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. 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###" --glob has been deprecated. Please prefix the pattern with `glob:` instead. "###); insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" @ bar-2 foo-1@origin foo-3@origin foo-4@origin 6fbf398c2d59 ◉ 000000000000 "###); // The deleted branches are still there insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" bar-2: qpvuntsm 6fbf398c (empty) commit @origin: qpvuntsm 6fbf398c (empty) commit foo-1 (deleted) @origin: qpvuntsm 6fbf398c (empty) commit (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) foo-3 (deleted) @origin: qpvuntsm 6fbf398c (empty) commit (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) foo-4 (deleted) @origin: qpvuntsm 6fbf398c (empty) commit (this branch will be *deleted permanently* on the remote on the next `jj git push`. Use `jj branch forget` to prevent this) "###); // 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'. "###); } #[test] fn test_branch_delete_export() { 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"); 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 = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @r###" foo (deleted) @git: rlvkpnrz 65b6b74e (empty) (no description set) (this branch will be deleted from the underlying Git repo on the next `jj git export`) "###); test_env.jj_cmd_ok(&repo_path, &["git", "export"]); let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @r###" "###); } #[test] fn test_branch_forget_export() { 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"); test_env.jj_cmd_ok(&repo_path, &["new"]); test_env.jj_cmd_ok(&repo_path, &["branch", "create", "foo"]); let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @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, @""); // Forgetting a branch deletes local and remote-tracking branches including // the corresponding git-tracking branch. let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @""); 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, @""); let stdout = test_env.jj_cmd_success(&repo_path, &["branch", "list", "--all"]); insta::assert_snapshot!(stdout, @""); } #[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(), &["init", "repo", "--git"]); 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",