jj/cli/tests/test_bookmark_command.rs
Scott Taylor 33ad38bfed templates: add "divergent" label to log for divergent changes
It would be good to include the word "divergent" in the log when a
change is divergent, since users are often unsure what's happening when
they see a divergent change, and giving them a term to search for would
be helpful. However, I don't think it looks good to put this label next
to the change ID itself if both are the same color, since it ends up
being hard to distinguish from the change offset at a glance. Also,
putting the label next to the change ID also messes up the alignment of
fields in the log. Therefore, I think it looks better to put the
"divergent" label at the end of the line.

Since divergence and hidden commits are similar, it makes sense for both
labels to be in the same place, so I also moved the hidden label to the
end for consistency.

One downside is that the labels are less obviously connected with the
change ID itself due to them being farther apart. I think this could be
fine, since they are still visually connected by being the same color.
2025-12-20 16:55:51 +00:00

2818 lines
97 KiB
Rust
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 -------
Warning: Target revision is empty.
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"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision is empty.
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 -------
Warning: Target revision is empty.
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 -------
Warning: Target revision is empty.
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 -------
Warning: Target revision is empty.
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 -------
Warning: Target revision is empty.
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", ""]);
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://docs.jj-vcs.dev/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://docs.jj-vcs.dev/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://docs.jj-vcs.dev/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://docs.jj-vcs.dev/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 --remote=bar` to track it.
[EOF]
[exit status: 2]
");
// quoted name works
let output = work_dir.run_jj(["bookmark", "create", "'foo@bar'"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Warning: Target revision is empty.
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"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching bookmarks for names: foo
No bookmarks to update.
[EOF]
");
let output = work_dir.run_jj(["bookmark", "set", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision is empty.
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", "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 -------
Warning: Target revision is empty.
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 -------
Warning: Target revision is empty.
Moved 1 bookmarks to qpvuntsm e8849ae1 foo | (empty) (no description set)
[EOF]
");
let output = work_dir.run_jj(["bookmark", "move", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision is empty.
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 -------
Warning: Target revision is empty.
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", "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 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"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision is empty.
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"]).success();
work_dir.run_jj(["bookmark", "delete", "foo"]).success();
let output = work_dir.run_jj(["bookmark", "create", "foo"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision is empty.
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", "a1", "a2"])
.success();
work_dir.run_jj(["new", "-mhead1"]).success();
work_dir.run_jj(["new", "root()"]).success();
work_dir.run_jj(["bookmark", "create", "b1"]).success();
work_dir.run_jj(["new"]).success();
work_dir.run_jj(["bookmark", "create", "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]
");
let setup_opid = work_dir.current_operation_id();
// The default could be considered "--from=all() *", but is disabled
let output = work_dir.run_jj(["bookmark", "move"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
error: the following required arguments were not provided:
<NAMES|--from <REVSETS>>
Usage: jj bookmark move <NAMES|--from <REVSETS>>
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()"]);
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=::@", "'a?'"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
No bookmarks to update.
[EOF]
");
// 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=::@"]);
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(["op", "restore", &setup_opid]).success();
// Move multiple bookmarks by name
let output = work_dir.run_jj(["bookmark", "move", "b1", "c1"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Moved 2 bookmarks to vruxwmqv 0dd9a4b1 b1 c1 | (empty) head2
[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(["op", "restore", &setup_opid]).success();
// Try to move multiple bookmarks, but one of them isn't fast-forward
let output = work_dir.run_jj(["bookmark", "move", "'?1'"]);
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+", "'?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", "subject(A0)", "-mA1"]).success();
// Set up conflicting bookmark.
work_dir
.run_jj(["bookmark", "create", "-rsubject(A0)", "foo"])
.success();
work_dir
.run_jj(["bookmark", "create", "--at-op=@-", "-rsubject(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", "-rsubject(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", "-rsubject(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", "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", "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", "bremote", "buntracked"])
.success();
work_dir
.run_jj(["git", "push", "--allow-new", "-b=bremote", "-b=buntracked"])
.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 --deleted` would also be sufficient.
[EOF]
");
let op_id_after_rename = work_dir.current_operation_id();
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 bremote` to disassociate them.
[EOF]
");
work_dir
.run_jj(["op", "restore", &op_id_after_rename])
.success();
let output = work_dir.run_jj(["git", "push", "--bookmark", "bremote2"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Changes to push to origin:
Add bookmark bremote2 to a9d7418c1c3f
[EOF]
");
work_dir
.run_jj(["git", "push", "--named", "bremote-untracked=@"])
.success();
work_dir
.run_jj(["bookmark", "forget", "bremote-untracked"])
.success();
let output = work_dir.run_jj(["bookmark", "rename", "bremote2", "bremote-untracked"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: The renamed bookmark already exists on the remote 'origin', tracking state was dropped.
Hint: To track the existing remote bookmark, run `jj bookmark track bremote-untracked --remote=origin`
Warning: Tracked remote bookmarks for bookmark bremote2 were not renamed.
Hint: To rename the bookmark on the remote, you can `jj git push --bookmark bremote2` first (to delete it on the remote), and then `jj git push --bookmark bremote-untracked`. `jj git push --all --deleted` would also be sufficient.
[EOF]
");
// rename an untracked bookmark
work_dir
.run_jj(["bookmark", "untrack", "buntracked"])
.success();
let output = work_dir.run_jj(["bookmark", "rename", "buntracked", "buntracked2"]);
insta::assert_snapshot!(output, @"");
}
#[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", "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", "foo-1"]).success();
work_dir.run_jj(["bookmark", "create", "bar-2"]).success();
work_dir.run_jj(["bookmark", "create", "foo-3"]).success();
work_dir.run_jj(["bookmark", "create", "foo-4"]).success();
let setup_opid = work_dir.current_operation_id();
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", "'foo-[1-3]'"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Forgot 2 local bookmarks.
[EOF]
");
work_dir.run_jj(["op", "restore", &setup_opid]).success();
let output = work_dir.run_jj(["bookmark", "forget", "'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", "foo-*", "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: Failed to parse name pattern: Invalid string pattern
Caused by:
1: --> 1:1
|
1 | glob:'foo-[1-3'
| ^-------------^
|
= Invalid string pattern
2: error parsing glob 'foo-[1-3': unclosed character class; missing ']'
[EOF]
[exit status: 1]
");
// None of the globs match anything
let output = work_dir.run_jj(["bookmark", "forget", "baz*", "boom*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
No bookmarks to forget.
[EOF]
");
}
#[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", "foo-1"]).success();
work_dir.run_jj(["bookmark", "create", "bar-2"]).success();
work_dir.run_jj(["bookmark", "create", "foo-3"]).success();
work_dir.run_jj(["bookmark", "create", "foo-4"]).success();
// Push to create remote-tracking bookmarks
work_dir.run_jj(["git", "push", "--all"]).success();
// Add absent-tracked bookmark
work_dir.run_jj(["bookmark", "create", "foo-5"]).success();
work_dir.run_jj(["bookmark", "track", "foo-5"]).success();
let setup_opid = work_dir.current_operation_id();
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ bar-2 foo-1 foo-3 foo-4 foo-5* 8e056f6b8c37
◆ 000000000000
[EOF]
");
let output = work_dir.run_jj(["bookmark", "delete", "'foo-[1-3]'"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Deleted 2 bookmarks.
[EOF]
");
work_dir.run_jj(["op", "restore", &setup_opid]).success();
let output = work_dir.run_jj(["bookmark", "delete", "'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 foo-5* 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", "'foo-[1-3]'"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
No bookmarks to delete.
[EOF]
");
// 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", "foo-*", "foo-*"]);
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@origin 8e056f6b8c37
◆ 000000000000
[EOF]
");
// The deleted bookmarks are still there, whereas absent-tracked bookmarks
// aren't.
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: Failed to parse name pattern: Invalid string pattern
Caused by:
1: --> 1:1
|
1 | glob:'foo-[1-3'
| ^-------------^
|
= Invalid string pattern
2: error parsing glob 'foo-[1-3': unclosed character class; missing ']'
[EOF]
[exit status: 1]
");
// Unknown pattern kind
let output = work_dir.run_jj(["bookmark", "forget", "whatever:bookmark"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: Failed to parse name pattern: Invalid string pattern
Caused by:
1: --> 1:1
|
1 | whatever:bookmark
| ^---------------^
|
= Invalid string pattern
2: Invalid string pattern kind `whatever:`
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: 1]
");
}
#[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", "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", "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
// workspace, 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("remotes.origin.auto-track-bookmarks = '*'");
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 workspaces, 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("remotes.origin.auto-track-bookmarks = '*'");
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
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]
");
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 -------
Warning: No matching bookmarks for names: i_do_not_exist
No bookmarks to forget.
[EOF]
");
}
#[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("remotes.origin.auto-track-bookmarks = '~*'");
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.
let output = work_dir.run_jj(["bookmark", "track", "feature1@origin", "main@origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: <bookmark>@<remote> syntax is deprecated, use `<bookmark> --remote=<remote>` instead.
Started tracking 2 remote bookmarks.
[EOF]
");
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 non-existent remote bookmark
let output = work_dir.run_jj(["bookmark", "track", "feature3", "--remote=origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: No matching bookmarks for names: feature3
Nothing changed.
[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"]).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();
let output = work_dir.run_jj(["bookmark", "untrack", "feature1@origin", "feature2@origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: <bookmark>@<remote> syntax is deprecated, use `<bookmark> --remote=<remote>` instead.
Stopped tracking 2 remote bookmarks.
[EOF]
");
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("remotes.origin.auto-track-bookmarks = '*'");
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");
// add three remotes
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 git_repo_path = test_env.env_root().join("git-repo2");
git::init_bare(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin2", "../git-repo2"])
.success();
let git_repo_path = test_env.env_root().join("git-repo3");
git::init_bare(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "origin3", "../git-repo3"])
.success();
// create bookmark and push to origin
work_dir.run_jj(["bookmark", "create", "main"]).success();
work_dir.run_jj(["describe", "-m", "a"]).success();
work_dir
.run_jj(["git", "push", "--allow-new", "-b", "main"])
.success();
// adjust main and push to origin2, again for origin3
work_dir
.run_jj(["describe", "-m", "b", "-r", "main", "--ignore-immutable"])
.success();
work_dir
.run_jj(["git", "push", "-N", "-b", "main", "--remote", "origin2"])
.success();
work_dir
.run_jj(["describe", "-m", "c", "-r", "main", "--ignore-immutable"])
.success();
work_dir
.run_jj(["git", "push", "-N", "-b", "main", "--remote", "origin3"])
.success();
// stop and retrack origin; creates conflict
// origin2 and origin3 are not shown
work_dir
.run_jj(["bookmark", "untrack", "main", "--remote=origin"])
.success();
let output = work_dir.run_jj(["bookmark", "track", "main", "--remote=origin"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Started tracking 1 remote bookmarks.
main (conflicted):
+ qpvuntsm/0 467e027c (divergent) (empty) c
+ qpvuntsm/2 48ded843 (divergent) (empty) a
@origin (behind by 1 commits): qpvuntsm/2 48ded843 (divergent) (empty) a
[EOF]
");
// origin2 differs but is not in conflict
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
main (conflicted):
+ qpvuntsm/0 467e027c (divergent) (empty) c
+ qpvuntsm/2 48ded843 (divergent) (empty) a
@origin (behind by 1 commits): qpvuntsm/2 48ded843 (divergent) (empty) a
@origin2 (ahead by 1 commits, behind by 2 commits): qpvuntsm/1 579e0acd (hidden) (empty) b
@origin3 (behind by 1 commits): qpvuntsm/0 467e027c (divergent) (empty) c
[EOF]
");
// retracking origin2 adds to the conflict
work_dir
.run_jj(["bookmark", "untrack", "main", "--remote=origin2"])
.success();
let output = work_dir.run_jj(["bookmark", "track", "main", "--remote=origin2"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Started tracking 1 remote bookmarks.
main (conflicted):
+ qpvuntsm/0 467e027c (divergent) (empty) c
+ qpvuntsm/2 48ded843 (divergent) (empty) a
+ qpvuntsm/1 579e0acd (divergent) (empty) b
@origin2 (behind by 2 commits): qpvuntsm/1 579e0acd (divergent) (empty) b
[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("remotes.origin.auto-track-bookmarks = '~*'");
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/untrack new bookmark that doesn't exist at remote
work_dir.run_jj(["bookmark", "create", "main"]).success();
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "main"]), @r"
------- stderr -------
Started tracking 1 remote bookmarks.
[EOF]
");
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "untrack", "main"]), @r"
------- stderr -------
Stopped tracking 1 remote bookmarks.
[EOF]
");
insta::assert_snapshot!(
work_dir.run_jj(["bookmark", "untrack", "main", "--remote=o*"]), @r"
------- stderr -------
Warning: Remote bookmark not tracked yet: main@origin
Nothing changed.
[EOF]
");
// Track/untrack unknown bookmark
insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "maine*"]), @r"
------- stderr -------
Nothing changed.
[EOF]
");
insta::assert_snapshot!(
work_dir.run_jj(["bookmark", "untrack", "maine", "--remote=o* | unknown"]), @r"
------- stderr -------
Warning: No matching bookmarks for names: maine
Warning: No matching remotes for names: unknown
Nothing changed.
[EOF]
");
// Track already tracked bookmark
work_dir.run_jj(["bookmark", "track", "feature1"]).success();
let output = work_dir.run_jj(["bookmark", "track", "feature1"]);
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"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Remote bookmark not tracked yet: feature2@origin
Nothing changed.
[EOF]
");
// Track/untrack Git-tracking bookmark
work_dir.run_jj(["git", "export"]).success();
let output = work_dir.run_jj(["bookmark", "track", "main", "--remote=git"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Remote bookmark already tracked: main@git
Nothing changed.
[EOF]
");
let output = work_dir.run_jj(["bookmark", "untrack", "main", "--remote=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]
");
// Git-tracking remote should not be warned by default
let output = work_dir.run_jj(["bookmark", "track", "main"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Started tracking 1 remote bookmarks.
[EOF]
");
// Untrack by pattern
let output = work_dir.run_jj(["bookmark", "untrack", "~main", "--remote=*"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Git-tracking bookmark cannot be untracked: feature1@git
Warning: Remote bookmark not tracked yet: feature2@origin
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)
@origin (not created yet)
[EOF]
");
// Track by pattern
let output = work_dir.run_jj(["bookmark", "track", "'feature?' | main", "--remote=~git"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Remote bookmark already tracked: main@origin
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)
@origin (not created yet)
[EOF]
");
}
#[test]
fn test_bookmark_track_absent() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// Set up remotes
let git_repo1 = {
let git_repo_path = test_env.env_root().join("remote1");
let git_repo = git::init(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "remote1", "../remote1"])
.success();
git_repo
};
let _git_repo2 = {
let git_repo_path = test_env.env_root().join("remote2");
let git_repo = git::init(git_repo_path);
work_dir
.run_jj(["git", "remote", "add", "remote2", "../remote2"])
.success();
git_repo
};
// Create feature1@remote1
create_commit_with_refs(&git_repo1, "commit", b"", &["refs/heads/feature1"]);
let output = work_dir.run_jj(["git", "fetch", "--all-remotes"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
bookmark: feature1@remote1 [new] untracked
[EOF]
");
// Track feature1: remote2 isn't tracked because there's no local bookmark
insta::assert_snapshot!(
work_dir.run_jj(["bookmark", "track", "feature1", "--remote=*"]), @r"
------- stderr -------
Started tracking 1 remote bookmarks.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: quutswnw 3fb14832 commit
@remote1: quutswnw 3fb14832 commit
[EOF]
");
// Track feature1 again: remote2 is now tracked
insta::assert_snapshot!(
work_dir.run_jj(["bookmark", "track", "feature1", "--remote=*"]), @r"
------- stderr -------
Warning: Remote bookmark already tracked: feature1@remote1
Started tracking 1 remote bookmarks.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: quutswnw 3fb14832 commit
@remote1: quutswnw 3fb14832 commit
@remote2 (not created yet)
[EOF]
");
// Track newly-created bookmark: both remotes are tracked
work_dir
.run_jj(["bookmark", "create", "new-feature"])
.success();
insta::assert_snapshot!(
work_dir.run_jj(["bookmark", "track", "new-feature", "--remote=*"]), @r"
------- stderr -------
Started tracking 2 remote bookmarks.
[EOF]
");
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
feature1: quutswnw 3fb14832 commit
@remote1: quutswnw 3fb14832 commit
@remote2 (not created yet)
new-feature: qpvuntsm e8849ae1 (empty) (no description set)
@remote1 (not created yet)
@remote2 (not created yet)
[EOF]
");
}
#[test]
fn test_bookmark_list() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
// 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", 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([
"--config=remotes.origin.auto-track-bookmarks='~*'",
"bookmark",
"create",
"local-only",
"absent-tracked",
])
.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", "track", "absent-tracked"])
.success();
local_dir
.run_jj(["bookmark", "untrack", "remote-untrack"])
.success();
local_dir
.run_jj(["bookmark", "set", "--allow-backwards", "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"
absent-tracked: wqnwkozp 0353dd35 (empty) local-only
@origin (not created yet)
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"
absent-tracked: wqnwkozp 0353dd35 (empty) local-only
@origin (not created yet)
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 output = local_dir.run_jj(["bookmark", "list", "--all-remotes", "--color=always"]);
insta::assert_snapshot!(output, @r"
absent-tracked: wqnwkozp 0353dd35 (empty) local-only
@origin (not created yet)
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"
[absent-tracked]
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>
[absent-tracked@origin]
present: false
conflict: false
normal_target: <Error: No Commit available>
removed_targets:
added_targets:
tracked: true
tracking_present: true
tracking_ahead_count: 0
tracking_behind_count: 2
[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]
");
let output = local_dir.run_jj(["bookmark", "list", r#"-Tjson(self) ++ "\n""#]);
insta::assert_snapshot!(output, @r#"
{"name":"absent-tracked","target":["0353dd35c56156971ce5f023a1db7a6196160a8a"]}
{"name":"absent-tracked","remote":"origin","target":[null],"tracking_target":["0353dd35c56156971ce5f023a1db7a6196160a8a"]}
{"name":"local-only","target":["0353dd35c56156971ce5f023a1db7a6196160a8a"]}
{"name":"remote-delete","target":[null]}
{"name":"remote-delete","remote":"origin","target":["b32031cf329fbb90d042635c295b4e3fa2ca2651"],"tracking_target":[null]}
{"name":"remote-sync","target":["7a07dbeef135886b7ba7adb27d05190c39cd92ab"]}
{"name":"remote-unsync","target":["0353dd35c56156971ce5f023a1db7a6196160a8a"]}
{"name":"remote-unsync","remote":"origin","target":["553203baa52803406124962dbc0bcdc0227b20b2"],"tracking_target":["0353dd35c56156971ce5f023a1db7a6196160a8a"]}
[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("remotes.origin.auto-track-bookmarks = '*'");
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", 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([
"--config=remotes.origin.auto-track-bookmarks='~*'",
"bookmark",
"create",
"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/1 331d500d (hidden) (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/1 331d500d (hidden) (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/1 331d500d (hidden) (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/1 331d500d (hidden) (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/1 331d500d (hidden) (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/1 331d500d (hidden) (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/1 331d500d (hidden) (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/1 331d500d (hidden) (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", "'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/1 331d500d (hidden) (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(&["*-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 exact name pattern should be warned. "remote-delete" exists in
// remote. "remote-rewrite" exists, but isn't included in the match.
insta::assert_snapshot!(
query(&["local-keep", "push-*", "unknown | remote-delete ~ remote-rewrite"]), @r"
local-keep: kpqxywon 4b2bc95c (empty) local-keep
remote-delete (deleted)
@origin: zsuskuln 0e6b7968 (empty) remote-delete
[EOF]
------- stderr -------
Warning: No matching bookmarks for names: unknown
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]
");
// 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/1 331d500d (hidden) (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]
");
// Syntax error in name pattern
insta::assert_snapshot!(query(&["foo &"]), @r"
------- stderr -------
Error: Failed to parse name pattern: Syntax error
Caused by: --> 1:6
|
1 | foo &
| ^---
|
= expected `::`, `..`, `~`, or <primary>
Hint: See https://docs.jj-vcs.dev/latest/revsets/ or use `jj help -k revsets` for revsets syntax and how to quote symbols.
[EOF]
[exit status: 1]
");
}
#[test]
fn test_bookmark_list_quoted_name() {
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@", "'with space'"])
.success();
// quoted by default
let output = work_dir.run_jj(["bookmark", "list"]);
insta::assert_snapshot!(output, @r#"
"with space": qpvuntsm e8849ae1 (empty) (no description set)
[EOF]
"#);
// string method should apply to the original (unquoted) name
let template = r#"
separate(' ',
self,
name.contains('"'),
name.len(),
) ++ "\n"
"#;
let output = work_dir.run_jj(["bookmark", "list", "-T", template]);
insta::assert_snapshot!(output, @r#"
"with space" false 10
[EOF]
"#);
}
#[test]
fn test_bookmark_list_much_remote_divergence() {
let test_env = TestEnvironment::default();
test_env.add_config("remotes.origin.auto-track-bookmarks = '*'");
// 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([
"--config=remotes.origin.auto-track-bookmarks='~*'",
"bookmark",
"create",
"local-only",
])
.success();
// Mutate refs in local repository
local_dir
.run_jj(["bookmark", "set", "--allow-backwards", "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("remotes.origin.auto-track-bookmarks = '*'");
test_env.add_config("remotes.upstream.auto-track-bookmarks = '*'");
// 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", 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", "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([
"--config=remotes.origin.auto-track-bookmarks='~*'",
"--config=remotes.upstream.auto-track-bookmarks='~*'",
"bookmark",
"create",
"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", "--remote=origin"])
.success();
local_dir
.run_jj([
"git",
"push",
"--allow-new",
"--remote",
"upstream",
"--bookmark",
"remote-unsync",
])
.success();
local_dir
.run_jj(["bookmark", "set", "--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", "--remote=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", "bar"]).success();
work_dir
.run_jj(["bookmark", "create", "foo", "-rsubject(a)"])
.success();
work_dir
.run_jj(["bookmark", "create", "foo", "-rsubject(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]
------- stderr -------
Hint: Some bookmarks have conflicts. Use `jj bookmark set <name> -r <rev>` to resolve.
[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", 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", 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", 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_create_and_set_auto_track_bookmarks() {
let test_env = TestEnvironment::default();
let root_dir = test_env.work_dir("");
root_dir
.run_jj(["git", "init", "--colocate", "origin"])
.success();
test_env.add_config(
"
[remotes.origin]
auto-track-bookmarks = 'mine/*'
[remotes.fork]
auto-track-bookmarks = 'mine/* | not-mine/*'
",
);
root_dir.run_jj(["git", "init", "repo"]).success();
let repo_dir = test_env.work_dir("repo");
repo_dir
.run_jj(["git", "remote", "add", "origin", "../origin/.git"])
.success();
repo_dir
.run_jj(["git", "remote", "add", "fork", "dummy"])
.success();
// jj bookmark create obeys remotes.<name>.auto-track-bookmarks
repo_dir
.run_jj(["bookmark", "create", "mine/create", "not-mine/create"])
.success();
let output = repo_dir.run_jj([
"bookmark",
"list",
"--all",
"mine/create",
"not-mine/create",
]);
insta::assert_snapshot!(output, @r"
mine/create: rlvkpnrz 7eb1c95e (empty) (no description set)
@fork (not created yet)
@origin (not created yet)
not-mine/create: rlvkpnrz 7eb1c95e (empty) (no description set)
@fork (not created yet)
[EOF]
");
repo_dir.run_jj(["commit", "--message", "create"]).success();
// jj bookmark set obeys remotes.<name>.auto-track-bookmarks
repo_dir
.run_jj(["bookmark", "set", "mine/set", "not-mine/set"])
.success();
let output = repo_dir.run_jj(["bookmark", "list", "--all", "mine/set", "not-mine/set"]);
insta::assert_snapshot!(output, @r"
mine/set: yqosqzyt 5fbe2b20 (empty) (no description set)
@fork (not created yet)
@origin (not created yet)
not-mine/set: yqosqzyt 5fbe2b20 (empty) (no description set)
@fork (not created yet)
[EOF]
");
repo_dir.run_jj(["commit", "--message", "set"]).success();
// jj bookmark create/set warns when auto-tracking existing bookmark
repo_dir.run_jj(["git", "push"]).success();
repo_dir
.run_jj(["bookmark", "forget", "mine/create", "mine/set"])
.success();
let output = repo_dir.run_jj(["bookmark", "create", "mine/create"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision is empty.
Warning: Auto-tracking bookmark that exists on the remote: mine/create@origin
Created 1 bookmarks pointing to znkkpsqq 2e899fb8 mine/create* | (empty) (no description set)
[EOF]
");
let output = repo_dir.run_jj(["bookmark", "set", "mine/set"]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Warning: Target revision is empty.
Warning: Auto-tracking bookmark that exists on the remote: mine/set@origin
Created 1 bookmarks pointing to znkkpsqq 2e899fb8 mine/create* mine/set* | (empty) (no description set)
[EOF]
");
}
#[test]
fn test_bad_auto_track_bookmarks() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
// silence "Target revision is empty" warning
work_dir.write_file("file", "");
let output = work_dir.run_jj([
"bookmark",
"create",
"a",
"--config=remotes.origin.auto-track-bookmarks=''",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Config error: Invalid `remotes.origin.auto-track-bookmarks`: Syntax error
Caused by: --> 1:1
|
1 |
| ^---
|
= expected <expression>
Hint: See https://docs.jj-vcs.dev/latest/revsets/ or use `jj help -k revsets` for revsets syntax and how to quote symbols.
For help, see https://docs.jj-vcs.dev/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj([
"bookmark",
"create",
"a",
"--config=remotes.origin.auto-track-bookmarks='foo &'",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Config error: Invalid `remotes.origin.auto-track-bookmarks`: Syntax error
Caused by: --> 1:6
|
1 | foo &
| ^---
|
= expected `::`, `..`, `~`, or <primary>
Hint: See https://docs.jj-vcs.dev/latest/revsets/ or use `jj help -k revsets` for revsets syntax and how to quote symbols.
For help, see https://docs.jj-vcs.dev/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
let output = work_dir.run_jj([
"bookmark",
"create",
"a",
"--config=remotes.origin.auto-track-bookmarks=[{}]",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Config error: Invalid type or value for remotes.origin
Caused by: invalid type: sequence, expected a string
in `auto-track-bookmarks`
For help, see https://docs.jj-vcs.dev/latest/config/ or use `jj help -k config`.
[EOF]
[exit status: 1]
");
}
#[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"])
}