just playing with tangled
1// Copyright 2022 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use testutils::git;
16
17use crate::common::CommandOutput;
18use crate::common::TestEnvironment;
19use crate::common::TestWorkDir;
20
21fn create_commit_with_refs(
22 repo: &gix::Repository,
23 message: &str,
24 content: &[u8],
25 ref_names: &[&str],
26) {
27 let git::CommitResult {
28 tree_id: _,
29 commit_id,
30 } = git::add_commit(repo, "refs/heads/dummy", "file", content, message, &[]);
31 repo.find_reference("dummy").unwrap().delete().unwrap();
32
33 for name in ref_names {
34 repo.reference(
35 *name,
36 commit_id,
37 gix::refs::transaction::PreviousValue::Any,
38 "log message",
39 )
40 .unwrap();
41 }
42}
43
44#[test]
45fn test_bookmark_multiple_names() {
46 let test_env = TestEnvironment::default();
47 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
48 let work_dir = test_env.work_dir("repo");
49
50 let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo", "bar"]);
51 insta::assert_snapshot!(output, @r"
52 ------- stderr -------
53 Created 2 bookmarks pointing to qpvuntsm 230dd059 bar foo | (empty) (no description set)
54 [EOF]
55 ");
56 insta::assert_snapshot!(get_log_output(&work_dir), @r"
57 @ bar foo 230dd059e1b0
58 ◆ 000000000000
59 [EOF]
60 ");
61
62 work_dir.run_jj(["new"]).success();
63 let output = work_dir.run_jj(["bookmark", "set", "foo", "bar", "--to=@"]);
64 insta::assert_snapshot!(output, @r"
65 ------- stderr -------
66 Moved 2 bookmarks to zsuskuln 8bb159bc bar foo | (empty) (no description set)
67 [EOF]
68 ");
69 insta::assert_snapshot!(get_log_output(&work_dir), @r"
70 @ bar foo 8bb159bc30a9
71 ○ 230dd059e1b0
72 ◆ 000000000000
73 [EOF]
74 ");
75
76 let output = work_dir.run_jj(["bookmark", "delete", "foo", "bar", "foo"]);
77 insta::assert_snapshot!(output, @r"
78 ------- stderr -------
79 Deleted 2 bookmarks.
80 [EOF]
81 ");
82 insta::assert_snapshot!(get_log_output(&work_dir), @r"
83 @ 8bb159bc30a9
84 ○ 230dd059e1b0
85 ◆ 000000000000
86 [EOF]
87 ");
88
89 // Hint should be omitted if -r is specified
90 let output = work_dir.run_jj(["bookmark", "create", "-r@-", "foo", "bar"]);
91 insta::assert_snapshot!(output, @r"
92 ------- stderr -------
93 Created 2 bookmarks pointing to qpvuntsm 230dd059 bar foo | (empty) (no description set)
94 [EOF]
95 ");
96
97 // Create and move with explicit -r
98 let output = work_dir.run_jj(["bookmark", "set", "-r@", "bar", "baz"]);
99 insta::assert_snapshot!(output, @r"
100 ------- stderr -------
101 Created 1 bookmarks pointing to zsuskuln 8bb159bc bar baz | (empty) (no description set)
102 Moved 1 bookmarks to zsuskuln 8bb159bc bar baz | (empty) (no description set)
103 [EOF]
104 ");
105
106 // Noop changes should not be included in the stats
107 let output = work_dir.run_jj(["bookmark", "set", "-r@", "foo", "bar", "baz"]);
108 insta::assert_snapshot!(output, @r"
109 ------- stderr -------
110 Moved 1 bookmarks to zsuskuln 8bb159bc bar baz foo | (empty) (no description set)
111 [EOF]
112 ");
113}
114
115#[test]
116fn test_bookmark_at_root() {
117 let test_env = TestEnvironment::default();
118 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
119 let work_dir = test_env.work_dir("repo");
120
121 let output = work_dir.run_jj(["bookmark", "create", "fred", "-r=root()"]);
122 insta::assert_snapshot!(output, @r"
123 ------- stderr -------
124 Created 1 bookmarks pointing to zzzzzzzz 00000000 fred | (empty) (no description set)
125 [EOF]
126 ");
127 let output = work_dir.run_jj(["git", "export"]);
128 insta::assert_snapshot!(output, @r"
129 ------- stderr -------
130 Nothing changed.
131 Warning: Failed to export some bookmarks:
132 fred@git: Ref cannot point to the root commit in Git
133 [EOF]
134 ");
135}
136
137#[test]
138fn test_bookmark_bad_name() {
139 let test_env = TestEnvironment::default();
140 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
141 let work_dir = test_env.work_dir("repo");
142
143 let output = work_dir.run_jj(["bookmark", "create", "-r@", ""]);
144 insta::assert_snapshot!(output, @r"
145 ------- stderr -------
146 error: invalid value '' for '<NAMES>...': Failed to parse bookmark name: Syntax error
147
148 For more information, try '--help'.
149 Caused by: --> 1:1
150 |
151 1 |
152 | ^---
153 |
154 = expected <identifier>, <string_literal>, or <raw_string_literal>
155 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
156 [EOF]
157 [exit status: 2]
158 ");
159
160 let output = work_dir.run_jj(["bookmark", "set", "''"]);
161 insta::assert_snapshot!(output, @r"
162 ------- stderr -------
163 error: invalid value '''' for '<NAMES>...': Failed to parse bookmark name: Expected non-empty string
164
165 For more information, try '--help'.
166 Caused by: --> 1:1
167 |
168 1 | ''
169 | ^^
170 |
171 = Expected non-empty string
172 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
173 [EOF]
174 [exit status: 2]
175 ");
176
177 let output = work_dir.run_jj(["bookmark", "rename", "x", ""]);
178 insta::assert_snapshot!(output, @r"
179 ------- stderr -------
180 error: invalid value '' for '<NEW>': Failed to parse bookmark name: Syntax error
181
182 For more information, try '--help'.
183 Caused by: --> 1:1
184 |
185 1 |
186 | ^---
187 |
188 = expected <identifier>, <string_literal>, or <raw_string_literal>
189 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
190 [EOF]
191 [exit status: 2]
192 ");
193
194 // common errors
195 let output = work_dir.run_jj(["bookmark", "set", "@-", "foo"]);
196 insta::assert_snapshot!(output, @r"
197 ------- stderr -------
198 error: invalid value '@-' for '<NAMES>...': Failed to parse bookmark name: Syntax error
199
200 For more information, try '--help'.
201 Caused by: --> 1:1
202 |
203 1 | @-
204 | ^---
205 |
206 = expected <identifier>, <string_literal>, or <raw_string_literal>
207 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for how to quote symbols.
208 [EOF]
209 [exit status: 2]
210 ");
211
212 let stderr = work_dir.run_jj(["bookmark", "set", "-r@-", "foo@bar"]);
213 insta::assert_snapshot!(stderr, @r"
214 ------- stderr -------
215 error: invalid value 'foo@bar' for '<NAMES>...': Failed to parse bookmark name: Syntax error
216
217 For more information, try '--help'.
218 Caused by: --> 1:4
219 |
220 1 | foo@bar
221 | ^---
222 |
223 = expected <EOI>
224 Hint: Looks like remote bookmark. Run `jj bookmark track foo@bar` to track it.
225 [EOF]
226 [exit status: 2]
227 ");
228
229 // quoted name works
230 let output = work_dir.run_jj(["bookmark", "create", "-r@", "'foo@bar'"]);
231 insta::assert_snapshot!(output, @r"
232 ------- stderr -------
233 Created 1 bookmarks pointing to qpvuntsm 230dd059 foo@bar | (empty) (no description set)
234 [EOF]
235 ");
236}
237
238#[test]
239fn test_bookmark_move() {
240 let test_env = TestEnvironment::default();
241 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
242 let work_dir = test_env.work_dir("repo");
243
244 // Set up remote
245 let git_repo_path = test_env.env_root().join("git-repo");
246 git::init_bare(git_repo_path);
247 work_dir
248 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
249 .success();
250
251 let output = work_dir.run_jj(["bookmark", "move", "foo", "--to=@"]);
252 insta::assert_snapshot!(output, @r"
253 ------- stderr -------
254 Error: No such bookmark: foo
255 [EOF]
256 [exit status: 1]
257 ");
258
259 let output = work_dir.run_jj(["bookmark", "set", "foo", "--to=@"]);
260 insta::assert_snapshot!(output, @r"
261 ------- stderr -------
262 Created 1 bookmarks pointing to qpvuntsm 230dd059 foo | (empty) (no description set)
263 [EOF]
264 ");
265
266 work_dir.run_jj(["new"]).success();
267 let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo"]);
268 insta::assert_snapshot!(output, @r"
269 ------- stderr -------
270 Error: Bookmark already exists: foo
271 Hint: Use `jj bookmark set` to update it.
272 [EOF]
273 [exit status: 1]
274 ");
275
276 let output = work_dir.run_jj(["bookmark", "set", "foo", "--revision", "@"]);
277 insta::assert_snapshot!(output, @r"
278 ------- stderr -------
279 Moved 1 bookmarks to mzvwutvl 167f90e7 foo | (empty) (no description set)
280 [EOF]
281 ");
282
283 let output = work_dir.run_jj(["bookmark", "set", "-r@-", "foo"]);
284 insta::assert_snapshot!(output, @r"
285 ------- stderr -------
286 Error: Refusing to move bookmark backwards or sideways: foo
287 Hint: Use --allow-backwards to allow it.
288 [EOF]
289 [exit status: 1]
290 ");
291
292 let output = work_dir.run_jj(["bookmark", "set", "-r@-", "--allow-backwards", "foo"]);
293 insta::assert_snapshot!(output, @r"
294 ------- stderr -------
295 Moved 1 bookmarks to qpvuntsm 230dd059 foo | (empty) (no description set)
296 [EOF]
297 ");
298
299 let output = work_dir.run_jj(["bookmark", "move", "foo", "--to=@"]);
300 insta::assert_snapshot!(output, @r"
301 ------- stderr -------
302 Moved 1 bookmarks to mzvwutvl 167f90e7 foo | (empty) (no description set)
303 [EOF]
304 ");
305
306 let output = work_dir.run_jj(["bookmark", "move", "--to=@-", "foo"]);
307 insta::assert_snapshot!(output, @r"
308 ------- stderr -------
309 Error: Refusing to move bookmark backwards or sideways: foo
310 Hint: Use --allow-backwards to allow it.
311 [EOF]
312 [exit status: 1]
313 ");
314
315 let output = work_dir.run_jj(["bookmark", "move", "--to=@-", "--allow-backwards", "foo"]);
316 insta::assert_snapshot!(output, @r"
317 ------- stderr -------
318 Moved 1 bookmarks to qpvuntsm 230dd059 foo | (empty) (no description set)
319 [EOF]
320 ");
321
322 // Delete bookmark locally, but is still tracking remote
323 work_dir.run_jj(["describe", "@-", "-mcommit"]).success();
324 work_dir
325 .run_jj(["git", "push", "--allow-new", "-r@-"])
326 .success();
327 work_dir.run_jj(["bookmark", "delete", "foo"]).success();
328 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
329 foo (deleted)
330 @origin: qpvuntsm 1eb845f3 (empty) commit
331 [EOF]
332 ");
333
334 // Deleted tracking bookmark name should still be allocated
335 let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo"]);
336 insta::assert_snapshot!(output, @r"
337 ------- stderr -------
338 Error: Tracked remote bookmarks exist for deleted bookmark: foo
339 Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack 'glob:foo@*'` to disassociate them.
340 [EOF]
341 [exit status: 1]
342 ");
343
344 // Restoring local target shouldn't invalidate tracking state
345 let output = work_dir.run_jj(["bookmark", "set", "foo", "--to=@"]);
346 insta::assert_snapshot!(output, @r"
347 ------- stderr -------
348 Moved 1 bookmarks to mzvwutvl 66d48752 foo* | (empty) (no description set)
349 [EOF]
350 ");
351 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
352 foo: mzvwutvl 66d48752 (empty) (no description set)
353 @origin (behind by 1 commits): qpvuntsm 1eb845f3 (empty) commit
354 [EOF]
355 ");
356
357 // Untracked remote bookmark shouldn't block creation of local bookmark
358 work_dir
359 .run_jj(["bookmark", "untrack", "foo@origin"])
360 .success();
361 work_dir.run_jj(["bookmark", "delete", "foo"]).success();
362 let output = work_dir.run_jj(["bookmark", "create", "-r@", "foo"]);
363 insta::assert_snapshot!(output, @r"
364 ------- stderr -------
365 Created 1 bookmarks pointing to mzvwutvl 66d48752 foo | (empty) (no description set)
366 [EOF]
367 ");
368 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
369 foo: mzvwutvl 66d48752 (empty) (no description set)
370 foo@origin: qpvuntsm 1eb845f3 (empty) commit
371 [EOF]
372 ");
373}
374
375#[test]
376fn test_bookmark_move_matching() {
377 let test_env = TestEnvironment::default();
378 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
379 let work_dir = test_env.work_dir("repo");
380
381 work_dir
382 .run_jj(["bookmark", "create", "-r@", "a1", "a2"])
383 .success();
384 work_dir.run_jj(["new", "-mhead1"]).success();
385 work_dir.run_jj(["new", "root()"]).success();
386 work_dir
387 .run_jj(["bookmark", "create", "-r@", "b1"])
388 .success();
389 work_dir.run_jj(["new"]).success();
390 work_dir
391 .run_jj(["bookmark", "create", "-r@", "c1"])
392 .success();
393 work_dir.run_jj(["new", "-mhead2"]).success();
394 insta::assert_snapshot!(get_log_output(&work_dir), @r"
395 @ a2781dd9ee37
396 ○ c1 f4f38657a3dd
397 ○ b1 f652c32197cf
398 │ ○ 6b5e840ea72b
399 │ ○ a1 a2 230dd059e1b0
400 ├─╯
401 ◆ 000000000000
402 [EOF]
403 ");
404
405 // The default could be considered "--from=all() glob:*", but is disabled
406 let output = work_dir.run_jj(["bookmark", "move", "--to=@"]);
407 insta::assert_snapshot!(output, @r"
408 ------- stderr -------
409 error: the following required arguments were not provided:
410 <--from <REVSETS>|NAMES>
411
412 Usage: jj bookmark move --to <REVSET> <--from <REVSETS>|NAMES>
413
414 For more information, try '--help'.
415 [EOF]
416 [exit status: 2]
417 ");
418
419 // No bookmarks pointing to the source revisions
420 let output = work_dir.run_jj(["bookmark", "move", "--from=none()", "--to=@"]);
421 insta::assert_snapshot!(output, @r"
422 ------- stderr -------
423 No bookmarks to update.
424 [EOF]
425 ");
426
427 // No matching bookmarks within the source revisions
428 let output = work_dir.run_jj(["bookmark", "move", "--from=::@", "glob:a?", "--to=@"]);
429 insta::assert_snapshot!(output, @r"
430 ------- stderr -------
431 Error: No matching bookmarks for patterns: a?
432 [EOF]
433 [exit status: 1]
434 ");
435
436 // Noop move
437 let output = work_dir.run_jj(["bookmark", "move", "--to=a1", "a2"]);
438 insta::assert_snapshot!(output, @r"
439 ------- stderr -------
440 No bookmarks to update.
441 [EOF]
442 ");
443
444 // Move from multiple revisions
445 let output = work_dir.run_jj(["bookmark", "move", "--from=::@", "--to=@"]);
446 insta::assert_snapshot!(output, @r"
447 ------- stderr -------
448 Moved 2 bookmarks to vruxwmqv a2781dd9 b1 c1 | (empty) head2
449 Hint: Specify bookmark by name to update just one of the bookmarks.
450 [EOF]
451 ");
452 insta::assert_snapshot!(get_log_output(&work_dir), @r"
453 @ b1 c1 a2781dd9ee37
454 ○ f4f38657a3dd
455 ○ f652c32197cf
456 │ ○ 6b5e840ea72b
457 │ ○ a1 a2 230dd059e1b0
458 ├─╯
459 ◆ 000000000000
460 [EOF]
461 ");
462 work_dir.run_jj(["undo"]).success();
463
464 // Try to move multiple bookmarks, but one of them isn't fast-forward
465 let output = work_dir.run_jj(["bookmark", "move", "glob:?1", "--to=@"]);
466 insta::assert_snapshot!(output, @r"
467 ------- stderr -------
468 Error: Refusing to move bookmark backwards or sideways: a1
469 Hint: Use --allow-backwards to allow it.
470 [EOF]
471 [exit status: 1]
472 ");
473 insta::assert_snapshot!(get_log_output(&work_dir), @r"
474 @ a2781dd9ee37
475 ○ c1 f4f38657a3dd
476 ○ b1 f652c32197cf
477 │ ○ 6b5e840ea72b
478 │ ○ a1 a2 230dd059e1b0
479 ├─╯
480 ◆ 000000000000
481 [EOF]
482 ");
483
484 // Select by revision and name
485 let output = work_dir.run_jj(["bookmark", "move", "--from=::a1+", "--to=a1+", "glob:?1"]);
486 insta::assert_snapshot!(output, @r"
487 ------- stderr -------
488 Moved 1 bookmarks to kkmpptxz 6b5e840e a1 | (empty) head1
489 [EOF]
490 ");
491 insta::assert_snapshot!(get_log_output(&work_dir), @r"
492 @ a2781dd9ee37
493 ○ c1 f4f38657a3dd
494 ○ b1 f652c32197cf
495 │ ○ a1 6b5e840ea72b
496 │ ○ a2 230dd059e1b0
497 ├─╯
498 ◆ 000000000000
499 [EOF]
500 ");
501}
502
503#[test]
504fn test_bookmark_move_conflicting() {
505 let test_env = TestEnvironment::default();
506 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
507 let work_dir = test_env.work_dir("repo");
508 let get_log = || {
509 let template = r#"separate(" ", description.first_line(), bookmarks)"#;
510 work_dir.run_jj(["log", "-T", template])
511 };
512
513 work_dir.run_jj(["new", "root()", "-mA0"]).success();
514 work_dir.run_jj(["new", "root()", "-mB0"]).success();
515 work_dir.run_jj(["new", "root()", "-mC0"]).success();
516 work_dir
517 .run_jj(["new", "description(A0)", "-mA1"])
518 .success();
519
520 // Set up conflicting bookmark.
521 work_dir
522 .run_jj(["bookmark", "create", "-rdescription(A0)", "foo"])
523 .success();
524 work_dir
525 .run_jj([
526 "bookmark",
527 "create",
528 "--at-op=@-",
529 "-rdescription(B0)",
530 "foo",
531 ])
532 .success();
533 insta::assert_snapshot!(get_log(), @r"
534 @ A1
535 ○ A0 foo??
536 │ ○ C0
537 ├─╯
538 │ ○ B0 foo??
539 ├─╯
540 ◆
541 [EOF]
542 ------- stderr -------
543 Concurrent modification detected, resolving automatically.
544 [EOF]
545 ");
546
547 // Can't move the bookmark to C0 since it's sibling.
548 let output = work_dir.run_jj(["bookmark", "set", "-rdescription(C0)", "foo"]);
549 insta::assert_snapshot!(output, @r"
550 ------- stderr -------
551 Error: Refusing to move bookmark backwards or sideways: foo
552 Hint: Use --allow-backwards to allow it.
553 [EOF]
554 [exit status: 1]
555 ");
556
557 // Can move the bookmark to A1 since it's descendant of A0. It's not
558 // descendant of B0, though.
559 let output = work_dir.run_jj(["bookmark", "set", "-rdescription(A1)", "foo"]);
560 insta::assert_snapshot!(output, @r"
561 ------- stderr -------
562 Moved 1 bookmarks to mzvwutvl 9328d344 foo | (empty) A1
563 [EOF]
564 ");
565 insta::assert_snapshot!(get_log(), @r"
566 @ A1 foo
567 ○ A0
568 │ ○ C0
569 ├─╯
570 │ ○ B0
571 ├─╯
572 ◆
573 [EOF]
574 ");
575}
576
577#[test]
578fn test_bookmark_rename() {
579 let test_env = TestEnvironment::default();
580 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
581 let work_dir = test_env.work_dir("repo");
582
583 // Set up remote
584 let git_repo_path = test_env.env_root().join("git-repo");
585 git::init_bare(git_repo_path);
586 work_dir
587 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
588 .success();
589
590 let output = work_dir.run_jj(["bookmark", "rename", "bnoexist", "blocal"]);
591 insta::assert_snapshot!(output, @r"
592 ------- stderr -------
593 Error: No such bookmark: bnoexist
594 [EOF]
595 [exit status: 1]
596 ");
597
598 work_dir.run_jj(["describe", "-m=commit-0"]).success();
599 work_dir
600 .run_jj(["bookmark", "create", "-r@", "blocal"])
601 .success();
602 let output = work_dir.run_jj(["bookmark", "rename", "blocal", "blocal1"]);
603 insta::assert_snapshot!(output, @"");
604
605 work_dir.run_jj(["new"]).success();
606 work_dir.run_jj(["describe", "-m=commit-1"]).success();
607 work_dir
608 .run_jj(["bookmark", "create", "-r@", "bexist"])
609 .success();
610 let output = work_dir.run_jj(["bookmark", "rename", "blocal1", "bexist"]);
611 insta::assert_snapshot!(output, @r"
612 ------- stderr -------
613 Error: Bookmark already exists: bexist
614 [EOF]
615 [exit status: 1]
616 ");
617
618 work_dir.run_jj(["new"]).success();
619 work_dir.run_jj(["describe", "-m=commit-2"]).success();
620 work_dir
621 .run_jj(["bookmark", "create", "-r@", "bremote"])
622 .success();
623 work_dir
624 .run_jj(["git", "push", "--allow-new", "-b=bremote"])
625 .success();
626 let output = work_dir.run_jj(["bookmark", "rename", "bremote", "bremote2"]);
627 insta::assert_snapshot!(output, @r"
628 ------- stderr -------
629 Warning: Tracked remote bookmarks for bookmark bremote were not renamed.
630 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.
631 [EOF]
632 ");
633 let output = work_dir.run_jj(["bookmark", "rename", "bremote2", "bremote"]);
634 insta::assert_snapshot!(output, @r"
635 ------- stderr -------
636 Warning: Tracked remote bookmarks for bookmark bremote exist.
637 Hint: Run `jj bookmark untrack 'glob:bremote@*'` to disassociate them.
638 [EOF]
639 ");
640}
641
642#[test]
643fn test_bookmark_rename_colocated() {
644 let test_env = TestEnvironment::default();
645 test_env
646 .run_jj_in(".", ["git", "init", "repo", "--colocate"])
647 .success();
648 let work_dir = test_env.work_dir("repo");
649
650 work_dir.run_jj(["describe", "-m=commit-0"]).success();
651 work_dir
652 .run_jj(["bookmark", "create", "-r@", "blocal"])
653 .success();
654
655 // Make sure that git tracking bookmarks don't cause a warning
656 let output = work_dir.run_jj(["bookmark", "rename", "blocal", "blocal1"]);
657 insta::assert_snapshot!(output, @"");
658}
659
660#[test]
661fn test_bookmark_forget_glob() {
662 let test_env = TestEnvironment::default();
663 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
664 let work_dir = test_env.work_dir("repo");
665
666 work_dir
667 .run_jj(["bookmark", "create", "-r@", "foo-1"])
668 .success();
669 work_dir
670 .run_jj(["bookmark", "create", "-r@", "bar-2"])
671 .success();
672 work_dir
673 .run_jj(["bookmark", "create", "-r@", "foo-3"])
674 .success();
675 work_dir
676 .run_jj(["bookmark", "create", "-r@", "foo-4"])
677 .success();
678
679 insta::assert_snapshot!(get_log_output(&work_dir), @r"
680 @ bar-2 foo-1 foo-3 foo-4 230dd059e1b0
681 ◆ 000000000000
682 [EOF]
683 ");
684 let output = work_dir.run_jj(["bookmark", "forget", "glob:foo-[1-3]"]);
685 insta::assert_snapshot!(output, @r"
686 ------- stderr -------
687 Forgot 2 local bookmarks.
688 [EOF]
689 ");
690 work_dir.run_jj(["undo"]).success();
691 let output = work_dir.run_jj(["bookmark", "forget", "glob:foo-[1-3]"]);
692 insta::assert_snapshot!(output, @r"
693 ------- stderr -------
694 Forgot 2 local bookmarks.
695 [EOF]
696 ");
697 insta::assert_snapshot!(get_log_output(&work_dir), @r"
698 @ bar-2 foo-4 230dd059e1b0
699 ◆ 000000000000
700 [EOF]
701 ");
702
703 // Forgetting a bookmark via both explicit name and glob pattern, or with
704 // multiple glob patterns, shouldn't produce an error.
705 let output = work_dir.run_jj(["bookmark", "forget", "foo-4", "glob:foo-*", "glob:foo-*"]);
706 insta::assert_snapshot!(output, @r"
707 ------- stderr -------
708 Forgot 1 local bookmarks.
709 [EOF]
710 ");
711 insta::assert_snapshot!(get_log_output(&work_dir), @r"
712 @ bar-2 230dd059e1b0
713 ◆ 000000000000
714 [EOF]
715 ");
716
717 // Malformed glob
718 let output = work_dir.run_jj(["bookmark", "forget", "glob:foo-[1-3"]);
719 insta::assert_snapshot!(output, @r"
720 ------- stderr -------
721 error: invalid value 'glob:foo-[1-3' for '<NAMES>...': Pattern syntax error near position 4: invalid range pattern
722
723 For more information, try '--help'.
724 [EOF]
725 [exit status: 2]
726 ");
727
728 // We get an error if none of the globs match anything
729 let output = work_dir.run_jj(["bookmark", "forget", "glob:bar*", "glob:baz*", "glob:boom*"]);
730 insta::assert_snapshot!(output, @r"
731 ------- stderr -------
732 Error: No matching bookmarks for patterns: baz*, boom*
733 [EOF]
734 [exit status: 1]
735 ");
736}
737
738#[test]
739fn test_bookmark_delete_glob() {
740 // Set up a git repo with a bookmark and a jj repo that has it as a remote.
741 let test_env = TestEnvironment::default();
742 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
743 let work_dir = test_env.work_dir("repo");
744 let git_repo_path = test_env.env_root().join("git-repo");
745 let git_repo = git::init_bare(git_repo_path);
746 let blob_oid = git_repo.write_blob(b"content").unwrap();
747 let mut tree_editor = git_repo
748 .edit_tree(gix::ObjectId::empty_tree(gix::hash::Kind::default()))
749 .unwrap();
750 tree_editor
751 .upsert("file", gix::object::tree::EntryKind::Blob, blob_oid)
752 .unwrap();
753 let _tree_id = tree_editor.write().unwrap();
754 work_dir
755 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
756 .success();
757
758 work_dir.run_jj(["describe", "-m=commit"]).success();
759 work_dir
760 .run_jj(["bookmark", "create", "-r@", "foo-1"])
761 .success();
762 work_dir
763 .run_jj(["bookmark", "create", "-r@", "bar-2"])
764 .success();
765 work_dir
766 .run_jj(["bookmark", "create", "-r@", "foo-3"])
767 .success();
768 work_dir
769 .run_jj(["bookmark", "create", "-r@", "foo-4"])
770 .success();
771 // Push to create remote-tracking bookmarks
772 work_dir.run_jj(["git", "push", "--all"]).success();
773
774 insta::assert_snapshot!(get_log_output(&work_dir), @r"
775 @ bar-2 foo-1 foo-3 foo-4 312a98d6f27b
776 ◆ 000000000000
777 [EOF]
778 ");
779 let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3]"]);
780 insta::assert_snapshot!(output, @r"
781 ------- stderr -------
782 Deleted 2 bookmarks.
783 [EOF]
784 ");
785 work_dir.run_jj(["undo"]).success();
786 let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3]"]);
787 insta::assert_snapshot!(output, @r"
788 ------- stderr -------
789 Deleted 2 bookmarks.
790 [EOF]
791 ");
792 insta::assert_snapshot!(get_log_output(&work_dir), @r"
793 @ bar-2 foo-1@origin foo-3@origin foo-4 312a98d6f27b
794 ◆ 000000000000
795 [EOF]
796 ");
797
798 // We get an error if none of the globs match live bookmarks. Unlike `jj
799 // bookmark forget`, it's not allowed to delete already deleted bookmarks.
800 let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3]"]);
801 insta::assert_snapshot!(output, @r"
802 ------- stderr -------
803 Error: No matching bookmarks for patterns: foo-[1-3]
804 [EOF]
805 [exit status: 1]
806 ");
807
808 // Deleting a bookmark via both explicit name and glob pattern, or with
809 // multiple glob patterns, shouldn't produce an error.
810 let output = work_dir.run_jj(["bookmark", "delete", "foo-4", "glob:foo-*", "glob:foo-*"]);
811 insta::assert_snapshot!(output, @r"
812 ------- stderr -------
813 Deleted 1 bookmarks.
814 [EOF]
815 ");
816 insta::assert_snapshot!(get_log_output(&work_dir), @r"
817 @ bar-2 foo-1@origin foo-3@origin foo-4@origin 312a98d6f27b
818 ◆ 000000000000
819 [EOF]
820 ");
821
822 // The deleted bookmarks are still there
823 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
824 bar-2: qpvuntsm 312a98d6 (empty) commit
825 @origin: qpvuntsm 312a98d6 (empty) commit
826 foo-1 (deleted)
827 @origin: qpvuntsm 312a98d6 (empty) commit
828 foo-3 (deleted)
829 @origin: qpvuntsm 312a98d6 (empty) commit
830 foo-4 (deleted)
831 @origin: qpvuntsm 312a98d6 (empty) commit
832 [EOF]
833 ");
834
835 // Malformed glob
836 let output = work_dir.run_jj(["bookmark", "delete", "glob:foo-[1-3"]);
837 insta::assert_snapshot!(output, @r"
838 ------- stderr -------
839 error: invalid value 'glob:foo-[1-3' for '<NAMES>...': Pattern syntax error near position 4: invalid range pattern
840
841 For more information, try '--help'.
842 [EOF]
843 [exit status: 2]
844 ");
845
846 // Unknown pattern kind
847 let output = work_dir.run_jj(["bookmark", "forget", "whatever:bookmark"]);
848 insta::assert_snapshot!(output, @r"
849 ------- stderr -------
850 error: invalid value 'whatever:bookmark' for '<NAMES>...': Invalid string pattern kind `whatever:`
851
852 For more information, try '--help'.
853 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
854 [EOF]
855 [exit status: 2]
856 ");
857}
858
859#[test]
860fn test_bookmark_delete_export() {
861 let test_env = TestEnvironment::default();
862 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
863 let work_dir = test_env.work_dir("repo");
864
865 work_dir.run_jj(["new"]).success();
866 work_dir
867 .run_jj(["bookmark", "create", "-r@", "foo"])
868 .success();
869 work_dir.run_jj(["git", "export"]).success();
870
871 work_dir.run_jj(["bookmark", "delete", "foo"]).success();
872 let output = work_dir.run_jj(["bookmark", "list", "--all-remotes"]);
873 insta::assert_snapshot!(output, @r"
874 foo (deleted)
875 @git: rlvkpnrz 65b6b74e (empty) (no description set)
876 [EOF]
877 ------- stderr -------
878 Hint: Bookmarks marked as deleted will be deleted from the underlying Git repo on the next `jj git export`.
879 [EOF]
880 ");
881
882 work_dir.run_jj(["git", "export"]).success();
883 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
884}
885
886#[test]
887fn test_bookmark_forget_export() {
888 let test_env = TestEnvironment::default();
889 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
890 let work_dir = test_env.work_dir("repo");
891
892 work_dir.run_jj(["new"]).success();
893 work_dir
894 .run_jj(["bookmark", "create", "-r@", "foo"])
895 .success();
896 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
897 foo: rlvkpnrz 65b6b74e (empty) (no description set)
898 [EOF]
899 ");
900
901 // Exporting the bookmark to git creates a local-git tracking bookmark
902 let output = work_dir.run_jj(["git", "export"]);
903 insta::assert_snapshot!(output, @"");
904 let output = work_dir.run_jj(["bookmark", "forget", "--include-remotes", "foo"]);
905 insta::assert_snapshot!(output, @r"
906 ------- stderr -------
907 Forgot 1 local bookmarks.
908 Forgot 1 remote bookmarks.
909 [EOF]
910 ");
911 // Forgetting a bookmark with --include-remotes deletes local and
912 // remote-tracking bookmarks including the corresponding git-tracking bookmark.
913 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
914 let output = work_dir.run_jj(["log", "-r=foo", "--no-graph"]);
915 insta::assert_snapshot!(output, @r"
916 ------- stderr -------
917 Error: Revision `foo` doesn't exist
918 [EOF]
919 [exit status: 1]
920 ");
921
922 // `jj git export` will delete the bookmark from git. In a colocated repo,
923 // this will happen automatically immediately after a `jj bookmark forget`.
924 // This is demonstrated in `test_git_colocated_bookmark_forget` in
925 // test_git_colocated.rs
926 let output = work_dir.run_jj(["git", "export"]);
927 insta::assert_snapshot!(output, @"");
928 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
929}
930
931#[test]
932fn test_bookmark_forget_fetched_bookmark() {
933 // Much of this test is borrowed from `test_git_fetch_remote_only_bookmark` in
934 // test_git_fetch.rs
935
936 // Set up a git repo with a bookmark and a jj repo that has it as a remote.
937 let test_env = TestEnvironment::default();
938 test_env.add_config("git.auto-local-bookmark = true");
939 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
940 let work_dir = test_env.work_dir("repo");
941 let git_repo_path = test_env.env_root().join("git-repo");
942 let git_repo = git::init_bare(git_repo_path);
943 work_dir
944 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
945 .success();
946 // Create a commit and a bookmark in the git repo
947 let git::CommitResult {
948 tree_id,
949 commit_id: first_git_repo_commit,
950 } = git::add_commit(
951 &git_repo,
952 "refs/heads/feature1",
953 "file",
954 b"content",
955 "message",
956 &[],
957 );
958
959 // Fetch normally
960 work_dir
961 .run_jj(["git", "fetch", "--remote=origin"])
962 .success();
963 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
964 feature1: qomsplrm ebeb70d8 message
965 @origin: qomsplrm ebeb70d8 message
966 [EOF]
967 ");
968
969 // TEST 1: with export-import
970 // Forget the bookmark with --include-remotes
971 work_dir
972 .run_jj(["bookmark", "forget", "--include-remotes", "feature1"])
973 .success();
974 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
975
976 // At this point `jj git export && jj git import` does *not* recreate the
977 // bookmark. This behavior is important in colocated repos, as otherwise a
978 // forgotten bookmark would be immediately resurrected.
979 //
980 // Technically, this is because `jj bookmark forget` preserved
981 // the ref in jj view's `git_refs` tracking the local git repo's remote-tracking
982 // bookmark.
983 // TODO: Show that jj git push is also a no-op
984 let output = work_dir.run_jj(["git", "export"]);
985 insta::assert_snapshot!(output, @"");
986 let output = work_dir.run_jj(["git", "import"]);
987 insta::assert_snapshot!(output, @r"
988 ------- stderr -------
989 Nothing changed.
990 [EOF]
991 ");
992 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
993
994 // We can fetch feature1 again.
995 let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
996 insta::assert_snapshot!(output, @r"
997 ------- stderr -------
998 bookmark: feature1@origin [new] tracked
999 [EOF]
1000 ");
1001 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1002 feature1: qomsplrm ebeb70d8 message
1003 @origin: qomsplrm ebeb70d8 message
1004 [EOF]
1005 ");
1006
1007 // TEST 2: No export/import (otherwise the same as test 1)
1008 work_dir
1009 .run_jj(["bookmark", "forget", "--include-remotes", "feature1"])
1010 .success();
1011 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
1012 // Fetch works even without the export-import
1013 let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
1014 insta::assert_snapshot!(output, @r"
1015 ------- stderr -------
1016 bookmark: feature1@origin [new] tracked
1017 [EOF]
1018 ");
1019 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1020 feature1: qomsplrm ebeb70d8 message
1021 @origin: qomsplrm ebeb70d8 message
1022 [EOF]
1023 ");
1024
1025 // TEST 3: fetch bookmark that was moved & forgotten with --include-remotes
1026
1027 // Move the bookmark in the git repo.
1028 git::write_commit(
1029 &git_repo,
1030 "refs/heads/feature1",
1031 tree_id,
1032 "another message",
1033 &[first_git_repo_commit],
1034 );
1035 let output = work_dir.run_jj(["bookmark", "forget", "--include-remotes", "feature1"]);
1036 insta::assert_snapshot!(output, @r"
1037 ------- stderr -------
1038 Forgot 1 local bookmarks.
1039 Forgot 1 remote bookmarks.
1040 [EOF]
1041 ");
1042
1043 // Fetching a moved bookmark does not create a conflict
1044 let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
1045 insta::assert_snapshot!(output, @r"
1046 ------- stderr -------
1047 bookmark: feature1@origin [new] tracked
1048 [EOF]
1049 ");
1050 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1051 feature1: tyvxnvqr 9175cb32 (empty) another message
1052 @origin: tyvxnvqr 9175cb32 (empty) another message
1053 [EOF]
1054 ");
1055
1056 // TEST 4: If `--include-remotes` isn't used, remote bookmarks are untracked
1057 work_dir
1058 .run_jj(["bookmark", "forget", "feature1"])
1059 .success();
1060 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1061 feature1@origin: tyvxnvqr 9175cb32 (empty) another message
1062 [EOF]
1063 ");
1064 // There should be no output here since the remote bookmark wasn't forgotten
1065 let output = work_dir.run_jj(["git", "fetch", "--remote=origin"]);
1066 insta::assert_snapshot!(output, @r"
1067 ------- stderr -------
1068 Nothing changed.
1069 [EOF]
1070 ");
1071 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1072 feature1@origin: tyvxnvqr 9175cb32 (empty) another message
1073 [EOF]
1074 ");
1075}
1076
1077#[test]
1078fn test_bookmark_forget_deleted_or_nonexistent_bookmark() {
1079 // Much of this test is borrowed from `test_git_fetch_remote_only_bookmark` in
1080 // test_git_fetch.rs
1081
1082 // ======== Beginning of test setup ========
1083 // Set up a git repo with a bookmark and a jj repo that has it as a remote.
1084 let test_env = TestEnvironment::default();
1085 test_env.add_config("git.auto-local-bookmark = true");
1086 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1087 let work_dir = test_env.work_dir("repo");
1088 let git_repo_path = test_env.env_root().join("git-repo");
1089 let git_repo = git::init_bare(git_repo_path);
1090 // Create a commit and a bookmark in the git repo
1091 git::add_commit(
1092 &git_repo,
1093 "refs/heads/feature1",
1094 "file",
1095 b"content",
1096 "message",
1097 &[],
1098 );
1099 work_dir
1100 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
1101 .success();
1102
1103 // Fetch and then delete the bookmark
1104 work_dir
1105 .run_jj(["git", "fetch", "--remote=origin"])
1106 .success();
1107 work_dir
1108 .run_jj(["bookmark", "delete", "feature1"])
1109 .success();
1110 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1111 feature1 (deleted)
1112 @origin: qomsplrm ebeb70d8 message
1113 [EOF]
1114 ");
1115
1116 // ============ End of test setup ============
1117
1118 // We can forget a deleted bookmark
1119 work_dir
1120 .run_jj(["bookmark", "forget", "--include-remotes", "feature1"])
1121 .success();
1122 insta::assert_snapshot!(get_bookmark_output(&work_dir), @"");
1123
1124 // Can't forget a non-existent bookmark
1125 let output = work_dir.run_jj(["bookmark", "forget", "i_do_not_exist"]);
1126 insta::assert_snapshot!(output, @r"
1127 ------- stderr -------
1128 Error: No such bookmark: i_do_not_exist
1129 [EOF]
1130 [exit status: 1]
1131 ");
1132}
1133
1134#[test]
1135fn test_bookmark_track_untrack() {
1136 let test_env = TestEnvironment::default();
1137 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1138 let work_dir = test_env.work_dir("repo");
1139
1140 // Set up remote
1141 let git_repo_path = test_env.env_root().join("git-repo");
1142 let git_repo = git::init(git_repo_path);
1143 work_dir
1144 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
1145 .success();
1146
1147 // Fetch new commit without auto tracking. No local bookmarks should be
1148 // created.
1149 create_commit_with_refs(
1150 &git_repo,
1151 "commit 1",
1152 b"content 1",
1153 &[
1154 "refs/heads/main",
1155 "refs/heads/feature1",
1156 "refs/heads/feature2",
1157 ],
1158 );
1159 test_env.add_config("git.auto-local-bookmark = false");
1160 let output = work_dir.run_jj(["git", "fetch"]);
1161 insta::assert_snapshot!(output, @r"
1162 ------- stderr -------
1163 bookmark: feature1@origin [new] untracked
1164 bookmark: feature2@origin [new] untracked
1165 bookmark: main@origin [new] untracked
1166 [EOF]
1167 ");
1168 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1169 feature1@origin: qxxqrkql bd843888 commit 1
1170 feature2@origin: qxxqrkql bd843888 commit 1
1171 main@origin: qxxqrkql bd843888 commit 1
1172 [EOF]
1173 ");
1174 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1175 @ 230dd059e1b0
1176 │ ◆ feature1@origin feature2@origin main@origin bd843888ee66
1177 ├─╯
1178 ◆ 000000000000
1179 [EOF]
1180 ");
1181
1182 // Track new bookmark. Local bookmark should be created.
1183 work_dir
1184 .run_jj(["bookmark", "track", "feature1@origin", "main@origin"])
1185 .success();
1186 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1187 feature1: qxxqrkql bd843888 commit 1
1188 @origin: qxxqrkql bd843888 commit 1
1189 feature2@origin: qxxqrkql bd843888 commit 1
1190 main: qxxqrkql bd843888 commit 1
1191 @origin: qxxqrkql bd843888 commit 1
1192 [EOF]
1193 ");
1194
1195 // Track existing bookmark. Local bookmark should result in conflict.
1196 work_dir
1197 .run_jj(["bookmark", "create", "-r@", "feature2"])
1198 .success();
1199 work_dir
1200 .run_jj(["bookmark", "track", "feature2@origin"])
1201 .success();
1202 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1203 feature1: qxxqrkql bd843888 commit 1
1204 @origin: qxxqrkql bd843888 commit 1
1205 feature2 (conflicted):
1206 + qpvuntsm 230dd059 (empty) (no description set)
1207 + qxxqrkql bd843888 commit 1
1208 @origin (behind by 1 commits): qxxqrkql bd843888 commit 1
1209 main: qxxqrkql bd843888 commit 1
1210 @origin: qxxqrkql bd843888 commit 1
1211 [EOF]
1212 ");
1213
1214 // Untrack existing and locally-deleted bookmarks. Bookmark targets should be
1215 // unchanged
1216 work_dir
1217 .run_jj(["bookmark", "delete", "feature2"])
1218 .success();
1219 work_dir
1220 .run_jj(["bookmark", "untrack", "feature1@origin", "feature2@origin"])
1221 .success();
1222 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1223 feature1: qxxqrkql bd843888 commit 1
1224 feature1@origin: qxxqrkql bd843888 commit 1
1225 feature2@origin: qxxqrkql bd843888 commit 1
1226 main: qxxqrkql bd843888 commit 1
1227 @origin: qxxqrkql bd843888 commit 1
1228 [EOF]
1229 ");
1230 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1231 @ 230dd059e1b0
1232 │ ◆ feature1 feature1@origin feature2@origin main bd843888ee66
1233 ├─╯
1234 ◆ 000000000000
1235 [EOF]
1236 ");
1237
1238 // Fetch new commit. Only tracking bookmark "main" should be merged.
1239 create_commit_with_refs(
1240 &git_repo,
1241 "commit 2",
1242 b"content 2",
1243 &[
1244 "refs/heads/main",
1245 "refs/heads/feature1",
1246 "refs/heads/feature2",
1247 ],
1248 );
1249 let output = work_dir.run_jj(["git", "fetch"]);
1250 insta::assert_snapshot!(output, @r"
1251 ------- stderr -------
1252 bookmark: feature1@origin [updated] untracked
1253 bookmark: feature2@origin [updated] untracked
1254 bookmark: main@origin [updated] tracked
1255 [EOF]
1256 ");
1257 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1258 feature1: qxxqrkql bd843888 commit 1
1259 feature1@origin: psynomvr 48ec79a4 commit 2
1260 feature2@origin: psynomvr 48ec79a4 commit 2
1261 main: psynomvr 48ec79a4 commit 2
1262 @origin: psynomvr 48ec79a4 commit 2
1263 [EOF]
1264 ");
1265 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1266 @ 230dd059e1b0
1267 │ ◆ feature1@origin feature2@origin main 48ec79a430e9
1268 ├─╯
1269 │ ○ feature1 bd843888ee66
1270 ├─╯
1271 ◆ 000000000000
1272 [EOF]
1273 ");
1274
1275 // Fetch new commit with auto tracking. Tracking bookmark "main" and new
1276 // bookmark "feature3" should be merged.
1277 create_commit_with_refs(
1278 &git_repo,
1279 "commit 3",
1280 b"content 3",
1281 &[
1282 "refs/heads/main",
1283 "refs/heads/feature1",
1284 "refs/heads/feature2",
1285 "refs/heads/feature3",
1286 ],
1287 );
1288 test_env.add_config("git.auto-local-bookmark = true");
1289 let output = work_dir.run_jj(["git", "fetch"]);
1290 insta::assert_snapshot!(output, @r"
1291 ------- stderr -------
1292 bookmark: feature1@origin [updated] untracked
1293 bookmark: feature2@origin [updated] untracked
1294 bookmark: feature3@origin [new] tracked
1295 bookmark: main@origin [updated] tracked
1296 Abandoned 1 commits that are no longer reachable.
1297 [EOF]
1298 ");
1299 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1300 feature1: qxxqrkql bd843888 commit 1
1301 feature1@origin: yumopmsr d8cd3e02 commit 3
1302 feature2@origin: yumopmsr d8cd3e02 commit 3
1303 feature3: yumopmsr d8cd3e02 commit 3
1304 @origin: yumopmsr d8cd3e02 commit 3
1305 main: yumopmsr d8cd3e02 commit 3
1306 @origin: yumopmsr d8cd3e02 commit 3
1307 [EOF]
1308 ");
1309 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1310 @ 230dd059e1b0
1311 │ ◆ feature1@origin feature2@origin feature3 main d8cd3e020382
1312 ├─╯
1313 │ ○ feature1 bd843888ee66
1314 ├─╯
1315 ◆ 000000000000
1316 [EOF]
1317 ");
1318}
1319
1320#[test]
1321fn test_bookmark_track_conflict() {
1322 let test_env = TestEnvironment::default();
1323 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1324 let work_dir = test_env.work_dir("repo");
1325
1326 let git_repo_path = test_env.env_root().join("git-repo");
1327 git::init_bare(git_repo_path);
1328 work_dir
1329 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
1330 .success();
1331 work_dir
1332 .run_jj(["bookmark", "create", "-r@", "main"])
1333 .success();
1334 work_dir.run_jj(["describe", "-m", "a"]).success();
1335 work_dir
1336 .run_jj(["git", "push", "--allow-new", "-b", "main"])
1337 .success();
1338 work_dir
1339 .run_jj(["bookmark", "untrack", "main@origin"])
1340 .success();
1341 work_dir
1342 .run_jj(["describe", "-m", "b", "-r", "main", "--ignore-immutable"])
1343 .success();
1344 let output = work_dir.run_jj(["bookmark", "track", "main@origin"]);
1345 insta::assert_snapshot!(output, @r"
1346 ------- stderr -------
1347 Started tracking 1 remote bookmarks.
1348 main (conflicted):
1349 + qpvuntsm?? e802c4f8 (empty) b
1350 + qpvuntsm?? 427890ea (empty) a
1351 @origin (behind by 1 commits): qpvuntsm?? 427890ea (empty) a
1352 [EOF]
1353 ");
1354}
1355
1356#[test]
1357fn test_bookmark_track_untrack_patterns() {
1358 let test_env = TestEnvironment::default();
1359 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1360 let work_dir = test_env.work_dir("repo");
1361
1362 // Set up remote
1363 let git_repo_path = test_env.env_root().join("git-repo");
1364 let git_repo = git::init(git_repo_path);
1365 work_dir
1366 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
1367 .success();
1368
1369 // Create remote commit
1370 create_commit_with_refs(
1371 &git_repo,
1372 "commit",
1373 b"content",
1374 &["refs/heads/feature1", "refs/heads/feature2"],
1375 );
1376
1377 // Fetch new commit without auto tracking
1378 test_env.add_config("git.auto-local-bookmark = false");
1379 let output = work_dir.run_jj(["git", "fetch"]);
1380 insta::assert_snapshot!(output, @r"
1381 ------- stderr -------
1382 bookmark: feature1@origin [new] untracked
1383 bookmark: feature2@origin [new] untracked
1384 [EOF]
1385 ");
1386
1387 // Track local bookmark
1388 work_dir
1389 .run_jj(["bookmark", "create", "-r@", "main"])
1390 .success();
1391 insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "main"]), @r"
1392 ------- stderr -------
1393 error: invalid value 'main' for '<BOOKMARK@REMOTE>...': remote bookmark must be specified in bookmark@remote form
1394
1395 For more information, try '--help'.
1396 [EOF]
1397 [exit status: 2]
1398 ");
1399
1400 // Track/untrack unknown bookmark
1401 insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "main@origin"]), @r"
1402 ------- stderr -------
1403 Error: No such remote bookmark: main@origin
1404 [EOF]
1405 [exit status: 1]
1406 ");
1407 insta::assert_snapshot!(work_dir.run_jj(["bookmark", "untrack", "main@origin"]), @r"
1408 ------- stderr -------
1409 Error: No such remote bookmark: main@origin
1410 [EOF]
1411 [exit status: 1]
1412 ");
1413 insta::assert_snapshot!(work_dir.run_jj(["bookmark", "track", "glob:maine@*"]), @r"
1414 ------- stderr -------
1415 Error: No matching remote bookmarks for patterns: maine@*
1416 [EOF]
1417 [exit status: 1]
1418 ");
1419 insta::assert_snapshot!(
1420 work_dir.run_jj(["bookmark", "untrack", "main@origin", "glob:main@o*"]), @r"
1421 ------- stderr -------
1422 Error: No matching remote bookmarks for patterns: main@origin, main@o*
1423 [EOF]
1424 [exit status: 1]
1425 ");
1426
1427 // Track already tracked bookmark
1428 work_dir
1429 .run_jj(["bookmark", "track", "feature1@origin"])
1430 .success();
1431 let output = work_dir.run_jj(["bookmark", "track", "feature1@origin"]);
1432 insta::assert_snapshot!(output, @r"
1433 ------- stderr -------
1434 Warning: Remote bookmark already tracked: feature1@origin
1435 Nothing changed.
1436 [EOF]
1437 ");
1438
1439 // Untrack non-tracking bookmark
1440 let output = work_dir.run_jj(["bookmark", "untrack", "feature2@origin"]);
1441 insta::assert_snapshot!(output, @r"
1442 ------- stderr -------
1443 Warning: Remote bookmark not tracked yet: feature2@origin
1444 Nothing changed.
1445 [EOF]
1446 ");
1447
1448 // Untrack Git-tracking bookmark
1449 work_dir.run_jj(["git", "export"]).success();
1450 let output = work_dir.run_jj(["bookmark", "untrack", "main@git"]);
1451 insta::assert_snapshot!(output, @r"
1452 ------- stderr -------
1453 Warning: Git-tracking bookmark cannot be untracked: main@git
1454 Nothing changed.
1455 [EOF]
1456 ");
1457 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1458 feature1: yrnqsqlx 41e7a49d commit
1459 @git: yrnqsqlx 41e7a49d commit
1460 @origin: yrnqsqlx 41e7a49d commit
1461 feature2@origin: yrnqsqlx 41e7a49d commit
1462 main: qpvuntsm 230dd059 (empty) (no description set)
1463 @git: qpvuntsm 230dd059 (empty) (no description set)
1464 [EOF]
1465 ");
1466
1467 // Untrack by pattern
1468 let output = work_dir.run_jj(["bookmark", "untrack", "glob:*@*"]);
1469 insta::assert_snapshot!(output, @r"
1470 ------- stderr -------
1471 Warning: Git-tracking bookmark cannot be untracked: feature1@git
1472 Warning: Remote bookmark not tracked yet: feature2@origin
1473 Warning: Git-tracking bookmark cannot be untracked: main@git
1474 Stopped tracking 1 remote bookmarks.
1475 [EOF]
1476 ");
1477 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1478 feature1: yrnqsqlx 41e7a49d commit
1479 @git: yrnqsqlx 41e7a49d commit
1480 feature1@origin: yrnqsqlx 41e7a49d commit
1481 feature2@origin: yrnqsqlx 41e7a49d commit
1482 main: qpvuntsm 230dd059 (empty) (no description set)
1483 @git: qpvuntsm 230dd059 (empty) (no description set)
1484 [EOF]
1485 ");
1486
1487 // Track by pattern
1488 let output = work_dir.run_jj(["bookmark", "track", "glob:feature?@origin"]);
1489 insta::assert_snapshot!(output, @r"
1490 ------- stderr -------
1491 Started tracking 2 remote bookmarks.
1492 [EOF]
1493 ");
1494 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
1495 feature1: yrnqsqlx 41e7a49d commit
1496 @git: yrnqsqlx 41e7a49d commit
1497 @origin: yrnqsqlx 41e7a49d commit
1498 feature2: yrnqsqlx 41e7a49d commit
1499 @origin: yrnqsqlx 41e7a49d commit
1500 main: qpvuntsm 230dd059 (empty) (no description set)
1501 @git: qpvuntsm 230dd059 (empty) (no description set)
1502 [EOF]
1503 ");
1504}
1505
1506#[test]
1507fn test_bookmark_list() {
1508 let test_env = TestEnvironment::default();
1509 test_env.add_config("git.auto-local-bookmark = true");
1510
1511 // Initialize remote refs
1512 test_env.run_jj_in(".", ["git", "init", "remote"]).success();
1513 let remote_dir = test_env.work_dir("remote");
1514 for bookmark in [
1515 "remote-sync",
1516 "remote-unsync",
1517 "remote-untrack",
1518 "remote-delete",
1519 ] {
1520 remote_dir
1521 .run_jj(["new", "root()", "-m", bookmark])
1522 .success();
1523 remote_dir
1524 .run_jj(["bookmark", "create", "-r@", bookmark])
1525 .success();
1526 }
1527 remote_dir.run_jj(["new"]).success();
1528 remote_dir.run_jj(["git", "export"]).success();
1529
1530 // Initialize local refs
1531 let mut remote_git_path = remote_dir.root().to_owned();
1532 remote_git_path.extend([".jj", "repo", "store", "git"]);
1533 test_env
1534 .run_jj_in(
1535 ".",
1536 ["git", "clone", remote_git_path.to_str().unwrap(), "local"],
1537 )
1538 .success();
1539 let local_dir = test_env.work_dir("local");
1540 local_dir
1541 .run_jj(["new", "root()", "-m", "local-only"])
1542 .success();
1543 local_dir
1544 .run_jj(["bookmark", "create", "-r@", "local-only"])
1545 .success();
1546
1547 // Mutate refs in local repository
1548 local_dir
1549 .run_jj(["bookmark", "delete", "remote-delete"])
1550 .success();
1551 local_dir
1552 .run_jj(["bookmark", "delete", "remote-untrack"])
1553 .success();
1554 local_dir
1555 .run_jj(["bookmark", "untrack", "remote-untrack@origin"])
1556 .success();
1557 local_dir
1558 .run_jj([
1559 "bookmark",
1560 "set",
1561 "--allow-backwards",
1562 "--to=@",
1563 "remote-unsync",
1564 ])
1565 .success();
1566
1567 // Synchronized tracking remotes and non-tracking remotes aren't listed by
1568 // default
1569 let output = local_dir.run_jj(["bookmark", "list"]);
1570 insta::assert_snapshot!(output, @r"
1571 local-only: wqnwkozp 4e887f78 (empty) local-only
1572 remote-delete (deleted)
1573 @origin: mnmymoky 203e60eb (empty) remote-delete
1574 remote-sync: zwtyzrop c761c7ea (empty) remote-sync
1575 remote-unsync: wqnwkozp 4e887f78 (empty) local-only
1576 @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
1577 [EOF]
1578 ------- stderr -------
1579 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1580 [EOF]
1581 ");
1582
1583 let output = local_dir.run_jj(["bookmark", "list", "--all-remotes"]);
1584 insta::assert_snapshot!(output, @r"
1585 local-only: wqnwkozp 4e887f78 (empty) local-only
1586 remote-delete (deleted)
1587 @origin: mnmymoky 203e60eb (empty) remote-delete
1588 remote-sync: zwtyzrop c761c7ea (empty) remote-sync
1589 @origin: zwtyzrop c761c7ea (empty) remote-sync
1590 remote-unsync: wqnwkozp 4e887f78 (empty) local-only
1591 @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
1592 remote-untrack@origin: vmortlor 71a16b05 (empty) remote-untrack
1593 [EOF]
1594 ------- stderr -------
1595 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1596 [EOF]
1597 ");
1598
1599 let template = r#"
1600 concat(
1601 "[" ++ name ++ if(remote, "@" ++ remote) ++ "]\n",
1602 separate(" ", "present:", present) ++ "\n",
1603 separate(" ", "conflict:", conflict) ++ "\n",
1604 separate(" ", "normal_target:", normal_target.description().first_line()) ++ "\n",
1605 separate(" ", "removed_targets:", removed_targets.map(|c| c.description().first_line())) ++ "\n",
1606 separate(" ", "added_targets:", added_targets.map(|c| c.description().first_line())) ++ "\n",
1607 separate(" ", "tracked:", tracked) ++ "\n",
1608 separate(" ", "tracking_present:", tracking_present) ++ "\n",
1609 separate(" ", "tracking_ahead_count:", tracking_ahead_count.lower()) ++ "\n",
1610 separate(" ", "tracking_behind_count:", tracking_behind_count.lower()) ++ "\n",
1611 )
1612 "#;
1613 let output = local_dir.run_jj(["bookmark", "list", "--all-remotes", "-T", template]);
1614 insta::assert_snapshot!(output, @r"
1615 [local-only]
1616 present: true
1617 conflict: false
1618 normal_target: local-only
1619 removed_targets:
1620 added_targets: local-only
1621 tracked: false
1622 tracking_present: false
1623 tracking_ahead_count: <Error: Not a tracked remote ref>
1624 tracking_behind_count: <Error: Not a tracked remote ref>
1625 [remote-delete]
1626 present: false
1627 conflict: false
1628 normal_target: <Error: No Commit available>
1629 removed_targets:
1630 added_targets:
1631 tracked: false
1632 tracking_present: false
1633 tracking_ahead_count: <Error: Not a tracked remote ref>
1634 tracking_behind_count: <Error: Not a tracked remote ref>
1635 [remote-delete@origin]
1636 present: true
1637 conflict: false
1638 normal_target: remote-delete
1639 removed_targets:
1640 added_targets: remote-delete
1641 tracked: true
1642 tracking_present: false
1643 tracking_ahead_count: 2
1644 tracking_behind_count: 0
1645 [remote-sync]
1646 present: true
1647 conflict: false
1648 normal_target: remote-sync
1649 removed_targets:
1650 added_targets: remote-sync
1651 tracked: false
1652 tracking_present: false
1653 tracking_ahead_count: <Error: Not a tracked remote ref>
1654 tracking_behind_count: <Error: Not a tracked remote ref>
1655 [remote-sync@origin]
1656 present: true
1657 conflict: false
1658 normal_target: remote-sync
1659 removed_targets:
1660 added_targets: remote-sync
1661 tracked: true
1662 tracking_present: true
1663 tracking_ahead_count: 0
1664 tracking_behind_count: 0
1665 [remote-unsync]
1666 present: true
1667 conflict: false
1668 normal_target: local-only
1669 removed_targets:
1670 added_targets: local-only
1671 tracked: false
1672 tracking_present: false
1673 tracking_ahead_count: <Error: Not a tracked remote ref>
1674 tracking_behind_count: <Error: Not a tracked remote ref>
1675 [remote-unsync@origin]
1676 present: true
1677 conflict: false
1678 normal_target: remote-unsync
1679 removed_targets:
1680 added_targets: remote-unsync
1681 tracked: true
1682 tracking_present: true
1683 tracking_ahead_count: 1
1684 tracking_behind_count: 1
1685 [remote-untrack@origin]
1686 present: true
1687 conflict: false
1688 normal_target: remote-untrack
1689 removed_targets:
1690 added_targets: remote-untrack
1691 tracked: false
1692 tracking_present: false
1693 tracking_ahead_count: <Error: Not a tracked remote ref>
1694 tracking_behind_count: <Error: Not a tracked remote ref>
1695 [EOF]
1696 ------- stderr -------
1697 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1698 [EOF]
1699 ");
1700}
1701
1702#[test]
1703fn test_bookmark_list_filtered() {
1704 let test_env = TestEnvironment::default();
1705 test_env.add_config("git.auto-local-bookmark = true");
1706 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#);
1707
1708 // Initialize remote refs
1709 test_env.run_jj_in(".", ["git", "init", "remote"]).success();
1710 let remote_dir = test_env.work_dir("remote");
1711 for bookmark in ["remote-keep", "remote-delete", "remote-rewrite"] {
1712 remote_dir
1713 .run_jj(["new", "root()", "-m", bookmark])
1714 .success();
1715 remote_dir
1716 .run_jj(["bookmark", "create", "-r@", bookmark])
1717 .success();
1718 }
1719 remote_dir.run_jj(["new"]).success();
1720 remote_dir.run_jj(["git", "export"]).success();
1721
1722 // Initialize local refs
1723 let mut remote_git_path = remote_dir.root().to_owned();
1724 remote_git_path.extend([".jj", "repo", "store", "git"]);
1725 test_env
1726 .run_jj_in(
1727 ".",
1728 ["git", "clone", remote_git_path.to_str().unwrap(), "local"],
1729 )
1730 .success();
1731 let local_dir = test_env.work_dir("local");
1732 local_dir
1733 .run_jj(["new", "root()", "-m", "local-keep"])
1734 .success();
1735 local_dir
1736 .run_jj(["bookmark", "create", "-r@", "local-keep"])
1737 .success();
1738
1739 // Mutate refs in local repository
1740 local_dir
1741 .run_jj(["bookmark", "delete", "remote-delete"])
1742 .success();
1743 local_dir
1744 .run_jj(["describe", "-mrewritten", "remote-rewrite"])
1745 .success();
1746
1747 let template = r#"separate(" ", commit_id.short(), bookmarks, if(hidden, "(hidden)"))"#;
1748 insta::assert_snapshot!(
1749 local_dir.run_jj(["log", "-r::(bookmarks() | remote_bookmarks())", "-T", template]), @r"
1750 @ c7b4c09cd77c local-keep
1751 │ ○ e31634b64294 remote-rewrite*
1752 ├─╯
1753 │ ○ 3e9a5af6ef15 remote-rewrite@origin (hidden)
1754 ├─╯
1755 │ ○ dad5f298ca57 remote-delete@origin
1756 ├─╯
1757 │ ○ 911e912015fb remote-keep
1758 ├─╯
1759 ◆ 000000000000
1760 [EOF]
1761 ");
1762
1763 // All bookmarks are listed by default.
1764 let output = local_dir.run_jj(["bookmark", "list"]);
1765 insta::assert_snapshot!(output, @r"
1766 local-keep: kpqxywon c7b4c09c (empty) local-keep
1767 remote-delete (deleted)
1768 @origin: yxusvupt dad5f298 (empty) remote-delete
1769 remote-keep: nlwprzpn 911e9120 (empty) remote-keep
1770 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1771 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1772 [EOF]
1773 ------- stderr -------
1774 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1775 [EOF]
1776 ");
1777
1778 let query =
1779 |args: &[&str]| local_dir.run_jj_with(|cmd| cmd.args(["bookmark", "list"]).args(args));
1780
1781 // "all()" doesn't include deleted bookmarks since they have no local targets.
1782 // So "all()" is identical to "bookmarks()".
1783 insta::assert_snapshot!(query(&["-rall()"]), @r"
1784 local-keep: kpqxywon c7b4c09c (empty) local-keep
1785 remote-keep: nlwprzpn 911e9120 (empty) remote-keep
1786 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1787 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1788 [EOF]
1789 ");
1790
1791 // Exclude remote-only bookmarks. "remote-rewrite@origin" is included since
1792 // local "remote-rewrite" target matches.
1793 insta::assert_snapshot!(query(&["-rbookmarks()"]), @r"
1794 local-keep: kpqxywon c7b4c09c (empty) local-keep
1795 remote-keep: nlwprzpn 911e9120 (empty) remote-keep
1796 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1797 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1798 [EOF]
1799 ");
1800
1801 // Select bookmarks by name.
1802 insta::assert_snapshot!(query(&["remote-rewrite"]), @r"
1803 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1804 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1805 [EOF]
1806 ");
1807 insta::assert_snapshot!(query(&["-rbookmarks(remote-rewrite)"]), @r"
1808 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1809 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1810 [EOF]
1811 ");
1812
1813 // Select bookmarks by name, combined with --all-remotes
1814 local_dir.run_jj(["git", "export"]).success();
1815 insta::assert_snapshot!(query(&["--all-remotes", "remote-rewrite"]), @r"
1816 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1817 @git: xyxluytn e31634b6 (empty) rewritten
1818 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1819 [EOF]
1820 ");
1821 insta::assert_snapshot!(query(&["--all-remotes", "-rbookmarks(remote-rewrite)"]), @r"
1822 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1823 @git: xyxluytn e31634b6 (empty) rewritten
1824 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1825 [EOF]
1826 ");
1827
1828 // Select bookmarks with --remote
1829 insta::assert_snapshot!(query(&["--remote", "origin"]), @r"
1830 remote-delete (deleted)
1831 @origin: yxusvupt dad5f298 (empty) remote-delete
1832 remote-keep: nlwprzpn 911e9120 (empty) remote-keep
1833 @origin: nlwprzpn 911e9120 (empty) remote-keep
1834 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1835 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1836 [EOF]
1837 ------- stderr -------
1838 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1839 [EOF]
1840 ");
1841 insta::assert_snapshot!(query(&["--remote", "glob:gi?"]), @r"
1842 local-keep: kpqxywon c7b4c09c (empty) local-keep
1843 @git: kpqxywon c7b4c09c (empty) local-keep
1844 remote-keep: nlwprzpn 911e9120 (empty) remote-keep
1845 @git: nlwprzpn 911e9120 (empty) remote-keep
1846 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1847 @git: xyxluytn e31634b6 (empty) rewritten
1848 [EOF]
1849 ");
1850 insta::assert_snapshot!(query(&["--remote", "origin", "--remote", "git"]), @r"
1851 local-keep: kpqxywon c7b4c09c (empty) local-keep
1852 @git: kpqxywon c7b4c09c (empty) local-keep
1853 remote-delete (deleted)
1854 @origin: yxusvupt dad5f298 (empty) remote-delete
1855 remote-keep: nlwprzpn 911e9120 (empty) remote-keep
1856 @git: nlwprzpn 911e9120 (empty) remote-keep
1857 @origin: nlwprzpn 911e9120 (empty) remote-keep
1858 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1859 @git: xyxluytn e31634b6 (empty) rewritten
1860 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1861 [EOF]
1862 ------- stderr -------
1863 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1864 [EOF]
1865 ");
1866
1867 // Can select deleted bookmark by name pattern, but not by revset.
1868 insta::assert_snapshot!(query(&["remote-delete"]), @r"
1869 remote-delete (deleted)
1870 @origin: yxusvupt dad5f298 (empty) remote-delete
1871 [EOF]
1872 ------- stderr -------
1873 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1874 [EOF]
1875 ");
1876 insta::assert_snapshot!(query(&["-rbookmarks(remote-delete)"]), @"");
1877 insta::assert_snapshot!(query(&["-rremote-delete"]), @r"
1878 ------- stderr -------
1879 Error: Revision `remote-delete` doesn't exist
1880 Hint: Did you mean `remote-delete@origin`, `remote-keep`, `remote-rewrite`, `remote-rewrite@origin`?
1881 [EOF]
1882 [exit status: 1]
1883 ");
1884
1885 // Name patterns are OR-ed.
1886 insta::assert_snapshot!(query(&["glob:*-keep", "remote-delete"]), @r"
1887 local-keep: kpqxywon c7b4c09c (empty) local-keep
1888 remote-delete (deleted)
1889 @origin: yxusvupt dad5f298 (empty) remote-delete
1890 remote-keep: nlwprzpn 911e9120 (empty) remote-keep
1891 [EOF]
1892 ------- stderr -------
1893 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
1894 [EOF]
1895 ");
1896
1897 // Unmatched name pattern shouldn't be an error. A warning can be added later.
1898 insta::assert_snapshot!(query(&["local-keep", "glob:push-*"]), @r"
1899 local-keep: kpqxywon c7b4c09c (empty) local-keep
1900 [EOF]
1901 ");
1902
1903 // Name pattern and revset are OR-ed.
1904 insta::assert_snapshot!(query(&["local-keep", "-rbookmarks(remote-rewrite)"]), @r"
1905 local-keep: kpqxywon c7b4c09c (empty) local-keep
1906 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1907 @origin (ahead by 1 commits, behind by 1 commits): xyxluytn hidden 3e9a5af6 (empty) remote-rewrite
1908 [EOF]
1909 ");
1910
1911 // … but still filtered by --remote
1912 insta::assert_snapshot!(query(&[
1913 "local-keep",
1914 "-rbookmarks(remote-rewrite)",
1915 "--remote",
1916 "git",
1917 ]), @r"
1918 local-keep: kpqxywon c7b4c09c (empty) local-keep
1919 @git: kpqxywon c7b4c09c (empty) local-keep
1920 remote-rewrite: xyxluytn e31634b6 (empty) rewritten
1921 @git: xyxluytn e31634b6 (empty) rewritten
1922 [EOF]
1923 ");
1924}
1925
1926#[test]
1927fn test_bookmark_list_much_remote_divergence() {
1928 let test_env = TestEnvironment::default();
1929 test_env.add_config("git.auto-local-bookmark = true");
1930
1931 // Initialize remote refs
1932 test_env.run_jj_in(".", ["git", "init", "remote"]).success();
1933 let remote_dir = test_env.work_dir("remote");
1934 remote_dir
1935 .run_jj(["new", "root()", "-m", "remote-unsync"])
1936 .success();
1937 for _ in 0..15 {
1938 remote_dir.run_jj(["new", "-m", "remote-unsync"]).success();
1939 }
1940 remote_dir
1941 .run_jj(["bookmark", "create", "-r@", "remote-unsync"])
1942 .success();
1943 remote_dir.run_jj(["new"]).success();
1944 remote_dir.run_jj(["git", "export"]).success();
1945
1946 // Initialize local refs
1947 let mut remote_git_path = remote_dir.root().to_owned();
1948 remote_git_path.extend([".jj", "repo", "store", "git"]);
1949 test_env
1950 .run_jj_in(
1951 ".",
1952 ["git", "clone", remote_git_path.to_str().unwrap(), "local"],
1953 )
1954 .success();
1955 let local_dir = test_env.work_dir("local");
1956 local_dir
1957 .run_jj(["new", "root()", "-m", "local-only"])
1958 .success();
1959 for _ in 0..15 {
1960 local_dir.run_jj(["new", "-m", "local-only"]).success();
1961 }
1962 local_dir
1963 .run_jj(["bookmark", "create", "-r@", "local-only"])
1964 .success();
1965
1966 // Mutate refs in local repository
1967 local_dir
1968 .run_jj([
1969 "bookmark",
1970 "set",
1971 "--allow-backwards",
1972 "--to=@",
1973 "remote-unsync",
1974 ])
1975 .success();
1976
1977 let output = local_dir.run_jj(["bookmark", "list"]);
1978 insta::assert_snapshot!(output, @r"
1979 local-only: zkyosouw 4ab3f751 (empty) local-only
1980 remote-unsync: zkyosouw 4ab3f751 (empty) local-only
1981 @origin (ahead by at least 10 commits, behind by at least 10 commits): lxyktnks 19582022 (empty) remote-unsync
1982 [EOF]
1983 ");
1984}
1985
1986#[test]
1987fn test_bookmark_list_tracked() {
1988 let test_env = TestEnvironment::default();
1989 test_env.add_config("git.auto-local-bookmark = true");
1990
1991 // Initialize remote refs
1992 test_env.run_jj_in(".", ["git", "init", "remote"]).success();
1993 let remote_dir = test_env.work_dir("remote");
1994 for bookmark in [
1995 "remote-sync",
1996 "remote-unsync",
1997 "remote-untrack",
1998 "remote-delete",
1999 ] {
2000 remote_dir
2001 .run_jj(["new", "root()", "-m", bookmark])
2002 .success();
2003 remote_dir
2004 .run_jj(["bookmark", "create", "-r@", bookmark])
2005 .success();
2006 }
2007 remote_dir.run_jj(["new"]).success();
2008 remote_dir.run_jj(["git", "export"]).success();
2009
2010 // Initialize local refs
2011 let mut remote_git_path = remote_dir.root().to_owned();
2012 remote_git_path.extend([".jj", "repo", "store", "git"]);
2013 test_env
2014 .run_jj_in(
2015 ".",
2016 [
2017 "git",
2018 "clone",
2019 "--colocate",
2020 remote_git_path.to_str().unwrap(),
2021 "local",
2022 ],
2023 )
2024 .success();
2025
2026 test_env
2027 .run_jj_in(".", ["git", "init", "upstream"])
2028 .success();
2029
2030 // Initialize a second remote
2031 let upstream_dir = test_env.work_dir("upstream");
2032 upstream_dir
2033 .run_jj(["new", "root()", "-m", "upstream-sync"])
2034 .success();
2035 upstream_dir
2036 .run_jj(["bookmark", "create", "-r@", "upstream-sync"])
2037 .success();
2038 upstream_dir.run_jj(["new"]).success();
2039 upstream_dir.run_jj(["git", "export"]).success();
2040
2041 let mut upstream_git_path = upstream_dir.root().to_owned();
2042 upstream_git_path.extend([".jj", "repo", "store", "git"]);
2043
2044 let local_dir = test_env.work_dir("local");
2045
2046 local_dir
2047 .run_jj([
2048 "git",
2049 "remote",
2050 "add",
2051 "upstream",
2052 upstream_git_path.to_str().unwrap(),
2053 ])
2054 .success();
2055 local_dir
2056 .run_jj(["git", "fetch", "--all-remotes"])
2057 .success();
2058
2059 local_dir
2060 .run_jj(["new", "root()", "-m", "local-only"])
2061 .success();
2062 local_dir
2063 .run_jj(["bookmark", "create", "-r@", "local-only"])
2064 .success();
2065
2066 // Mutate refs in local repository
2067 local_dir
2068 .run_jj(["bookmark", "delete", "remote-delete"])
2069 .success();
2070 local_dir
2071 .run_jj(["bookmark", "delete", "remote-untrack"])
2072 .success();
2073 local_dir
2074 .run_jj(["bookmark", "untrack", "remote-untrack@origin"])
2075 .success();
2076 local_dir
2077 .run_jj([
2078 "git",
2079 "push",
2080 "--allow-new",
2081 "--remote",
2082 "upstream",
2083 "--bookmark",
2084 "remote-unsync",
2085 ])
2086 .success();
2087 local_dir
2088 .run_jj([
2089 "bookmark",
2090 "set",
2091 "--to=@",
2092 "--allow-backwards",
2093 "remote-unsync",
2094 ])
2095 .success();
2096
2097 let output = local_dir.run_jj(["bookmark", "list", "--all-remotes"]);
2098 insta::assert_snapshot!(output, @r"
2099 local-only: nmzmmopx e1da745b (empty) local-only
2100 @git: nmzmmopx e1da745b (empty) local-only
2101 remote-delete (deleted)
2102 @origin: mnmymoky 203e60eb (empty) remote-delete
2103 remote-sync: zwtyzrop c761c7ea (empty) remote-sync
2104 @git: zwtyzrop c761c7ea (empty) remote-sync
2105 @origin: zwtyzrop c761c7ea (empty) remote-sync
2106 remote-unsync: nmzmmopx e1da745b (empty) local-only
2107 @git: nmzmmopx e1da745b (empty) local-only
2108 @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2109 @upstream (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2110 remote-untrack@origin: vmortlor 71a16b05 (empty) remote-untrack
2111 upstream-sync: lolpmnqw 32fa6da0 (empty) upstream-sync
2112 @git: lolpmnqw 32fa6da0 (empty) upstream-sync
2113 @upstream: lolpmnqw 32fa6da0 (empty) upstream-sync
2114 [EOF]
2115 ------- stderr -------
2116 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
2117 [EOF]
2118 ");
2119
2120 let output = local_dir.run_jj(["bookmark", "list", "--tracked"]);
2121 insta::assert_snapshot!(output, @r"
2122 remote-delete (deleted)
2123 @origin: mnmymoky 203e60eb (empty) remote-delete
2124 remote-sync: zwtyzrop c761c7ea (empty) remote-sync
2125 @origin: zwtyzrop c761c7ea (empty) remote-sync
2126 remote-unsync: nmzmmopx e1da745b (empty) local-only
2127 @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2128 @upstream (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2129 upstream-sync: lolpmnqw 32fa6da0 (empty) upstream-sync
2130 @upstream: lolpmnqw 32fa6da0 (empty) upstream-sync
2131 [EOF]
2132 ------- stderr -------
2133 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
2134 [EOF]
2135 ");
2136
2137 let output = local_dir.run_jj(["bookmark", "list", "--tracked", "--remote", "origin"]);
2138 insta::assert_snapshot!(output, @r"
2139 remote-delete (deleted)
2140 @origin: mnmymoky 203e60eb (empty) remote-delete
2141 remote-sync: zwtyzrop c761c7ea (empty) remote-sync
2142 @origin: zwtyzrop c761c7ea (empty) remote-sync
2143 remote-unsync: nmzmmopx e1da745b (empty) local-only
2144 @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2145 [EOF]
2146 ------- stderr -------
2147 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this.
2148 [EOF]
2149 ");
2150
2151 let output = local_dir.run_jj(["bookmark", "list", "--tracked", "remote-unsync"]);
2152 insta::assert_snapshot!(output, @r"
2153 remote-unsync: nmzmmopx e1da745b (empty) local-only
2154 @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2155 @upstream (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2156 [EOF]
2157 ");
2158
2159 let output = local_dir.run_jj(["bookmark", "list", "--tracked", "remote-untrack"]);
2160 insta::assert_snapshot!(output, @"");
2161
2162 local_dir
2163 .run_jj(["bookmark", "untrack", "remote-unsync@upstream"])
2164 .success();
2165
2166 let output = local_dir.run_jj(["bookmark", "list", "--tracked", "remote-unsync"]);
2167 insta::assert_snapshot!(output, @r"
2168 remote-unsync: nmzmmopx e1da745b (empty) local-only
2169 @origin (ahead by 1 commits, behind by 1 commits): qpsqxpyq 38ef8af7 (empty) remote-unsync
2170 [EOF]
2171 ");
2172}
2173
2174#[test]
2175fn test_bookmark_list_conflicted() {
2176 let test_env = TestEnvironment::default();
2177 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2178 let work_dir = test_env.work_dir("repo");
2179
2180 // Track existing bookmark. Local bookmark should result in conflict.
2181 work_dir.run_jj(["new", "root()", "-m", "a"]).success();
2182 work_dir.run_jj(["new", "root()", "-m", "b"]).success();
2183 work_dir
2184 .run_jj(["bookmark", "create", "-r@", "bar"])
2185 .success();
2186 work_dir
2187 .run_jj(["bookmark", "create", "foo", "-r", "description(a)"])
2188 .success();
2189 work_dir
2190 .run_jj([
2191 "bookmark",
2192 "create",
2193 "foo",
2194 "-r",
2195 "description(b)",
2196 "--at-op=@-",
2197 ])
2198 .success();
2199 work_dir.run_jj(["status"]).success();
2200 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
2201 bar: kkmpptxz 06a973bc (empty) b
2202 foo (conflicted):
2203 + rlvkpnrz d8d5f980 (empty) a
2204 + kkmpptxz 06a973bc (empty) b
2205 [EOF]
2206 ");
2207 insta::assert_snapshot!(work_dir.run_jj(["bookmark", "list", "--conflicted"]), @r"
2208 foo (conflicted):
2209 + rlvkpnrz d8d5f980 (empty) a
2210 + kkmpptxz 06a973bc (empty) b
2211 [EOF]
2212 ");
2213}
2214
2215#[test]
2216fn test_bookmark_create_with_default_target_revision() {
2217 let test_env = TestEnvironment::default();
2218 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2219 let work_dir = test_env.work_dir("repo");
2220
2221 let output = work_dir.run_jj(["bookmark", "create", "foo"]);
2222 insta::assert_snapshot!(output, @r"
2223 ------- stderr -------
2224 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.
2225 Created 1 bookmarks pointing to qpvuntsm 230dd059 foo | (empty) (no description set)
2226 [EOF]
2227 ");
2228}
2229
2230#[test]
2231fn test_bookmark_set_with_default_target_revision() {
2232 let test_env = TestEnvironment::default();
2233 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2234 let work_dir = test_env.work_dir("repo");
2235
2236 let output = work_dir.run_jj(["bookmark", "set", "foo"]);
2237 insta::assert_snapshot!(output, @r"
2238 ------- stderr -------
2239 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.
2240 Created 1 bookmarks pointing to qpvuntsm 230dd059 foo | (empty) (no description set)
2241 [EOF]
2242 ");
2243}
2244
2245#[test]
2246fn test_bookmark_move_with_default_target_revision() {
2247 let test_env = TestEnvironment::default();
2248 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2249 let work_dir = test_env.work_dir("repo");
2250
2251 // Set up remote
2252 let git_repo_path = test_env.env_root().join("git-repo");
2253 git::init_bare(git_repo_path);
2254 work_dir
2255 .run_jj(["git", "remote", "add", "origin", "../git-repo"])
2256 .success();
2257
2258 let output = work_dir.run_jj(["bookmark", "create", "foo", "-r@"]);
2259 insta::assert_snapshot!(output, @r"
2260 ------- stderr -------
2261 Created 1 bookmarks pointing to qpvuntsm 230dd059 foo | (empty) (no description set)
2262 [EOF]
2263 ");
2264
2265 work_dir.run_jj(["new"]).success();
2266 let output = work_dir.run_jj(["bookmark", "move", "foo"]);
2267 insta::assert_snapshot!(output, @r"
2268 ------- stderr -------
2269 Warning: Target revision was not specified, defaulting to the working copy (--to=@). In the near future it will be required to explicitly specify it.
2270 Moved 1 bookmarks to zsuskuln 8bb159bc foo | (empty) (no description set)
2271 [EOF]
2272 ");
2273}
2274
2275#[must_use]
2276fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
2277 let template = r#"bookmarks ++ " " ++ commit_id.short()"#;
2278 work_dir.run_jj(["log", "-T", template])
2279}
2280
2281#[must_use]
2282fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput {
2283 // --quiet to suppress deleted bookmarks hint
2284 work_dir.run_jj(["bookmark", "list", "--all-remotes", "--quiet"])
2285}