jj/cli/tests/test_bookmark_command.rs
Martin von Zweigbergk acd824269f git: write change-id header by default
We haven't had any reports of problems from people who opted in. Since
it's early in the release cycle now, let's now test it on everyone who
builds from head, so we get almost a month of testing from those
people before it's enabled by default in a released version.

This impacts lots of test cases because the change-id header is added
to the Git commit. Most are uninteresting. `test_git_fetch` now sees
some divergent changes where it used to see only divergent bookmarks,
which makes sense.
2025-05-08 23:05:38 +00:00

2404 lines
80 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 testutils::git;
use crate::common::CommandOutput;
use crate::common::TestEnvironment;
use crate::common::TestWorkDir;
fn create_commit_with_refs(
repo: &gix::Repository,
message: &str,
content: &[u8],
ref_names: &[&str],
) {
let git::CommitResult {
tree_id: _,
commit_id,
} = git::add_commit(repo, "refs/heads/dummy", "file", content, message, &[]);
repo.find_reference("dummy").unwrap().delete().unwrap();
for name in ref_names {
repo.reference(
*name,
commit_id,
gix::refs::transaction::PreviousValue::Any,
"log message",
)
.unwrap();
}
}
#[test]
fn test_bookmark_multiple_names() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo", "bar"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 2 bookmarks pointing to qpvuntsm e8849ae1 bar foo | (empty) (no description set)
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar foo e8849ae12c70
◆ 000000000000
[EOF]
");
work_dir.run_jj(["new"]).success();
let output = work_dir.run_jj(["bookmark", "set", "foo", "bar", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 2 bookmarks to zsuskuln 0e555a27 bar foo | (empty) (no description set)
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar foo 0e555a27ac99
○ e8849ae12c70
◆ 000000000000
[EOF]
");
let output = work_dir.run_jj(["bookmark", "delete", "foo", "bar", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Deleted 2 bookmarks.
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ 0e555a27ac99
○ e8849ae12c70
◆ 000000000000
[EOF]
");
// Hint should be omitted if -r is specified
let output = work_dir.run_jj(["bookmark", "create", "-r@-", "foo", "bar"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 2 bookmarks pointing to qpvuntsm e8849ae1 bar foo | (empty) (no description set)
[EOF]
");
// Create and move with explicit -r
let output = work_dir.run_jj(["bookmark", "set", "-r@", "bar", "baz"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 1 bookmarks pointing to zsuskuln 0e555a27 bar baz | (empty) (no description set)
Moved 1 bookmarks to zsuskuln 0e555a27 bar baz | (empty) (no description set)
[EOF]
");
// Noop changes should not be included in the stats
let output = work_dir.run_jj(["bookmark", "set", "-r@", "foo", "bar", "baz"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to zsuskuln 0e555a27 bar baz foo | (empty) (no description set)
[EOF]
");
}
#[test]
fn test_bookmark_at_root() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["bookmark", "create", "fred", "-r=root()"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 1 bookmarks pointing to zzzzzzzz 00000000 fred | (empty) (no description set)
[EOF]
");
let output = work_dir.run_jj(["git", "export"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
Warning: Failed to export some bookmarks:
fred@git: Ref cannot point to the root commit in Git
[EOF]
");
}
#[test]
fn test_bookmark_bad_name() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["bookmark", "create", "-r@", ""]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: invalid value '' for '<NAMES>...': Failed to parse bookmark name: Syntax error
For more information, try '--help'.
Caused by: --> 1:1
|
1 |
| ^---
|
= expected <identifier>, <string_literal>, or <raw_string_literal>
Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
[EOF]
[exit status: 2]
");
let output = work_dir.run_jj(["bookmark", "set", "''"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: invalid value '''' for '<NAMES>...': Failed to parse bookmark name: Expected non-empty string
For more information, try '--help'.
Caused by: --> 1:1
|
1 | ''
| ^^
|
= Expected non-empty string
Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
[EOF]
[exit status: 2]
");
let output = work_dir.run_jj(["bookmark", "rename", "x", ""]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: invalid value '' for '<NEW>': Failed to parse bookmark name: Syntax error
For more information, try '--help'.
Caused by: --> 1:1
|
1 |
| ^---
|
= expected <identifier>, <string_literal>, or <raw_string_literal>
Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
[EOF]
[exit status: 2]
");
// common errors
let output = work_dir.run_jj(["bookmark", "set", "@-", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: invalid value '@-' for '<NAMES>...': Failed to parse bookmark name: Syntax error
For more information, try '--help'.
Caused by: --> 1:1
|
1 | @-
| ^---
|
= expected <identifier>, <string_literal>, or <raw_string_literal>
Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
[EOF]
[exit status: 2]
");
let stderr = work_dir.run_jj(["bookmark", "set", "-r@-", "foo@bar"]);
insta::assert_snapshot!(stderr, @r"
------- stderr -------
error: invalid value 'foo@bar' for '<NAMES>...': Failed to parse bookmark name: Syntax error
For more information, try '--help'.
Caused by: --> 1:4
|
1 | foo@bar
| ^---
|
= expected <EOI>
Hint: Looks like remote bookmark. Run `jj bookmark track foo@bar` to track it.
[EOF]
[exit status: 2]
");
// quoted name works
let output = work_dir.run_jj(["bookmark", "create", "-r@", "'foo@bar'"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 1 bookmarks pointing to qpvuntsm e8849ae1 foo@bar | (empty) (no description set)
[EOF]
");
}
#[test]
fn test_bookmark_move() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
git::init_bare(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
let output = work_dir.run_jj(["bookmark", "move", "foo", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: No such bookmark: foo
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["bookmark", "set", "foo", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 1 bookmarks pointing to qpvuntsm e8849ae1 foo | (empty) (no description set)
[EOF]
");
work_dir.run_jj(["new"]).success();
let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Bookmark already exists: foo
Hint: Use `jj bookmark set` to update it.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["bookmark", "set", "foo", "--revision", "@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to mzvwutvl 8afc18ff foo | (empty) (no description set)
[EOF]
");
let output = work_dir.run_jj(["bookmark", "set", "-r@-", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Refusing to move bookmark backwards or sideways: foo
Hint: Use --allow-backwards to allow it.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["bookmark", "set", "-r@-", "--allow-backwards", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to qpvuntsm e8849ae1 foo | (empty) (no description set)
[EOF]
");
let output = work_dir.run_jj(["bookmark", "move", "foo", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to mzvwutvl 8afc18ff foo | (empty) (no description set)
[EOF]
");
let output = work_dir.run_jj(["bookmark", "move", "--to=@-", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Refusing to move bookmark backwards or sideways: foo
Hint: Use --allow-backwards to allow it.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj(["bookmark", "move", "--to=@-", "--allow-backwards", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to qpvuntsm e8849ae1 foo | (empty) (no description set)
[EOF]
");
// Delete bookmark locally, but is still tracking remote
work_dir.run_jj(["describe", "@-", "-mcommit"]).success();
work_dir
.run_jj(["git", "push", "--allow-new", "-r@-"])
.success();
work_dir.run_jj(["bookmark", "delete", "foo"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
foo (deleted)
@origin: qpvuntsm 5f3ceb1e (empty) commit
[EOF]
");
// Deleted tracking bookmark name should still be allocated
let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Tracked remote bookmarks exist for deleted bookmark: foo
Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack 'glob:foo@*'` to disassociate them.
[EOF]
[exit status: 1]
");
// Restoring local target shouldn't invalidate tracking state
let output = work_dir.run_jj(["bookmark", "set", "foo", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to mzvwutvl 91b59745 foo* | (empty) (no description set)
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
foo: mzvwutvl 91b59745 (empty) (no description set)
@origin (behind by 1 commits): qpvuntsm 5f3ceb1e (empty) commit
[EOF]
");
// Untracked remote bookmark shouldn't block creation of local bookmark
work_dir
.run_jj(["bookmark", "untrack", "foo@origin"])
.success();
work_dir.run_jj(["bookmark", "delete", "foo"]).success();
let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 1 bookmarks pointing to mzvwutvl 91b59745 foo | (empty) (no description set)
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
foo: mzvwutvl 91b59745 (empty) (no description set)
foo@origin: qpvuntsm 5f3ceb1e (empty) commit
[EOF]
");
}
#[test]
fn test_bookmark_move_matching() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir
.run_jj(["bookmark", "create", "-r@", "a1", "a2"])
.success();
work_dir.run_jj(["new", "-mhead1"]).success();
work_dir.run_jj(["new", "root()"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "b1"])
.success();
work_dir.run_jj(["new"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "c1"])
.success();
work_dir.run_jj(["new", "-mhead2"]).success();
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ 0dd9a4b12283
○ c1 2cbf65662e56
○ b1 c2934cfbfb19
│ ○ 9328ecc52471
│ ○ a1 a2 e8849ae12c70
├─╯
◆ 000000000000
[EOF]
");
// The default could be considered "--from=all() glob:*", but is disabled
let output = work_dir.run_jj(["bookmark", "move", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: the following required arguments were not provided:
<--from <REVSETS>|NAMES>
Usage: jj bookmark move --to <REVSET> <--from <REVSETS>|NAMES>
For more information, try '--help'.
[EOF]
[exit status: 2]
");
// No bookmarks pointing to the source revisions
let output = work_dir.run_jj(["bookmark", "move", "--from=none()", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
No bookmarks to update.
[EOF]
");
// No matching bookmarks within the source revisions
let output = work_dir.run_jj(["bookmark", "move", "--from=::@", "glob:a?", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: No matching bookmarks for patterns: a?
[EOF]
[exit status: 1]
");
// Noop move
let output = work_dir.run_jj(["bookmark", "move", "--to=a1", "a2"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
No bookmarks to update.
[EOF]
");
// Move from multiple revisions
let output = work_dir.run_jj(["bookmark", "move", "--from=::@", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 2 bookmarks to vruxwmqv 0dd9a4b1 b1 c1 | (empty) head2
Hint: Specify bookmark by name to update just one of the bookmarks.
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ b1 c1 0dd9a4b12283
○ 2cbf65662e56
○ c2934cfbfb19
│ ○ 9328ecc52471
│ ○ a1 a2 e8849ae12c70
├─╯
◆ 000000000000
[EOF]
");
work_dir.run_jj(["undo"]).success();
// Try to move multiple bookmarks, but one of them isn't fast-forward
let output = work_dir.run_jj(["bookmark", "move", "glob:?1", "--to=@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Refusing to move bookmark backwards or sideways: a1
Hint: Use --allow-backwards to allow it.
[EOF]
[exit status: 1]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ 0dd9a4b12283
○ c1 2cbf65662e56
○ b1 c2934cfbfb19
│ ○ 9328ecc52471
│ ○ a1 a2 e8849ae12c70
├─╯
◆ 000000000000
[EOF]
");
// Select by revision and name
let output = work_dir.run_jj(["bookmark", "move", "--from=::a1+", "--to=a1+", "glob:?1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to kkmpptxz 9328ecc5 a1 | (empty) head1
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ 0dd9a4b12283
○ c1 2cbf65662e56
○ b1 c2934cfbfb19
│ ○ a1 9328ecc52471
│ ○ a2 e8849ae12c70
├─╯
◆ 000000000000
[EOF]
");
}
#[test]
fn test_bookmark_move_conflicting() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let get_log = || {
let template = r#"separate(" ", description.first_line(), bookmarks)"#;
work_dir.run_jj(["log", "-T", template])
};
work_dir.run_jj(["new", "root()", "-mA0"]).success();
work_dir.run_jj(["new", "root()", "-mB0"]).success();
work_dir.run_jj(["new", "root()", "-mC0"]).success();
work_dir
.run_jj(["new", "description(A0)", "-mA1"])
.success();
// Set up conflicting bookmark.
work_dir
.run_jj(["bookmark", "create", "-rdescription(A0)", "foo"])
.success();
work_dir
.run_jj([
"bookmark",
"create",
"--at-op=@-",
"-rdescription(B0)",
"foo",
])
.success();
insta::assert_snapshot!(get_log(), @r"
@ A1
○ A0 foo??
│ ○ C0
├─╯
│ ○ B0 foo??
├─╯
[EOF]
------- stderr -------
Concurrent modification detected, resolving automatically.
[EOF]
");
// Can't move the bookmark to C0 since it's sibling.
let output = work_dir.run_jj(["bookmark", "set", "-rdescription(C0)", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Refusing to move bookmark backwards or sideways: foo
Hint: Use --allow-backwards to allow it.
[EOF]
[exit status: 1]
");
// Can move the bookmark to A1 since it's descendant of A0. It's not
// descendant of B0, though.
let output = work_dir.run_jj(["bookmark", "set", "-rdescription(A1)", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 1 bookmarks to mzvwutvl 0f5f3e2c foo | (empty) A1
[EOF]
");
insta::assert_snapshot!(get_log(), @r"
@ A1 foo
○ A0
│ ○ C0
├─╯
│ ○ B0
├─╯
[EOF]
");
}
#[test]
fn test_bookmark_rename() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
git::init_bare(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
let output = work_dir.run_jj(["bookmark", "rename", "bnoexist", "blocal"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: No such bookmark: bnoexist
[EOF]
[exit status: 1]
");
work_dir.run_jj(["describe", "-m=commit-0"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "blocal"])
.success();
let output = work_dir.run_jj(["bookmark", "rename", "blocal", "blocal1"]);
insta::assert_snapshot!(output, @"");
work_dir.run_jj(["new"]).success();
work_dir.run_jj(["describe", "-m=commit-1"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bexist"])
.success();
let output = work_dir.run_jj(["bookmark", "rename", "blocal1", "bexist"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Bookmark already exists: bexist
[EOF]
[exit status: 1]
");
work_dir.run_jj(["new"]).success();
work_dir.run_jj(["describe", "-m=commit-2"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bremote"])
.success();
work_dir
.run_jj(["git", "push", "--allow-new", "-b=bremote"])
.success();
let output = work_dir.run_jj(["bookmark", "rename", "bremote", "bremote2"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Tracked remote bookmarks for bookmark bremote were not renamed.
Hint: To rename the bookmark on the remote, you can `jj git push --bookmark bremote` first (to delete it on the remote), and then `jj git push --bookmark bremote2`. `jj git push --all` would also be sufficient.
[EOF]
");
let output = work_dir.run_jj(["bookmark", "rename", "bremote2", "bremote"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Tracked remote bookmarks for bookmark bremote exist.
Hint: Run `jj bookmark untrack 'glob:bremote@*'` to disassociate them.
[EOF]
");
}
#[test]
fn test_bookmark_rename_colocated() {
let test_env = TestEnvironment::default();
test_env
.run_jj_in(".", ["git", "init", "repo", "--colocate"])
.success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["describe", "-m=commit-0"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "blocal"])
.success();
// Make sure that git tracking bookmarks don't cause a warning
let output = work_dir.run_jj(["bookmark", "rename", "blocal", "blocal1"]);
insta::assert_snapshot!(output, @"");
}
#[test]
fn test_bookmark_forget_glob() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir
.run_jj(["bookmark", "create", "-r@", "foo-1"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bar-2"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "foo-3"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "foo-4"])
.success();
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar-2 foo-1 foo-3 foo-4 e8849ae12c70
◆ 000000000000
[EOF]
");
let output = work_dir.run_jj(["bookmark", "forget", "glob:foo-[1-3]"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Forgot 2 local bookmarks.
[EOF]
");
work_dir.run_jj(["undo"]).success();
let output = work_dir.run_jj(["bookmark", "forget", "glob:foo-[1-3]"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Forgot 2 local bookmarks.
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar-2 foo-4 e8849ae12c70
◆ 000000000000
[EOF]
");
// Forgetting a bookmark via both explicit name and glob pattern, or with
// multiple glob patterns, shouldn't produce an error.
let output = work_dir.run_jj(["bookmark", "forget", "foo-4", "glob:foo-*", "glob:foo-*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Forgot 1 local bookmarks.
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar-2 e8849ae12c70
◆ 000000000000
[EOF]
");
// Malformed glob
let output = work_dir.run_jj(["bookmark", "forget", "glob:foo-[1-3"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: invalid value 'glob:foo-[1-3' for '<NAMES>...': Pattern syntax error near position 4: invalid range pattern
For more information, try '--help'.
[EOF]
[exit status: 2]
");
// We get an error if none of the globs match anything
let output = work_dir.run_jj(["bookmark", "forget", "glob:bar*", "glob:baz*", "glob:boom*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: No matching bookmarks for patterns: baz*, boom*
[EOF]
[exit status: 1]
");
}
#[test]
fn test_bookmark_delete_glob() {
// Set up a git repo with a bookmark and a jj repo that has it as a remote.
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git::init_bare(git_repo_path);
let blob_oid = git_repo.write_blob(b"content").unwrap();
let mut tree_editor = git_repo
.edit_tree(gix::ObjectId::empty_tree(gix::hash::Kind::default()))
.unwrap();
tree_editor
.upsert("file", gix::object::tree::EntryKind::Blob, blob_oid)
.unwrap();
let _tree_id = tree_editor.write().unwrap();
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
work_dir.run_jj(["describe", "-m=commit"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "foo-1"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bar-2"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "foo-3"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "foo-4"])
.success();
// Push to create remote-tracking bookmarks
work_dir.run_jj(["git", "push", "--all"]).success();
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar-2 foo-1 foo-3 foo-4 8e056f6b8c37
◆ 000000000000
[EOF]
");
let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3]"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Deleted 2 bookmarks.
[EOF]
");
work_dir.run_jj(["undo"]).success();
let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3]"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Deleted 2 bookmarks.
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar-2 foo-1@origin foo-3@origin foo-4 8e056f6b8c37
◆ 000000000000
[EOF]
");
// We get an error if none of the globs match live bookmarks. Unlike `jj
// bookmark forget`, it's not allowed to delete already deleted bookmarks.
let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3]"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: No matching bookmarks for patterns: foo-[1-3]
[EOF]
[exit status: 1]
");
// Deleting a bookmark via both explicit name and glob pattern, or with
// multiple glob patterns, shouldn't produce an error.
let output = work_dir.run_jj(["bookmark", "delete", "foo-4", "glob:foo-*", "glob:foo-*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Deleted 1 bookmarks.
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar-2 foo-1@origin foo-3@origin foo-4@origin 8e056f6b8c37
◆ 000000000000
[EOF]
");
// The deleted bookmarks are still there
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
bar-2: qpvuntsm 8e056f6b (empty) commit
@origin: qpvuntsm 8e056f6b (empty) commit
foo-1 (deleted)
@origin: qpvuntsm 8e056f6b (empty) commit
foo-3 (deleted)
@origin: qpvuntsm 8e056f6b (empty) commit
foo-4 (deleted)
@origin: qpvuntsm 8e056f6b (empty) commit
[EOF]
");
// Malformed glob
let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: invalid value 'glob:foo-[1-3' for '<NAMES>...': Pattern syntax error near position 4: invalid range pattern
For more information, try '--help'.
[EOF]
[exit status: 2]
");
// Unknown pattern kind
let output = work_dir.run_jj(["bookmark", "forget", "whatever:bookmark"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: invalid value 'whatever:bookmark' for '<NAMES>...': Invalid string pattern kind `whatever:`
For more information, try '--help'.
Hint: Try prefixing with one of `exact:`, `glob:`, `regex:`, `substring:`, or one of these with `-i` suffix added (e.g. `glob-i:`) for case-insensitive matching
[EOF]
[exit status: 2]
");
}
#[test]
fn test_bookmark_delete_export() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["new"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "foo"])
.success();
work_dir.run_jj(["git", "export"]).success();
work_dir.run_jj(["bookmark", "delete", "foo"]).success();
let output = work_dir.run_jj(["bookmark", "list", "--all-remotes"]);
insta::assert_snapshot!(output, @r"
foo (deleted)
@git: rlvkpnrz 43444d88 (empty) (no description set)
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted will be deleted from the underlying Git repo on the next `jj git export`.
[EOF]
");
work_dir.run_jj(["git", "export"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
}
#[test]
fn test_bookmark_forget_export() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.run_jj(["new"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "foo"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
foo: rlvkpnrz 43444d88 (empty) (no description set)
[EOF]
");
// Exporting the bookmark to git creates a local-git tracking bookmark
let output = work_dir.run_jj(["git", "export"]);
insta::assert_snapshot!(output, @"");
let output = work_dir.run_jj(["bookmark", "forget", "--include-remotes", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Forgot 1 local bookmarks.
Forgot 1 remote bookmarks.
[EOF]
");
// Forgetting a bookmark with --include-remotes deletes local and
// remote-tracking bookmarks including the corresponding git-tracking bookmark.
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
let output = work_dir.run_jj(["log", "-r=foo", "--no-graph"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Revision `foo` doesn't exist
[EOF]
[exit status: 1]
");
// `jj git export` will delete the bookmark from git. In a colocated repo,
// this will happen automatically immediately after a `jj bookmark forget`.
// This is demonstrated in `test_git_colocated_bookmark_forget` in
// test_git_colocated.rs
let output = work_dir.run_jj(["git", "export"]);
insta::assert_snapshot!(output, @"");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
}
#[test]
fn test_bookmark_forget_fetched_bookmark() {
// Much of this test is borrowed from `test_git_fetch_remote_only_bookmark` in
// test_git_fetch.rs
// Set up a git repo with a bookmark and a jj repo that has it as a remote.
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-bookmark = true");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git::init_bare(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
// Create a commit and a bookmark in the git repo
let git::CommitResult {
tree_id,
commit_id: first_git_repo_commit,
} = git::add_commit(
&git_repo,
"refs/heads/feature1",
"file",
b"content",
"message",
&[],
);
// Fetch normally
work_dir
.run_jj(["git", "fetch", "--remote=origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qomsplrm ebeb70d8 message
@origin: qomsplrm ebeb70d8 message
[EOF]
");
// TEST 1: with export-import
// Forget the bookmark with --include-remotes
work_dir
.run_jj(["bookmark", "forget", "--include-remotes", "feature1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
// At this point `jj git export && jj git import` does *not* recreate the
// bookmark. This behavior is important in colocated repos, as otherwise a
// forgotten bookmark would be immediately resurrected.
//
// Technically, this is because `jj bookmark forget` preserved
// the ref in jj view's `git_refs` tracking the local git repo's remote-tracking
// bookmark.
// TODO: Show that jj git push is also a no-op
let output = work_dir.run_jj(["git", "export"]);
insta::assert_snapshot!(output, @"");
let output = work_dir.run_jj(["git", "import"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
// We can fetch feature1 again.
let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qomsplrm ebeb70d8 message
@origin: qomsplrm ebeb70d8 message
[EOF]
");
// TEST 2: No export/import (otherwise the same as test 1)
work_dir
.run_jj(["bookmark", "forget", "--include-remotes", "feature1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
// Fetch works even without the export-import
let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qomsplrm ebeb70d8 message
@origin: qomsplrm ebeb70d8 message
[EOF]
");
// TEST 3: fetch bookmark that was moved & forgotten with --include-remotes
// Move the bookmark in the git repo.
git::write_commit(
&git_repo,
"refs/heads/feature1",
tree_id,
"another message",
&[first_git_repo_commit],
);
let output = work_dir.run_jj(["bookmark", "forget", "--include-remotes", "feature1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Forgot 1 local bookmarks.
Forgot 1 remote bookmarks.
[EOF]
");
// Fetching a moved bookmark does not create a conflict
let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@origin [new] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: tyvxnvqr 9175cb32 (empty) another message
@origin: tyvxnvqr 9175cb32 (empty) another message
[EOF]
");
// TEST 4: If `--include-remotes` isn't used, remote bookmarks are untracked
work_dir
.run_jj(["bookmark", "forget", "feature1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1@origin: tyvxnvqr 9175cb32 (empty) another message
[EOF]
");
// There should be no output here since the remote bookmark wasn't forgotten
let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1@origin: tyvxnvqr 9175cb32 (empty) another message
[EOF]
");
}
#[test]
fn test_bookmark_forget_deleted_or_nonexistent_bookmark() {
// Much of this test is borrowed from `test_git_fetch_remote_only_bookmark` in
// test_git_fetch.rs
// ======== Beginning of test setup ========
// Set up a git repo with a bookmark and a jj repo that has it as a remote.
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-bookmark = true");
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git::init_bare(git_repo_path);
// Create a commit and a bookmark in the git repo
git::add_commit(
&git_repo,
"refs/heads/feature1",
"file",
b"content",
"message",
&[],
);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
// Fetch and then delete the bookmark
work_dir
.run_jj(["git", "fetch", "--remote=origin"])
.success();
work_dir
.run_jj(["bookmark", "delete", "feature1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1 (deleted)
@origin: qomsplrm ebeb70d8 message
[EOF]
");
// ============ End of test setup ============
// We can forget a deleted bookmark
work_dir
.run_jj(["bookmark", "forget", "--include-remotes", "feature1"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
// Can't forget a non-existent bookmark
let output = work_dir.run_jj(["bookmark", "forget", "i_do_not_exist"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: No such bookmark: i_do_not_exist
[EOF]
[exit status: 1]
");
}
#[test]
fn test_bookmark_track_untrack() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git::init(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
// Fetch new commit without auto tracking. No local bookmarks should be
// created.
create_commit_with_refs(
&git_repo,
"commit 1",
b"content 1",
&[
"refs/heads/main",
"refs/heads/feature1",
"refs/heads/feature2",
],
);
test_env.add_config("git.auto-local-bookmark = false");
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@origin [new] untracked
bookmark: feature2@origin [new] untracked
bookmark: main@origin [new] untracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1@origin: qxxqrkql bd843888 commit 1
feature2@origin: qxxqrkql bd843888 commit 1
main@origin: qxxqrkql bd843888 commit 1
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ e8849ae12c70
│ ◆ feature1@origin feature2@origin main@origin bd843888ee66
├─╯
◆ 000000000000
[EOF]
");
// Track new bookmark. Local bookmark should be created.
work_dir
.run_jj(["bookmark", "track", "feature1@origin", "main@origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qxxqrkql bd843888 commit 1
@origin: qxxqrkql bd843888 commit 1
feature2@origin: qxxqrkql bd843888 commit 1
main: qxxqrkql bd843888 commit 1
@origin: qxxqrkql bd843888 commit 1
[EOF]
");
// Track existing bookmark. Local bookmark should result in conflict.
work_dir
.run_jj(["bookmark", "create", "-r@", "feature2"])
.success();
work_dir
.run_jj(["bookmark", "track", "feature2@origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qxxqrkql bd843888 commit 1
@origin: qxxqrkql bd843888 commit 1
feature2 (conflicted):
+ qpvuntsm e8849ae1 (empty) (no description set)
+ qxxqrkql bd843888 commit 1
@origin (behind by 1 commits): qxxqrkql bd843888 commit 1
main: qxxqrkql bd843888 commit 1
@origin: qxxqrkql bd843888 commit 1
[EOF]
");
// Untrack existing and locally-deleted bookmarks. Bookmark targets should be
// unchanged
work_dir
.run_jj(["bookmark", "delete", "feature2"])
.success();
work_dir
.run_jj(["bookmark", "untrack", "feature1@origin", "feature2@origin"])
.success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qxxqrkql bd843888 commit 1
feature1@origin: qxxqrkql bd843888 commit 1
feature2@origin: qxxqrkql bd843888 commit 1
main: qxxqrkql bd843888 commit 1
@origin: qxxqrkql bd843888 commit 1
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ e8849ae12c70
│ ◆ feature1 feature1@origin feature2@origin main bd843888ee66
├─╯
◆ 000000000000
[EOF]
");
// Fetch new commit. Only tracking bookmark "main" should be merged.
create_commit_with_refs(
&git_repo,
"commit 2",
b"content 2",
&[
"refs/heads/main",
"refs/heads/feature1",
"refs/heads/feature2",
],
);
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@origin [updated] untracked
bookmark: feature2@origin [updated] untracked
bookmark: main@origin [updated] tracked
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qxxqrkql bd843888 commit 1
feature1@origin: psynomvr 48ec79a4 commit 2
feature2@origin: psynomvr 48ec79a4 commit 2
main: psynomvr 48ec79a4 commit 2
@origin: psynomvr 48ec79a4 commit 2
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ e8849ae12c70
│ ◆ feature1@origin feature2@origin main 48ec79a430e9
├─╯
│ ○ feature1 bd843888ee66
├─╯
◆ 000000000000
[EOF]
");
// Fetch new commit with auto tracking. Tracking bookmark "main" and new
// bookmark "feature3" should be merged.
create_commit_with_refs(
&git_repo,
"commit 3",
b"content 3",
&[
"refs/heads/main",
"refs/heads/feature1",
"refs/heads/feature2",
"refs/heads/feature3",
],
);
test_env.add_config("git.auto-local-bookmark = true");
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@origin [updated] untracked
bookmark: feature2@origin [updated] untracked
bookmark: feature3@origin [new] tracked
bookmark: main@origin [updated] tracked
Abandoned 1 commits that are no longer reachable.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: qxxqrkql bd843888 commit 1
feature1@origin: yumopmsr d8cd3e02 commit 3
feature2@origin: yumopmsr d8cd3e02 commit 3
feature3: yumopmsr d8cd3e02 commit 3
@origin: yumopmsr d8cd3e02 commit 3
main: yumopmsr d8cd3e02 commit 3
@origin: yumopmsr d8cd3e02 commit 3
[EOF]
");
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ e8849ae12c70
│ ◆ feature1@origin feature2@origin feature3 main d8cd3e020382
├─╯
│ ○ feature1 bd843888ee66
├─╯
◆ 000000000000
[EOF]
");
}
#[test]
fn test_bookmark_track_conflict() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let git_repo_path = test_env.env_root().join("git-repo");
git::init_bare(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", "main"])
.success();
work_dir.run_jj(["describe", "-m", "a"]).success();
work_dir
.run_jj(["git", "push", "--allow-new", "-b", "main"])
.success();
work_dir
.run_jj(["bookmark", "untrack", "main@origin"])
.success();
work_dir
.run_jj(["describe", "-m", "b", "-r", "main", "--ignore-immutable"])
.success();
let output = work_dir.run_jj(["bookmark", "track", "main@origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Started tracking 1 remote bookmarks.
main (conflicted):
+ qpvuntsm?? 56b9f16b (empty) b
+ qpvuntsm?? 7d5ca8e4 (empty) a
@origin (behind by 1 commits): qpvuntsm?? 7d5ca8e4 (empty) a
[EOF]
");
}
#[test]
fn test_bookmark_track_untrack_patterns() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
let git_repo = git::init(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
// Create remote commit
create_commit_with_refs(
&git_repo,
"commit",
b"content",
&["refs/heads/feature1", "refs/heads/feature2"],
);
// Fetch new commit without auto tracking
test_env.add_config("git.auto-local-bookmark = false");
let output = work_dir.run_jj(["git", "fetch"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@origin [new] untracked
bookmark: feature2@origin [new] untracked
[EOF]
");
// Track local bookmark
work_dir
.run_jj(["bookmark", "create", "-r@", "main"])
.success();
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "main"]), @r"
------- stderr -------
error: invalid value 'main' for '<BOOKMARK@REMOTE>...': remote bookmark must be specified in bookmark@remote form
For more information, try '--help'.
[EOF]
[exit status: 2]
");
// Track/untrack unknown bookmark
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "main@origin"]), @r"
------- stderr -------
Error: No such remote bookmark: main@origin
[EOF]
[exit status: 1]
");
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "untrack", "main@origin"]), @r"
------- stderr -------
Error: No such remote bookmark: main@origin
[EOF]
[exit status: 1]
");
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "glob:maine@*"]), @r"
------- stderr -------
Error: No matching remote bookmarks for patterns: maine@*
[EOF]
[exit status: 1]
");
insta::assert_snapshot!(
work_dir.run_jj(["bookmark", "untrack", "main@origin", "glob:main@o*"]), @r"
------- stderr -------
Error: No matching remote bookmarks for patterns: main@origin, main@o*
[EOF]
[exit status: 1]
");
// Track already tracked bookmark
work_dir
.run_jj(["bookmark", "track", "feature1@origin"])
.success();
let output = work_dir.run_jj(["bookmark", "track", "feature1@origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Remote bookmark already tracked: feature1@origin
Nothing changed.
[EOF]
");
// Untrack non-tracking bookmark
let output = work_dir.run_jj(["bookmark", "untrack", "feature2@origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Remote bookmark not tracked yet: feature2@origin
Nothing changed.
[EOF]
");
// Untrack Git-tracking bookmark
work_dir.run_jj(["git", "export"]).success();
let output = work_dir.run_jj(["bookmark", "untrack", "main@git"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Git-tracking bookmark cannot be untracked: main@git
Nothing changed.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: yrnqsqlx 41e7a49d commit
@git: yrnqsqlx 41e7a49d commit
@origin: yrnqsqlx 41e7a49d commit
feature2@origin: yrnqsqlx 41e7a49d commit
main: qpvuntsm e8849ae1 (empty) (no description set)
@git: qpvuntsm e8849ae1 (empty) (no description set)
[EOF]
");
// Untrack by pattern
let output = work_dir.run_jj(["bookmark", "untrack", "glob:*@*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Git-tracking bookmark cannot be untracked: feature1@git
Warning: Remote bookmark not tracked yet: feature2@origin
Warning: Git-tracking bookmark cannot be untracked: main@git
Stopped tracking 1 remote bookmarks.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: yrnqsqlx 41e7a49d commit
@git: yrnqsqlx 41e7a49d commit
feature1@origin: yrnqsqlx 41e7a49d commit
feature2@origin: yrnqsqlx 41e7a49d commit
main: qpvuntsm e8849ae1 (empty) (no description set)
@git: qpvuntsm e8849ae1 (empty) (no description set)
[EOF]
");
// Track by pattern
let output = work_dir.run_jj(["bookmark", "track", "glob:feature?@origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Started tracking 2 remote bookmarks.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: yrnqsqlx 41e7a49d commit
@git: yrnqsqlx 41e7a49d commit
@origin: yrnqsqlx 41e7a49d commit
feature2: yrnqsqlx 41e7a49d commit
@origin: yrnqsqlx 41e7a49d commit
main: qpvuntsm e8849ae1 (empty) (no description set)
@git: qpvuntsm e8849ae1 (empty) (no description set)
[EOF]
");
}
#[test]
fn test_bookmark_list() {
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-bookmark = true");
// Initialize remote refs
test_env.run_jj_in(".", ["git", "init", "remote"]).success();
let remote_dir = test_env.work_dir("remote");
for bookmark in [
"remote-sync",
"remote-unsync",
"remote-untrack",
"remote-delete",
] {
remote_dir
.run_jj(["new", "root()", "-m", bookmark])
.success();
remote_dir
.run_jj(["bookmark", "create", "-r@", bookmark])
.success();
}
remote_dir.run_jj(["new"]).success();
remote_dir.run_jj(["git", "export"]).success();
// Initialize local refs
let mut remote_git_path = remote_dir.root().to_owned();
remote_git_path.extend([".jj", "repo", "store", "git"]);
test_env
.run_jj_in(
".",
["git", "clone", remote_git_path.to_str().unwrap(), "local"],
)
.success();
let local_dir = test_env.work_dir("local");
local_dir
.run_jj(["new", "root()", "-m", "local-only"])
.success();
local_dir
.run_jj(["bookmark", "create", "-r@", "local-only"])
.success();
// Mutate refs in local repository
local_dir
.run_jj(["bookmark", "delete", "remote-delete"])
.success();
local_dir
.run_jj(["bookmark", "delete", "remote-untrack"])
.success();
local_dir
.run_jj(["bookmark", "untrack", "remote-untrack@origin"])
.success();
local_dir
.run_jj([
"bookmark",
"set",
"--allow-backwards",
"--to=@",
"remote-unsync",
])
.success();
// Synchronized tracking remotes and non-tracking remotes aren't listed by
// default
let output = local_dir.run_jj(["bookmark", "list"]);
insta::assert_snapshot!(output, @r"
local-only: wqnwkozp 0353dd35 (empty) local-only
remote-delete (deleted)
@origin: vruxwmqv b32031cf (empty) remote-delete
remote-sync: rlvkpnrz 7a07dbee (empty) remote-sync
remote-unsync: wqnwkozp 0353dd35 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
let output = local_dir.run_jj(["bookmark", "list", "--all-remotes"]);
insta::assert_snapshot!(output, @r"
local-only: wqnwkozp 0353dd35 (empty) local-only
remote-delete (deleted)
@origin: vruxwmqv b32031cf (empty) remote-delete
remote-sync: rlvkpnrz 7a07dbee (empty) remote-sync
@origin: rlvkpnrz 7a07dbee (empty) remote-sync
remote-unsync: wqnwkozp 0353dd35 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
remote-untrack@origin: royxmykx 149bc756 (empty) remote-untrack
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
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 output = local_dir.run_jj(["bookmark", "list", "--all-remotes", "-T", template]);
insta::assert_snapshot!(output, @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>
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
}
#[test]
fn test_bookmark_list_filtered() {
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-bookmark = true");
test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
// Initialize remote refs
test_env.run_jj_in(".", ["git", "init", "remote"]).success();
let remote_dir = test_env.work_dir("remote");
for bookmark in ["remote-keep", "remote-delete", "remote-rewrite"] {
remote_dir
.run_jj(["new", "root()", "-m", bookmark])
.success();
remote_dir
.run_jj(["bookmark", "create", "-r@", bookmark])
.success();
}
remote_dir.run_jj(["new"]).success();
remote_dir.run_jj(["git", "export"]).success();
// Initialize local refs
let mut remote_git_path = remote_dir.root().to_owned();
remote_git_path.extend([".jj", "repo", "store", "git"]);
test_env
.run_jj_in(
".",
["git", "clone", remote_git_path.to_str().unwrap(), "local"],
)
.success();
let local_dir = test_env.work_dir("local");
local_dir
.run_jj(["new", "root()", "-m", "local-keep"])
.success();
local_dir
.run_jj(["bookmark", "create", "-r@", "local-keep"])
.success();
// Mutate refs in local repository
local_dir
.run_jj(["bookmark", "delete", "remote-delete"])
.success();
local_dir
.run_jj(["describe", "-mrewritten", "remote-rewrite"])
.success();
let template = r#"separate(" ", commit_id.short(), bookmarks, if(hidden, "(hidden)"))"#;
insta::assert_snapshot!(
local_dir.run_jj(["log", "-r::(bookmarks() | remote_bookmarks())", "-T", template]), @r"
@ 4b2bc95cbda6 local-keep
│ ○ e6970e0e1f55 remote-rewrite*
├─╯
│ ○ 331d500d2fda remote-rewrite@origin (hidden)
├─╯
│ ○ 0e6b796871e6 remote-delete@origin
├─╯
│ ○ c2f2ee40f03a remote-keep
├─╯
◆ 000000000000
[EOF]
");
// All bookmarks are listed by default.
let output = local_dir.run_jj(["bookmark", "list"]);
insta::assert_snapshot!(output, @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
remote-delete (deleted)
@origin: zsuskuln 0e6b7968 (empty) remote-delete
remote-keep: rlvkpnrz c2f2ee40 (empty) remote-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
let query =
|args: &[&str]| local_dir.run_jj_with(|cmd| cmd.args(["bookmark", "list"]).args(args));
// "all()" doesn't include deleted bookmarks since they have no local targets.
// So "all()" is identical to "bookmarks()".
insta::assert_snapshot!(query(&["-rall()"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
remote-keep: rlvkpnrz c2f2ee40 (empty) remote-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
");
// Exclude remote-only bookmarks. "remote-rewrite@origin" is included since
// local "remote-rewrite" target matches.
insta::assert_snapshot!(query(&["-rbookmarks()"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
remote-keep: rlvkpnrz c2f2ee40 (empty) remote-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
");
// Select bookmarks by name.
insta::assert_snapshot!(query(&["remote-rewrite"]), @r"
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
");
insta::assert_snapshot!(query(&["-rbookmarks(remote-rewrite)"]), @r"
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
");
// Select bookmarks by name, combined with --all-remotes
local_dir.run_jj(["git", "export"]).success();
insta::assert_snapshot!(query(&["--all-remotes", "remote-rewrite"]), @r"
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@git: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
");
insta::assert_snapshot!(query(&["--all-remotes", "-rbookmarks(remote-rewrite)"]), @r"
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@git: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
");
// Select bookmarks with --remote
insta::assert_snapshot!(query(&["--remote", "origin"]), @r"
remote-delete (deleted)
@origin: zsuskuln 0e6b7968 (empty) remote-delete
remote-keep: rlvkpnrz c2f2ee40 (empty) remote-keep
@origin: rlvkpnrz c2f2ee40 (empty) remote-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
insta::assert_snapshot!(query(&["--remote", "glob:gi?"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
@git: kpqxywon 4b2bc95c (empty) local-keep
remote-keep: rlvkpnrz c2f2ee40 (empty) remote-keep
@git: rlvkpnrz c2f2ee40 (empty) remote-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@git: royxmykx e6970e0e (empty) rewritten
[EOF]
");
insta::assert_snapshot!(query(&["--remote", "origin", "--remote", "git"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
@git: kpqxywon 4b2bc95c (empty) local-keep
remote-delete (deleted)
@origin: zsuskuln 0e6b7968 (empty) remote-delete
remote-keep: rlvkpnrz c2f2ee40 (empty) remote-keep
@git: rlvkpnrz c2f2ee40 (empty) remote-keep
@origin: rlvkpnrz c2f2ee40 (empty) remote-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@git: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
// Can select deleted bookmark by name pattern, but not by revset.
insta::assert_snapshot!(query(&["remote-delete"]), @r"
remote-delete (deleted)
@origin: zsuskuln 0e6b7968 (empty) remote-delete
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
insta::assert_snapshot!(query(&["-rbookmarks(remote-delete)"]), @"");
insta::assert_snapshot!(query(&["-rremote-delete"]), @r"
------- stderr -------
Error: Revision `remote-delete` doesn't exist
Hint: Did you mean `remote-delete@origin`, `remote-keep`, `remote-rewrite`, `remote-rewrite@origin`?
[EOF]
[exit status: 1]
");
// Name patterns are OR-ed.
insta::assert_snapshot!(query(&["glob:*-keep", "remote-delete"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
remote-delete (deleted)
@origin: zsuskuln 0e6b7968 (empty) remote-delete
remote-keep: rlvkpnrz c2f2ee40 (empty) remote-keep
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
// Unmatched name pattern shouldn't be an error. A warning can be added later.
insta::assert_snapshot!(query(&["local-keep", "glob:push-*"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
[EOF]
");
// Name pattern and revset are OR-ed.
insta::assert_snapshot!(query(&["local-keep", "-rbookmarks(remote-rewrite)"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@origin (ahead by 1 commits, behind by 1 commits): royxmykx hidden 331d500d (empty) remote-rewrite
[EOF]
");
// … but still filtered by --remote
insta::assert_snapshot!(query(&[
"local-keep",
"-rbookmarks(remote-rewrite)",
"--remote",
"git",
]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
@git: kpqxywon 4b2bc95c (empty) local-keep
remote-rewrite: royxmykx e6970e0e (empty) rewritten
@git: royxmykx e6970e0e (empty) rewritten
[EOF]
");
}
#[test]
fn test_bookmark_list_much_remote_divergence() {
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-bookmark = true");
// Initialize remote refs
test_env.run_jj_in(".", ["git", "init", "remote"]).success();
let remote_dir = test_env.work_dir("remote");
remote_dir
.run_jj(["new", "root()", "-m", "remote-unsync"])
.success();
for _ in 0..15 {
remote_dir.run_jj(["new", "-m", "remote-unsync"]).success();
}
remote_dir
.run_jj(["bookmark", "create", "-r@", "remote-unsync"])
.success();
remote_dir.run_jj(["new"]).success();
remote_dir.run_jj(["git", "export"]).success();
// Initialize local refs
let mut remote_git_path = remote_dir.root().to_owned();
remote_git_path.extend([".jj", "repo", "store", "git"]);
test_env
.run_jj_in(
".",
["git", "clone", remote_git_path.to_str().unwrap(), "local"],
)
.success();
let local_dir = test_env.work_dir("local");
local_dir
.run_jj(["new", "root()", "-m", "local-only"])
.success();
for _ in 0..15 {
local_dir.run_jj(["new", "-m", "local-only"]).success();
}
local_dir
.run_jj(["bookmark", "create", "-r@", "local-only"])
.success();
// Mutate refs in local repository
local_dir
.run_jj([
"bookmark",
"set",
"--allow-backwards",
"--to=@",
"remote-unsync",
])
.success();
let output = local_dir.run_jj(["bookmark", "list"]);
insta::assert_snapshot!(output, @r"
local-only: zkyosouw a30800ad (empty) local-only
remote-unsync: zkyosouw a30800ad (empty) local-only
@origin (ahead by at least 10 commits, behind by at least 10 commits): uyznsvlq a52367f8 (empty) remote-unsync
[EOF]
");
}
#[test]
fn test_bookmark_list_tracked() {
let test_env = TestEnvironment::default();
test_env.add_config("git.auto-local-bookmark = true");
// Initialize remote refs
test_env.run_jj_in(".", ["git", "init", "remote"]).success();
let remote_dir = test_env.work_dir("remote");
for bookmark in [
"remote-sync",
"remote-unsync",
"remote-untrack",
"remote-delete",
] {
remote_dir
.run_jj(["new", "root()", "-m", bookmark])
.success();
remote_dir
.run_jj(["bookmark", "create", "-r@", bookmark])
.success();
}
remote_dir.run_jj(["new"]).success();
remote_dir.run_jj(["git", "export"]).success();
// Initialize local refs
let mut remote_git_path = remote_dir.root().to_owned();
remote_git_path.extend([".jj", "repo", "store", "git"]);
test_env
.run_jj_in(
".",
[
"git",
"clone",
"--colocate",
remote_git_path.to_str().unwrap(),
"local",
],
)
.success();
test_env
.run_jj_in(".", ["git", "init", "upstream"])
.success();
// Initialize a second remote
let upstream_dir = test_env.work_dir("upstream");
upstream_dir
.run_jj(["new", "root()", "-m", "upstream-sync"])
.success();
upstream_dir
.run_jj(["bookmark", "create", "-r@", "upstream-sync"])
.success();
upstream_dir.run_jj(["new"]).success();
upstream_dir.run_jj(["git", "export"]).success();
let mut upstream_git_path = upstream_dir.root().to_owned();
upstream_git_path.extend([".jj", "repo", "store", "git"]);
let local_dir = test_env.work_dir("local");
local_dir
.run_jj([
"git",
"remote",
"add",
"upstream",
upstream_git_path.to_str().unwrap(),
])
.success();
local_dir
.run_jj(["git", "fetch", "--all-remotes"])
.success();
local_dir
.run_jj(["new", "root()", "-m", "local-only"])
.success();
local_dir
.run_jj(["bookmark", "create", "-r@", "local-only"])
.success();
// Mutate refs in local repository
local_dir
.run_jj(["bookmark", "delete", "remote-delete"])
.success();
local_dir
.run_jj(["bookmark", "delete", "remote-untrack"])
.success();
local_dir
.run_jj(["bookmark", "untrack", "remote-untrack@origin"])
.success();
local_dir
.run_jj([
"git",
"push",
"--allow-new",
"--remote",
"upstream",
"--bookmark",
"remote-unsync",
])
.success();
local_dir
.run_jj([
"bookmark",
"set",
"--to=@",
"--allow-backwards",
"remote-unsync",
])
.success();
let output = local_dir.run_jj(["bookmark", "list", "--all-remotes"]);
insta::assert_snapshot!(output, @r"
local-only: nmzmmopx 2a685e16 (empty) local-only
@git: nmzmmopx 2a685e16 (empty) local-only
remote-delete (deleted)
@origin: vruxwmqv b32031cf (empty) remote-delete
remote-sync: rlvkpnrz 7a07dbee (empty) remote-sync
@git: rlvkpnrz 7a07dbee (empty) remote-sync
@origin: rlvkpnrz 7a07dbee (empty) remote-sync
remote-unsync: nmzmmopx 2a685e16 (empty) local-only
@git: nmzmmopx 2a685e16 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
@upstream (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
remote-untrack@origin: royxmykx 149bc756 (empty) remote-untrack
upstream-sync: lylxulpl 169ba7d9 (empty) upstream-sync
@git: lylxulpl 169ba7d9 (empty) upstream-sync
@upstream: lylxulpl 169ba7d9 (empty) upstream-sync
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
let output = local_dir.run_jj(["bookmark", "list", "--tracked"]);
insta::assert_snapshot!(output, @r"
remote-delete (deleted)
@origin: vruxwmqv b32031cf (empty) remote-delete
remote-sync: rlvkpnrz 7a07dbee (empty) remote-sync
@origin: rlvkpnrz 7a07dbee (empty) remote-sync
remote-unsync: nmzmmopx 2a685e16 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
@upstream (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
upstream-sync: lylxulpl 169ba7d9 (empty) upstream-sync
@upstream: lylxulpl 169ba7d9 (empty) upstream-sync
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
let output = local_dir.run_jj(["bookmark", "list", "--tracked", "--remote", "origin"]);
insta::assert_snapshot!(output, @r"
remote-delete (deleted)
@origin: vruxwmqv b32031cf (empty) remote-delete
remote-sync: rlvkpnrz 7a07dbee (empty) remote-sync
@origin: rlvkpnrz 7a07dbee (empty) remote-sync
remote-unsync: nmzmmopx 2a685e16 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
[EOF]
------- stderr -------
Hint: Bookmarks marked as deleted can be *deleted permanently* on the remote by running `jj git push --deleted`. Use `jj bookmark forget` if you don't want that.
[EOF]
");
let output = local_dir.run_jj(["bookmark", "list", "--tracked", "remote-unsync"]);
insta::assert_snapshot!(output, @r"
remote-unsync: nmzmmopx 2a685e16 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
@upstream (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
[EOF]
");
let output = local_dir.run_jj(["bookmark", "list", "--tracked", "remote-untrack"]);
insta::assert_snapshot!(output, @"");
local_dir
.run_jj(["bookmark", "untrack", "remote-unsync@upstream"])
.success();
let output = local_dir.run_jj(["bookmark", "list", "--tracked", "remote-unsync"]);
insta::assert_snapshot!(output, @r"
remote-unsync: nmzmmopx 2a685e16 (empty) local-only
@origin (ahead by 1 commits, behind by 1 commits): zsuskuln 553203ba (empty) remote-unsync
[EOF]
");
}
#[test]
fn test_bookmark_list_conflicted() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Track existing bookmark. Local bookmark should result in conflict.
work_dir.run_jj(["new", "root()", "-m", "a"]).success();
work_dir.run_jj(["new", "root()", "-m", "b"]).success();
work_dir
.run_jj(["bookmark", "create", "-r@", "bar"])
.success();
work_dir
.run_jj(["bookmark", "create", "foo", "-r", "description(a)"])
.success();
work_dir
.run_jj([
"bookmark",
"create",
"foo",
"-r",
"description(b)",
"--at-op=@-",
])
.success();
work_dir.run_jj(["status"]).success();
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
bar: kkmpptxz a82129fb (empty) b
foo (conflicted):
+ rlvkpnrz 4e1b2d80 (empty) a
+ kkmpptxz a82129fb (empty) b
[EOF]
");
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "list", "--conflicted"]), @r"
foo (conflicted):
+ rlvkpnrz 4e1b2d80 (empty) a
+ kkmpptxz a82129fb (empty) b
[EOF]
");
}
#[test]
fn test_bookmark_list_sort_unknown_key_error() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "list", "--sort", "date"]), @r"
------- stderr -------
error: invalid value 'date' for '--sort <SORT_KEY>'
[possible values: name, name-, author-name, author-name-, author-email, author-email-, author-date, author-date-, committer-name, committer-name-, committer-email, committer-email-, committer-date, committer-date-]
For more information, try '--help'.
[EOF]
[exit status: 2]
");
}
#[test]
fn test_bookmark_list_sort_multiple_keys() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
for (bookmark, email) in [("c", "bob@g.c"), ("b", "alice@g.c"), ("a", "bob@g.c")] {
work_dir
.run_jj([
&format!("--config=user.email={email}"),
"new",
"root()",
"-m",
"fix",
])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", bookmark])
.success();
}
let template =
r#"name ++ ": " ++ if(normal_target, normal_target.committer().email()) ++ "\n""#;
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "list", "-T", template, "--sort", "committer-email,committer-date-"]), @r"
b: alice@g.c
a: bob@g.c
c: bob@g.c
[EOF]
");
}
#[test]
fn test_bookmark_list_sort_using_config() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
for (bookmark, email) in [("c", "bob@g.c"), ("b", "alice@g.c"), ("a", "bob@g.c")] {
work_dir
.run_jj([
&format!("--config=user.email={email}"),
"new",
"root()",
"-m",
"fix",
])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", bookmark])
.success();
}
let template = r#"name ++ ": " ++ if(normal_target, normal_target.author().email()) ++ "\n""#;
insta::assert_snapshot!(work_dir.run_jj([
"--config=ui.bookmark-list-sort-keys=['author-email', 'author-date-']",
"bookmark",
"list",
"-T",
template
]), @r"
b: alice@g.c
a: bob@g.c
c: bob@g.c
[EOF]
");
}
#[test]
fn test_bookmark_list_sort_overriding_config() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
for (bookmark, email) in [("c", "bob@g.c"), ("b", "alice@g.c"), ("a", "bob@g.c")] {
work_dir
.run_jj([
&format!("--config=user.email={email}"),
"new",
"root()",
"-m",
"fix",
])
.success();
work_dir
.run_jj(["bookmark", "create", "-r@", bookmark])
.success();
}
let template = r#"name ++ ": " ++ if(normal_target, normal_target.author().email()) ++ "\n""#;
insta::assert_snapshot!(work_dir.run_jj([
"--config=ui.bookmark-list-sort-keys=['author-email', 'author-date-']",
"bookmark",
"list",
"--sort=name-", // overriding config.
"-T",
template
]), @r"
c: bob@g.c
b: alice@g.c
a: bob@g.c
[EOF]
");
}
#[test]
fn test_bookmark_create_with_default_target_revision() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["bookmark", "create", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision was not specified, defaulting to the working copy (-r@). In the near future it will be required to explicitly specify target revision.
Created 1 bookmarks pointing to qpvuntsm e8849ae1 foo | (empty) (no description set)
[EOF]
");
}
#[test]
fn test_bookmark_set_with_default_target_revision() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let output = work_dir.run_jj(["bookmark", "set", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision was not specified, defaulting to the working copy (--revision=@). In the near future it will be required to explicitly specify target revision.
Created 1 bookmarks pointing to qpvuntsm e8849ae1 foo | (empty) (no description set)
[EOF]
");
}
#[test]
fn test_bookmark_move_with_default_target_revision() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Set up remote
let git_repo_path = test_env.env_root().join("git-repo");
git::init_bare(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin", "../git-repo"])
.success();
let output = work_dir.run_jj(["bookmark", "create", "foo", "-r@"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Created 1 bookmarks pointing to qpvuntsm e8849ae1 foo | (empty) (no description set)
[EOF]
");
work_dir.run_jj(["new"]).success();
let output = work_dir.run_jj(["bookmark", "move", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision was not specified, defaulting to the working copy (--to=@). In the near future it will be required to explicitly specify it.
Moved 1 bookmarks to zsuskuln 0e555a27 foo | (empty) (no description set)
[EOF]
");
}
#[must_use]
fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
let template = r#"bookmarks ++ " " ++ commit_id.short()"#;
work_dir.run_jj(["log", "-T", template])
}
#[must_use]
fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput {
// --quiet to suppress deleted bookmarks hint
work_dir.run_jj(["bookmark", "list", "--all-remotes", "--quiet"])
}