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 std::path::Path;
16
17use test_case::test_case;
18
19use crate::common::CommandOutput;
20use crate::common::TestEnvironment;
21
22/// Test adding a second workspace
23#[test]
24fn test_workspaces_add_second_workspace() {
25 let test_env = TestEnvironment::default();
26 test_env.run_jj_in(".", ["git", "init", "main"]).success();
27 let main_path = test_env.env_root().join("main");
28 let secondary_path = test_env.env_root().join("secondary");
29
30 std::fs::write(main_path.join("file"), "contents").unwrap();
31 test_env
32 .run_jj_in(&main_path, ["commit", "-m", "initial"])
33 .success();
34
35 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
36 insta::assert_snapshot!(output, @r"
37 default: rlvkpnrz 8183d0fc (empty) (no description set)
38 [EOF]
39 ");
40
41 let output = test_env.run_jj_in(
42 &main_path,
43 ["workspace", "add", "--name", "second", "../secondary"],
44 );
45 insta::assert_snapshot!(output.normalize_backslash(), @r#"
46 ------- stderr -------
47 Created workspace in "../secondary"
48 Working copy (@) now at: rzvqmyuk 5ed2222c (empty) (no description set)
49 Parent commit (@-) : qpvuntsm 751b12b7 initial
50 Added 1 files, modified 0 files, removed 0 files
51 [EOF]
52 "#);
53
54 // Can see the working-copy commit in each workspace in the log output. The "@"
55 // node in the graph indicates the current workspace's working-copy commit.
56 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
57 @ 8183d0fcaa4c default@
58 │ ○ 5ed2222c28e2 second@
59 ├─╯
60 ○ 751b12b7b981
61 ◆ 000000000000
62 [EOF]
63 ");
64 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r"
65 @ 5ed2222c28e2 second@
66 │ ○ 8183d0fcaa4c default@
67 ├─╯
68 ○ 751b12b7b981
69 ◆ 000000000000
70 [EOF]
71 ");
72
73 // Both workspaces show up when we list them
74 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
75 insta::assert_snapshot!(output, @r"
76 default: rlvkpnrz 8183d0fc (empty) (no description set)
77 second: rzvqmyuk 5ed2222c (empty) (no description set)
78 [EOF]
79 ");
80}
81
82/// Test how sparse patterns are inherited
83#[test]
84fn test_workspaces_sparse_patterns() {
85 let test_env = TestEnvironment::default();
86 test_env.run_jj_in(".", ["git", "init", "ws1"]).success();
87 let ws1_path = test_env.env_root().join("ws1");
88 let ws2_path = test_env.env_root().join("ws2");
89 let ws3_path = test_env.env_root().join("ws3");
90 let ws4_path = test_env.env_root().join("ws4");
91 let ws5_path = test_env.env_root().join("ws5");
92 let ws6_path = test_env.env_root().join("ws6");
93
94 test_env
95 .run_jj_in(&ws1_path, ["sparse", "set", "--clear", "--add=foo"])
96 .success();
97 test_env
98 .run_jj_in(&ws1_path, ["workspace", "add", "../ws2"])
99 .success();
100 let output = test_env.run_jj_in(&ws2_path, ["sparse", "list"]);
101 insta::assert_snapshot!(output, @r"
102 foo
103 [EOF]
104 ");
105 test_env
106 .run_jj_in(&ws2_path, ["sparse", "set", "--add=bar"])
107 .success();
108 test_env
109 .run_jj_in(&ws2_path, ["workspace", "add", "../ws3"])
110 .success();
111 let output = test_env.run_jj_in(&ws3_path, ["sparse", "list"]);
112 insta::assert_snapshot!(output, @r"
113 bar
114 foo
115 [EOF]
116 ");
117 // --sparse-patterns behavior
118 test_env
119 .run_jj_in(
120 &ws3_path,
121 ["workspace", "add", "--sparse-patterns=copy", "../ws4"],
122 )
123 .success();
124 let output = test_env.run_jj_in(&ws4_path, ["sparse", "list"]);
125 insta::assert_snapshot!(output, @r"
126 bar
127 foo
128 [EOF]
129 ");
130 test_env
131 .run_jj_in(
132 &ws3_path,
133 ["workspace", "add", "--sparse-patterns=full", "../ws5"],
134 )
135 .success();
136 let output = test_env.run_jj_in(&ws5_path, ["sparse", "list"]);
137 insta::assert_snapshot!(output, @r"
138 .
139 [EOF]
140 ");
141 test_env
142 .run_jj_in(
143 &ws3_path,
144 ["workspace", "add", "--sparse-patterns=empty", "../ws6"],
145 )
146 .success();
147 let output = test_env.run_jj_in(&ws6_path, ["sparse", "list"]);
148 insta::assert_snapshot!(output, @"");
149}
150
151/// Test adding a second workspace while the current workspace is editing a
152/// merge
153#[test]
154fn test_workspaces_add_second_workspace_on_merge() {
155 let test_env = TestEnvironment::default();
156 test_env.run_jj_in(".", ["git", "init", "main"]).success();
157 let main_path = test_env.env_root().join("main");
158
159 test_env
160 .run_jj_in(&main_path, ["describe", "-m=left"])
161 .success();
162 test_env
163 .run_jj_in(&main_path, ["new", "@-", "-m=right"])
164 .success();
165 test_env
166 .run_jj_in(&main_path, ["new", "all:@-+", "-m=merge"])
167 .success();
168
169 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
170 insta::assert_snapshot!(output, @r"
171 default: zsuskuln 35e47bff (empty) merge
172 [EOF]
173 ");
174
175 test_env
176 .run_jj_in(
177 &main_path,
178 ["workspace", "add", "--name", "second", "../secondary"],
179 )
180 .success();
181
182 // The new workspace's working-copy commit shares all parents with the old one.
183 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
184 @ 35e47bff781e default@
185 ├─╮
186 │ │ ○ 7013a493bd09 second@
187 ╭─┬─╯
188 │ ○ 444b77e99d43
189 ○ │ 1694f2ddf8ec
190 ├─╯
191 ◆ 000000000000
192 [EOF]
193 ");
194}
195
196/// Test that --ignore-working-copy is respected
197#[test]
198fn test_workspaces_add_ignore_working_copy() {
199 let test_env = TestEnvironment::default();
200 test_env.run_jj_in(".", ["git", "init", "main"]).success();
201 let main_path = test_env.env_root().join("main");
202
203 // TODO: maybe better to error out early?
204 let output = test_env.run_jj_in(
205 &main_path,
206 ["workspace", "add", "--ignore-working-copy", "../secondary"],
207 );
208 insta::assert_snapshot!(output.normalize_backslash(), @r#"
209 ------- stderr -------
210 Created workspace in "../secondary"
211 Error: This command must be able to update the working copy.
212 Hint: Don't use --ignore-working-copy.
213 [EOF]
214 [exit status: 1]
215 "#);
216}
217
218/// Test that --at-op is respected
219#[test]
220fn test_workspaces_add_at_operation() {
221 let test_env = TestEnvironment::default();
222 test_env.run_jj_in(".", ["git", "init", "main"]).success();
223 let main_path = test_env.env_root().join("main");
224
225 std::fs::write(main_path.join("file1"), "").unwrap();
226 let output = test_env.run_jj_in(&main_path, ["commit", "-m1"]);
227 insta::assert_snapshot!(output, @r"
228 ------- stderr -------
229 Working copy (@) now at: rlvkpnrz 18d8b994 (empty) (no description set)
230 Parent commit (@-) : qpvuntsm 3364a7ed 1
231 [EOF]
232 ");
233
234 std::fs::write(main_path.join("file2"), "").unwrap();
235 let output = test_env.run_jj_in(&main_path, ["commit", "-m2"]);
236 insta::assert_snapshot!(output, @r"
237 ------- stderr -------
238 Working copy (@) now at: kkmpptxz 2e7dc5ab (empty) (no description set)
239 Parent commit (@-) : rlvkpnrz 0dbaa19a 2
240 [EOF]
241 ");
242
243 // --at-op should disable snapshot in the main workspace, but the newly
244 // created workspace should still be writable.
245 std::fs::write(main_path.join("file3"), "").unwrap();
246 let output = test_env.run_jj_in(
247 &main_path,
248 ["workspace", "add", "--at-op=@-", "../secondary"],
249 );
250 insta::assert_snapshot!(output.normalize_backslash(), @r#"
251 ------- stderr -------
252 Created workspace in "../secondary"
253 Working copy (@) now at: rzvqmyuk a4d1cbc9 (empty) (no description set)
254 Parent commit (@-) : qpvuntsm 3364a7ed 1
255 Added 1 files, modified 0 files, removed 0 files
256 [EOF]
257 "#);
258 let secondary_path = test_env.env_root().join("secondary");
259
260 // New snapshot can be taken in the secondary workspace.
261 std::fs::write(secondary_path.join("file4"), "").unwrap();
262 let output = test_env.run_jj_in(&secondary_path, ["status"]);
263 insta::assert_snapshot!(output, @r"
264 Working copy changes:
265 A file4
266 Working copy (@) : rzvqmyuk 2ba74f85 (no description set)
267 Parent commit (@-): qpvuntsm 3364a7ed 1
268 [EOF]
269 ------- stderr -------
270 Concurrent modification detected, resolving automatically.
271 [EOF]
272 ");
273
274 let output = test_env.run_jj_in(&secondary_path, ["op", "log", "-Tdescription"]);
275 insta::assert_snapshot!(output, @r"
276 @ snapshot working copy
277 ○ reconcile divergent operations
278 ├─╮
279 ○ │ commit cd06097124e3e5860867e35c2bb105902c28ea38
280 │ ○ create initial working-copy commit in workspace secondary
281 │ ○ add workspace 'secondary'
282 ├─╯
283 ○ snapshot working copy
284 ○ commit 1c867a0762e30de4591890ea208849f793742c1b
285 ○ snapshot working copy
286 ○ add workspace 'default'
287 ○
288 [EOF]
289 ");
290}
291
292/// Test adding a workspace, but at a specific revision using '-r'
293#[test]
294fn test_workspaces_add_workspace_at_revision() {
295 let test_env = TestEnvironment::default();
296 test_env.run_jj_in(".", ["git", "init", "main"]).success();
297 let main_path = test_env.env_root().join("main");
298 let secondary_path = test_env.env_root().join("secondary");
299
300 std::fs::write(main_path.join("file-1"), "contents").unwrap();
301 test_env
302 .run_jj_in(&main_path, ["commit", "-m", "first"])
303 .success();
304
305 std::fs::write(main_path.join("file-2"), "contents").unwrap();
306 test_env
307 .run_jj_in(&main_path, ["commit", "-m", "second"])
308 .success();
309
310 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
311 insta::assert_snapshot!(output, @r"
312 default: kkmpptxz dadeedb4 (empty) (no description set)
313 [EOF]
314 ");
315
316 let output = test_env.run_jj_in(
317 &main_path,
318 [
319 "workspace",
320 "add",
321 "--name",
322 "second",
323 "../secondary",
324 "-r",
325 "@--",
326 ],
327 );
328 insta::assert_snapshot!(output.normalize_backslash(), @r#"
329 ------- stderr -------
330 Created workspace in "../secondary"
331 Working copy (@) now at: zxsnswpr e374e74a (empty) (no description set)
332 Parent commit (@-) : qpvuntsm f6097c2f first
333 Added 1 files, modified 0 files, removed 0 files
334 [EOF]
335 "#);
336
337 // Can see the working-copy commit in each workspace in the log output. The "@"
338 // node in the graph indicates the current workspace's working-copy commit.
339 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
340 @ dadeedb493e8 default@
341 ○ c420244c6398
342 │ ○ e374e74aa0c8 second@
343 ├─╯
344 ○ f6097c2f7cac
345 ◆ 000000000000
346 [EOF]
347 ");
348 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r"
349 @ e374e74aa0c8 second@
350 │ ○ dadeedb493e8 default@
351 │ ○ c420244c6398
352 ├─╯
353 ○ f6097c2f7cac
354 ◆ 000000000000
355 [EOF]
356 ");
357}
358
359/// Test multiple `-r` flags to `workspace add` to create a workspace
360/// working-copy commit with multiple parents.
361#[test]
362fn test_workspaces_add_workspace_multiple_revisions() {
363 let test_env = TestEnvironment::default();
364 test_env.run_jj_in(".", ["git", "init", "main"]).success();
365 let main_path = test_env.env_root().join("main");
366
367 std::fs::write(main_path.join("file-1"), "contents").unwrap();
368 test_env
369 .run_jj_in(&main_path, ["commit", "-m", "first"])
370 .success();
371 test_env
372 .run_jj_in(&main_path, ["new", "-r", "root()"])
373 .success();
374
375 std::fs::write(main_path.join("file-2"), "contents").unwrap();
376 test_env
377 .run_jj_in(&main_path, ["commit", "-m", "second"])
378 .success();
379 test_env
380 .run_jj_in(&main_path, ["new", "-r", "root()"])
381 .success();
382
383 std::fs::write(main_path.join("file-3"), "contents").unwrap();
384 test_env
385 .run_jj_in(&main_path, ["commit", "-m", "third"])
386 .success();
387 test_env
388 .run_jj_in(&main_path, ["new", "-r", "root()"])
389 .success();
390
391 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
392 @ 5b36783cd11c
393 │ ○ 6c843d62ca29
394 ├─╯
395 │ ○ 544cd61f2d26
396 ├─╯
397 │ ○ f6097c2f7cac
398 ├─╯
399 ◆ 000000000000
400 [EOF]
401 ");
402
403 let output = test_env.run_jj_in(
404 &main_path,
405 [
406 "workspace",
407 "add",
408 "--name=merge",
409 "../merged",
410 "-r=description(third)",
411 "-r=description(second)",
412 "-r=description(first)",
413 ],
414 );
415 insta::assert_snapshot!(output.normalize_backslash(), @r#"
416 ------- stderr -------
417 Created workspace in "../merged"
418 Working copy (@) now at: wmwvqwsz f4fa64f4 (empty) (no description set)
419 Parent commit (@-) : mzvwutvl 6c843d62 third
420 Parent commit (@-) : kkmpptxz 544cd61f second
421 Parent commit (@-) : qpvuntsm f6097c2f first
422 Added 3 files, modified 0 files, removed 0 files
423 [EOF]
424 "#);
425
426 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
427 @ 5b36783cd11c default@
428 │ ○ f4fa64f40944 merge@
429 │ ├─┬─╮
430 │ │ │ ○ f6097c2f7cac
431 ├─────╯
432 │ │ ○ 544cd61f2d26
433 ├───╯
434 │ ○ 6c843d62ca29
435 ├─╯
436 ◆ 000000000000
437 [EOF]
438 ");
439}
440
441#[test]
442fn test_workspaces_add_workspace_from_subdir() {
443 let test_env = TestEnvironment::default();
444 test_env.run_jj_in(".", ["git", "init", "main"]).success();
445 let main_path = test_env.env_root().join("main");
446 let subdir_path = main_path.join("subdir");
447 let secondary_path = test_env.env_root().join("secondary");
448
449 std::fs::create_dir(&subdir_path).unwrap();
450 std::fs::write(subdir_path.join("file"), "contents").unwrap();
451 test_env
452 .run_jj_in(&main_path, ["commit", "-m", "initial"])
453 .success();
454
455 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
456 insta::assert_snapshot!(output, @r"
457 default: rlvkpnrz e1038e77 (empty) (no description set)
458 [EOF]
459 ");
460
461 // Create workspace while in sub-directory of current workspace
462 let output = test_env.run_jj_in(&subdir_path, ["workspace", "add", "../../secondary"]);
463 insta::assert_snapshot!(output.normalize_backslash(), @r#"
464 ------- stderr -------
465 Created workspace in "../../secondary"
466 Working copy (@) now at: rzvqmyuk 7ad84461 (empty) (no description set)
467 Parent commit (@-) : qpvuntsm a3a43d9e initial
468 Added 1 files, modified 0 files, removed 0 files
469 [EOF]
470 "#);
471
472 // Both workspaces show up when we list them
473 let output = test_env.run_jj_in(&secondary_path, ["workspace", "list"]);
474 insta::assert_snapshot!(output, @r"
475 default: rlvkpnrz e1038e77 (empty) (no description set)
476 secondary: rzvqmyuk 7ad84461 (empty) (no description set)
477 [EOF]
478 ");
479}
480
481#[test]
482fn test_workspaces_add_workspace_in_current_workspace() {
483 let test_env = TestEnvironment::default();
484 test_env.run_jj_in(".", ["git", "init", "main"]).success();
485 let main_path = test_env.env_root().join("main");
486
487 std::fs::write(main_path.join("file"), "contents").unwrap();
488 test_env
489 .run_jj_in(&main_path, ["commit", "-m", "initial"])
490 .success();
491
492 // Try to create workspace using name instead of path
493 let output = test_env.run_jj_in(&main_path, ["workspace", "add", "secondary"]);
494 insta::assert_snapshot!(output.normalize_backslash(), @r#"
495 ------- stderr -------
496 Created workspace in "secondary"
497 Warning: Workspace created inside current directory. If this was unintentional, delete the "secondary" directory and run `jj workspace forget secondary` to remove it.
498 Working copy (@) now at: pmmvwywv 0a77a39d (empty) (no description set)
499 Parent commit (@-) : qpvuntsm 751b12b7 initial
500 Added 1 files, modified 0 files, removed 0 files
501 [EOF]
502 "#);
503
504 // Workspace created despite warning
505 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
506 insta::assert_snapshot!(output, @r"
507 default: rlvkpnrz 46d9ba8b (no description set)
508 secondary: pmmvwywv 0a77a39d (empty) (no description set)
509 [EOF]
510 ");
511
512 // Use explicit path instead (no warning)
513 let output = test_env.run_jj_in(&main_path, ["workspace", "add", "./third"]);
514 insta::assert_snapshot!(output.normalize_backslash(), @r#"
515 ------- stderr -------
516 Created workspace in "third"
517 Working copy (@) now at: zxsnswpr 64746d4b (empty) (no description set)
518 Parent commit (@-) : qpvuntsm 751b12b7 initial
519 Added 1 files, modified 0 files, removed 0 files
520 [EOF]
521 "#);
522
523 // Both workspaces created
524 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
525 insta::assert_snapshot!(output, @r"
526 default: rlvkpnrz 477c647f (no description set)
527 secondary: pmmvwywv 0a77a39d (empty) (no description set)
528 third: zxsnswpr 64746d4b (empty) (no description set)
529 [EOF]
530 ");
531
532 // Can see files from the other workspaces in main workspace, since they are
533 // child directories and will therefore be snapshotted
534 let output = test_env.run_jj_in(&main_path, ["file", "list"]);
535 insta::assert_snapshot!(output.normalize_backslash(), @r"
536 file
537 secondary/file
538 third/file
539 [EOF]
540 ");
541}
542
543/// Test making changes to the working copy in a workspace as it gets rewritten
544/// from another workspace
545#[test]
546fn test_workspaces_conflicting_edits() {
547 let test_env = TestEnvironment::default();
548 test_env.run_jj_in(".", ["git", "init", "main"]).success();
549 let main_path = test_env.env_root().join("main");
550 let secondary_path = test_env.env_root().join("secondary");
551
552 std::fs::write(main_path.join("file"), "contents\n").unwrap();
553 test_env.run_jj_in(&main_path, ["new"]).success();
554
555 test_env
556 .run_jj_in(&main_path, ["workspace", "add", "../secondary"])
557 .success();
558
559 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
560 @ 06b57f44a3ca default@
561 │ ○ 3224de8ae048 secondary@
562 ├─╯
563 ○ 506f4ec3c2c6
564 ◆ 000000000000
565 [EOF]
566 ");
567
568 // Make changes in both working copies
569 std::fs::write(main_path.join("file"), "changed in main\n").unwrap();
570 std::fs::write(secondary_path.join("file"), "changed in second\n").unwrap();
571 // Squash the changes from the main workspace into the initial commit (before
572 // running any command in the secondary workspace
573 let output = test_env.run_jj_in(&main_path, ["squash"]);
574 insta::assert_snapshot!(output, @r"
575 ------- stderr -------
576 Rebased 1 descendant commits
577 Working copy (@) now at: mzvwutvl a58c9a9b (empty) (no description set)
578 Parent commit (@-) : qpvuntsm d4124476 (no description set)
579 [EOF]
580 ");
581
582 // The secondary workspace's working-copy commit was updated
583 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
584 @ a58c9a9b19ce default@
585 │ ○ e82cd4ee8faa secondary@
586 ├─╯
587 ○ d41244767d45
588 ◆ 000000000000
589 [EOF]
590 ");
591 let output = test_env.run_jj_in(&secondary_path, ["st"]);
592 insta::assert_snapshot!(output, @r"
593 ------- stderr -------
594 Error: The working copy is stale (not updated since operation c81af45155a2).
595 Hint: Run `jj workspace update-stale` to update it.
596 See https://jj-vcs.github.io/jj/latest/working-copy/#stale-working-copy for more information.
597 [EOF]
598 [exit status: 1]
599 ");
600 // Same error on second run, and from another command
601 let output = test_env.run_jj_in(&secondary_path, ["log"]);
602 insta::assert_snapshot!(output, @r"
603 ------- stderr -------
604 Error: The working copy is stale (not updated since operation c81af45155a2).
605 Hint: Run `jj workspace update-stale` to update it.
606 See https://jj-vcs.github.io/jj/latest/working-copy/#stale-working-copy for more information.
607 [EOF]
608 [exit status: 1]
609 ");
610 // It was detected that the working copy is now stale.
611 // Since there was an uncommitted change in the working copy, it should
612 // have been committed first (causing divergence)
613 let output = test_env.run_jj_in(&secondary_path, ["workspace", "update-stale"]);
614 insta::assert_snapshot!(output, @r"
615 ------- stderr -------
616 Concurrent modification detected, resolving automatically.
617 Rebased 1 descendant commits onto commits rewritten by other operation
618 Working copy (@) now at: pmmvwywv?? e82cd4ee (empty) (no description set)
619 Parent commit (@-) : qpvuntsm d4124476 (no description set)
620 Added 0 files, modified 1 files, removed 0 files
621 Updated working copy to fresh commit e82cd4ee8faa
622 [EOF]
623 ");
624 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path),
625 @r"
626 @ e82cd4ee8faa secondary@ (divergent)
627 │ × 30816012e0da (divergent)
628 ├─╯
629 │ ○ a58c9a9b19ce default@
630 ├─╯
631 ○ d41244767d45
632 ◆ 000000000000
633 [EOF]
634 ");
635 // The stale working copy should have been resolved by the previous command
636 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r"
637 @ e82cd4ee8faa secondary@ (divergent)
638 │ × 30816012e0da (divergent)
639 ├─╯
640 │ ○ a58c9a9b19ce default@
641 ├─╯
642 ○ d41244767d45
643 ◆ 000000000000
644 [EOF]
645 ");
646}
647
648/// Test a clean working copy that gets rewritten from another workspace
649#[test]
650fn test_workspaces_updated_by_other() {
651 let test_env = TestEnvironment::default();
652 test_env.run_jj_in(".", ["git", "init", "main"]).success();
653 let main_path = test_env.env_root().join("main");
654 let secondary_path = test_env.env_root().join("secondary");
655
656 std::fs::write(main_path.join("file"), "contents\n").unwrap();
657 test_env.run_jj_in(&main_path, ["new"]).success();
658
659 test_env
660 .run_jj_in(&main_path, ["workspace", "add", "../secondary"])
661 .success();
662
663 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
664 @ 06b57f44a3ca default@
665 │ ○ 3224de8ae048 secondary@
666 ├─╯
667 ○ 506f4ec3c2c6
668 ◆ 000000000000
669 [EOF]
670 ");
671
672 // Rewrite the check-out commit in one workspace.
673 std::fs::write(main_path.join("file"), "changed in main\n").unwrap();
674 let output = test_env.run_jj_in(&main_path, ["squash"]);
675 insta::assert_snapshot!(output, @r"
676 ------- stderr -------
677 Rebased 1 descendant commits
678 Working copy (@) now at: mzvwutvl a58c9a9b (empty) (no description set)
679 Parent commit (@-) : qpvuntsm d4124476 (no description set)
680 [EOF]
681 ");
682
683 // The secondary workspace's working-copy commit was updated.
684 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
685 @ a58c9a9b19ce default@
686 │ ○ e82cd4ee8faa secondary@
687 ├─╯
688 ○ d41244767d45
689 ◆ 000000000000
690 [EOF]
691 ");
692 let output = test_env.run_jj_in(&secondary_path, ["st"]);
693 insta::assert_snapshot!(output, @r"
694 ------- stderr -------
695 Error: The working copy is stale (not updated since operation c81af45155a2).
696 Hint: Run `jj workspace update-stale` to update it.
697 See https://jj-vcs.github.io/jj/latest/working-copy/#stale-working-copy for more information.
698 [EOF]
699 [exit status: 1]
700 ");
701 // It was detected that the working copy is now stale, but clean. So no
702 // divergent commit should be created.
703 let output = test_env.run_jj_in(&secondary_path, ["workspace", "update-stale"]);
704 insta::assert_snapshot!(output, @r"
705 ------- stderr -------
706 Working copy (@) now at: pmmvwywv e82cd4ee (empty) (no description set)
707 Parent commit (@-) : qpvuntsm d4124476 (no description set)
708 Added 0 files, modified 1 files, removed 0 files
709 Updated working copy to fresh commit e82cd4ee8faa
710 [EOF]
711 ");
712 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path),
713 @r"
714 @ e82cd4ee8faa secondary@
715 │ ○ a58c9a9b19ce default@
716 ├─╯
717 ○ d41244767d45
718 ◆ 000000000000
719 [EOF]
720 ");
721}
722
723/// Test a clean working copy that gets rewritten from another workspace
724#[test]
725fn test_workspaces_updated_by_other_automatic() {
726 let test_env = TestEnvironment::default();
727 test_env.add_config("[snapshot]\nauto-update-stale = true\n");
728
729 test_env.run_jj_in(".", ["git", "init", "main"]).success();
730 let main_path = test_env.env_root().join("main");
731 let secondary_path = test_env.env_root().join("secondary");
732
733 std::fs::write(main_path.join("file"), "contents\n").unwrap();
734 test_env.run_jj_in(&main_path, ["new"]).success();
735
736 test_env
737 .run_jj_in(&main_path, ["workspace", "add", "../secondary"])
738 .success();
739
740 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
741 @ 06b57f44a3ca default@
742 │ ○ 3224de8ae048 secondary@
743 ├─╯
744 ○ 506f4ec3c2c6
745 ◆ 000000000000
746 [EOF]
747 ");
748
749 // Rewrite the check-out commit in one workspace.
750 std::fs::write(main_path.join("file"), "changed in main\n").unwrap();
751 let output = test_env.run_jj_in(&main_path, ["squash"]);
752 insta::assert_snapshot!(output, @r"
753 ------- stderr -------
754 Rebased 1 descendant commits
755 Working copy (@) now at: mzvwutvl a58c9a9b (empty) (no description set)
756 Parent commit (@-) : qpvuntsm d4124476 (no description set)
757 [EOF]
758 ");
759
760 // The secondary workspace's working-copy commit was updated.
761 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
762 @ a58c9a9b19ce default@
763 │ ○ e82cd4ee8faa secondary@
764 ├─╯
765 ○ d41244767d45
766 ◆ 000000000000
767 [EOF]
768 ");
769
770 // The first working copy gets automatically updated.
771 let output = test_env.run_jj_in(&secondary_path, ["st"]);
772 insta::assert_snapshot!(output, @r"
773 The working copy has no changes.
774 Working copy (@) : pmmvwywv e82cd4ee (empty) (no description set)
775 Parent commit (@-): qpvuntsm d4124476 (no description set)
776 [EOF]
777 ------- stderr -------
778 Working copy (@) now at: pmmvwywv e82cd4ee (empty) (no description set)
779 Parent commit (@-) : qpvuntsm d4124476 (no description set)
780 Added 0 files, modified 1 files, removed 0 files
781 Updated working copy to fresh commit e82cd4ee8faa
782 [EOF]
783 ");
784
785 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path),
786 @r"
787 @ e82cd4ee8faa secondary@
788 │ ○ a58c9a9b19ce default@
789 ├─╯
790 ○ d41244767d45
791 ◆ 000000000000
792 [EOF]
793 ");
794}
795
796#[test_case(false; "manual")]
797#[test_case(true; "automatic")]
798fn test_workspaces_current_op_discarded_by_other(automatic: bool) {
799 let test_env = TestEnvironment::default();
800 if automatic {
801 test_env.add_config("[snapshot]\nauto-update-stale = true\n");
802 }
803
804 test_env.run_jj_in(".", ["git", "init", "main"]).success();
805 let main_path = test_env.env_root().join("main");
806 let secondary_path = test_env.env_root().join("secondary");
807
808 std::fs::write(main_path.join("modified"), "base\n").unwrap();
809 std::fs::write(main_path.join("deleted"), "base\n").unwrap();
810 std::fs::write(main_path.join("sparse"), "base\n").unwrap();
811 test_env.run_jj_in(&main_path, ["new"]).success();
812 std::fs::write(main_path.join("modified"), "main\n").unwrap();
813 test_env.run_jj_in(&main_path, ["new"]).success();
814
815 test_env
816 .run_jj_in(&main_path, ["workspace", "add", "../secondary"])
817 .success();
818 // Make unsnapshotted writes in the secondary working copy
819 test_env
820 .run_jj_in(
821 &secondary_path,
822 [
823 "sparse",
824 "set",
825 "--clear",
826 "--add=modified",
827 "--add=deleted",
828 "--add=added",
829 ],
830 )
831 .success();
832 std::fs::write(secondary_path.join("modified"), "secondary\n").unwrap();
833 std::fs::remove_file(secondary_path.join("deleted")).unwrap();
834 std::fs::write(secondary_path.join("added"), "secondary\n").unwrap();
835
836 // Create an op by abandoning the parent commit. Importantly, that commit also
837 // changes the target tree in the secondary workspace.
838 test_env.run_jj_in(&main_path, ["abandon", "@-"]).success();
839
840 let output = test_env.run_jj_in(
841 &main_path,
842 [
843 "operation",
844 "log",
845 "--template",
846 r#"id.short(10) ++ " " ++ description"#,
847 ],
848 );
849 insta::allow_duplicates! {
850 insta::assert_snapshot!(output, @r"
851 @ 64d9b429d9 abandon commit dc638a7f20571df2c846c84d1469b9fcd0edafc0
852 ○ 129f2dca87 create initial working-copy commit in workspace secondary
853 ○ 1516a7f851 add workspace 'secondary'
854 ○ 19bf99b2b1 new empty commit
855 ○ 38c9c18632 snapshot working copy
856 ○ 5e4f01399f new empty commit
857 ○ 299bc7a187 snapshot working copy
858 ○ eac759b9ab add workspace 'default'
859 ○ 0000000000
860 [EOF]
861 ");
862 }
863
864 // Abandon ops, including the one the secondary workspace is currently on.
865 test_env
866 .run_jj_in(&main_path, ["operation", "abandon", "..@-"])
867 .success();
868 test_env
869 .run_jj_in(&main_path, ["util", "gc", "--expire=now"])
870 .success();
871
872 insta::allow_duplicates! {
873 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
874 @ 2d02e07ed190 default@
875 │ ○ 3df3bf89ddf1 secondary@
876 ├─╯
877 ○ e734830954d8
878 ◆ 000000000000
879 [EOF]
880 ");
881 }
882
883 if automatic {
884 // Run a no-op command to set the randomness seed for commit hashes.
885 test_env.run_jj_in(&secondary_path, ["help"]).success();
886
887 let output = test_env.run_jj_in(&secondary_path, ["st"]);
888 insta::assert_snapshot!(output, @r"
889 Working copy changes:
890 C {modified => added}
891 D deleted
892 M modified
893 Working copy (@) : kmkuslsw 0b518140 RECOVERY COMMIT FROM `jj workspace update-stale`
894 Parent commit (@-): rzvqmyuk 3df3bf89 (empty) (no description set)
895 [EOF]
896 ------- stderr -------
897 Failed to read working copy's current operation; attempting recovery. Error message from read attempt: Object 129f2dca870b954e2966fba35893bb47a5bc6358db6e8c4065cee91d2d49073efc3e055b9b81269a13c443d964abb18e83d25de73db2376ff434c876c59976ac of type operation not found
898 Created and checked out recovery commit 8ed0355c5d31
899 [EOF]
900 ");
901 } else {
902 let output = test_env.run_jj_in(&secondary_path, ["st"]);
903 insta::assert_snapshot!(output, @r"
904 ------- stderr -------
905 Error: Could not read working copy's operation.
906 Hint: Run `jj workspace update-stale` to recover.
907 See https://jj-vcs.github.io/jj/latest/working-copy/#stale-working-copy for more information.
908 [EOF]
909 [exit status: 1]
910 ");
911
912 let output = test_env.run_jj_in(&secondary_path, ["workspace", "update-stale"]);
913 insta::assert_snapshot!(output, @r"
914 ------- stderr -------
915 Failed to read working copy's current operation; attempting recovery. Error message from read attempt: Object 129f2dca870b954e2966fba35893bb47a5bc6358db6e8c4065cee91d2d49073efc3e055b9b81269a13c443d964abb18e83d25de73db2376ff434c876c59976ac of type operation not found
916 Created and checked out recovery commit 8ed0355c5d31
917 [EOF]
918 ");
919 }
920
921 insta::allow_duplicates! {
922 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
923 @ 2d02e07ed190 default@
924 │ ○ 0b5181407d03 secondary@
925 │ ○ 3df3bf89ddf1
926 ├─╯
927 ○ e734830954d8
928 ◆ 000000000000
929 [EOF]
930 ");
931 }
932
933 // The sparse patterns should remain
934 let output = test_env.run_jj_in(&secondary_path, ["sparse", "list"]);
935 insta::allow_duplicates! {
936 insta::assert_snapshot!(output, @r"
937 added
938 deleted
939 modified
940 [EOF]
941 ");
942 }
943 let output = test_env.run_jj_in(&secondary_path, ["st"]);
944 insta::allow_duplicates! {
945 insta::assert_snapshot!(output, @r"
946 Working copy changes:
947 C {modified => added}
948 D deleted
949 M modified
950 Working copy (@) : kmkuslsw 0b518140 RECOVERY COMMIT FROM `jj workspace update-stale`
951 Parent commit (@-): rzvqmyuk 3df3bf89 (empty) (no description set)
952 [EOF]
953 ");
954 }
955 insta::allow_duplicates! {
956 // The modified file should have the same contents it had before (not reset to
957 // the base contents)
958 insta::assert_snapshot!(std::fs::read_to_string(secondary_path.join("modified")).unwrap(), @"secondary");
959 }
960
961 let output = test_env.run_jj_in(&secondary_path, ["evolog"]);
962 insta::allow_duplicates! {
963 insta::assert_snapshot!(output, @r"
964 @ kmkuslsw test.user@example.com 2001-02-03 08:05:18 secondary@ 0b518140
965 │ RECOVERY COMMIT FROM `jj workspace update-stale`
966 ○ kmkuslsw hidden test.user@example.com 2001-02-03 08:05:18 8ed0355c
967 (empty) RECOVERY COMMIT FROM `jj workspace update-stale`
968 [EOF]
969 ");
970 }
971}
972
973#[test]
974fn test_workspaces_update_stale_noop() {
975 let test_env = TestEnvironment::default();
976 test_env.run_jj_in(".", ["git", "init", "main"]).success();
977 let main_path = test_env.env_root().join("main");
978
979 let output = test_env.run_jj_in(&main_path, ["workspace", "update-stale"]);
980 insta::assert_snapshot!(output, @r"
981 ------- stderr -------
982 Attempted recovery, but the working copy is not stale
983 [EOF]
984 ");
985
986 let output = test_env.run_jj_in(
987 &main_path,
988 ["workspace", "update-stale", "--ignore-working-copy"],
989 );
990 insta::assert_snapshot!(output, @r"
991 ------- stderr -------
992 Error: This command must be able to update the working copy.
993 Hint: Don't use --ignore-working-copy.
994 [EOF]
995 [exit status: 1]
996 ");
997
998 let output = test_env.run_jj_in(&main_path, ["op", "log", "-Tdescription"]);
999 insta::assert_snapshot!(output, @r"
1000 @ add workspace 'default'
1001 ○
1002 [EOF]
1003 ");
1004}
1005
1006/// Test "update-stale" in a dirty, but not stale working copy.
1007#[test]
1008fn test_workspaces_update_stale_snapshot() {
1009 let test_env = TestEnvironment::default();
1010 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1011 let main_path = test_env.env_root().join("main");
1012 let secondary_path = test_env.env_root().join("secondary");
1013
1014 std::fs::write(main_path.join("file"), "changed in main\n").unwrap();
1015 test_env.run_jj_in(&main_path, ["new"]).success();
1016 test_env
1017 .run_jj_in(&main_path, ["workspace", "add", "../secondary"])
1018 .success();
1019
1020 // Record new operation in one workspace.
1021 test_env.run_jj_in(&main_path, ["new"]).success();
1022
1023 // Snapshot the other working copy, which unfortunately results in concurrent
1024 // operations, but should be resolved cleanly.
1025 std::fs::write(secondary_path.join("file"), "changed in second\n").unwrap();
1026 let output = test_env.run_jj_in(&secondary_path, ["workspace", "update-stale"]);
1027 insta::assert_snapshot!(output, @r"
1028 ------- stderr -------
1029 Concurrent modification detected, resolving automatically.
1030 Attempted recovery, but the working copy is not stale
1031 [EOF]
1032 ");
1033
1034 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r"
1035 @ e672fd8fefac secondary@
1036 │ ○ ea37b073f5ab default@
1037 │ ○ b13c81dedc64
1038 ├─╯
1039 ○ e6e9989f1179
1040 ◆ 000000000000
1041 [EOF]
1042 ");
1043}
1044
1045/// Test forgetting workspaces
1046#[test]
1047fn test_workspaces_forget() {
1048 let test_env = TestEnvironment::default();
1049 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1050 let main_path = test_env.env_root().join("main");
1051
1052 std::fs::write(main_path.join("file"), "contents").unwrap();
1053 test_env.run_jj_in(&main_path, ["new"]).success();
1054
1055 test_env
1056 .run_jj_in(&main_path, ["workspace", "add", "../secondary"])
1057 .success();
1058 let output = test_env.run_jj_in(&main_path, ["workspace", "forget"]);
1059 insta::assert_snapshot!(output, @"");
1060
1061 // When listing workspaces, only the secondary workspace shows up
1062 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1063 insta::assert_snapshot!(output, @r"
1064 secondary: pmmvwywv 18463f43 (empty) (no description set)
1065 [EOF]
1066 ");
1067
1068 // `jj status` tells us that there's no working copy here
1069 let output = test_env.run_jj_in(&main_path, ["st"]);
1070 insta::assert_snapshot!(output, @r"
1071 No working copy
1072 [EOF]
1073 ");
1074
1075 // The old working copy doesn't get an "@" in the log output
1076 // TODO: It seems useful to still have the "secondary@" marker here even though
1077 // there's only one workspace. We should show it when the command is not run
1078 // from that workspace.
1079 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
1080 ○ 18463f438cc9
1081 ○ 4e8f9d2be039
1082 ◆ 000000000000
1083 [EOF]
1084 ");
1085
1086 // Revision "@" cannot be used
1087 let output = test_env.run_jj_in(&main_path, ["log", "-r", "@"]);
1088 insta::assert_snapshot!(output, @r"
1089 ------- stderr -------
1090 Error: Workspace `default` doesn't have a working-copy commit
1091 [EOF]
1092 [exit status: 1]
1093 ");
1094
1095 // Try to add back the workspace
1096 // TODO: We should make this just add it back instead of failing
1097 let output = test_env.run_jj_in(&main_path, ["workspace", "add", "."]);
1098 insta::assert_snapshot!(output, @r"
1099 ------- stderr -------
1100 Error: Workspace already exists
1101 [EOF]
1102 [exit status: 1]
1103 ");
1104
1105 // Add a third workspace...
1106 test_env
1107 .run_jj_in(&main_path, ["workspace", "add", "../third"])
1108 .success();
1109 // ... and then forget it, and the secondary workspace too
1110 let output = test_env.run_jj_in(&main_path, ["workspace", "forget", "secondary", "third"]);
1111 insta::assert_snapshot!(output, @"");
1112 // No workspaces left
1113 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1114 insta::assert_snapshot!(output, @"");
1115}
1116
1117#[test]
1118fn test_workspaces_forget_multi_transaction() {
1119 let test_env = TestEnvironment::default();
1120 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1121 let main_path = test_env.env_root().join("main");
1122
1123 std::fs::write(main_path.join("file"), "contents").unwrap();
1124 test_env.run_jj_in(&main_path, ["new"]).success();
1125
1126 test_env
1127 .run_jj_in(&main_path, ["workspace", "add", "../second"])
1128 .success();
1129 test_env
1130 .run_jj_in(&main_path, ["workspace", "add", "../third"])
1131 .success();
1132
1133 // there should be three workspaces
1134 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1135 insta::assert_snapshot!(output, @r"
1136 default: rlvkpnrz 909d51b1 (empty) (no description set)
1137 second: pmmvwywv 18463f43 (empty) (no description set)
1138 third: rzvqmyuk cc383fa2 (empty) (no description set)
1139 [EOF]
1140 ");
1141
1142 // delete two at once, in a single tx
1143 test_env
1144 .run_jj_in(&main_path, ["workspace", "forget", "second", "third"])
1145 .success();
1146 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1147 insta::assert_snapshot!(output, @r"
1148 default: rlvkpnrz 909d51b1 (empty) (no description set)
1149 [EOF]
1150 ");
1151
1152 // the op log should have multiple workspaces forgotten in a single tx
1153 let output = test_env.run_jj_in(&main_path, ["op", "log", "--limit", "1"]);
1154 insta::assert_snapshot!(output, @r"
1155 @ 60b2b5a71a84 test-username@host.example.com 2001-02-03 04:05:12.000 +07:00 - 2001-02-03 04:05:12.000 +07:00
1156 │ forget workspaces second, third
1157 │ args: jj workspace forget second third
1158 [EOF]
1159 ");
1160
1161 // now, undo, and that should restore both workspaces
1162 test_env.run_jj_in(&main_path, ["op", "undo"]).success();
1163
1164 // finally, there should be three workspaces at the end
1165 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1166 insta::assert_snapshot!(output, @r"
1167 default: rlvkpnrz 909d51b1 (empty) (no description set)
1168 second: pmmvwywv 18463f43 (empty) (no description set)
1169 third: rzvqmyuk cc383fa2 (empty) (no description set)
1170 [EOF]
1171 ");
1172}
1173
1174#[test]
1175fn test_workspaces_forget_abandon_commits() {
1176 let test_env = TestEnvironment::default();
1177 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1178 let main_path = test_env.env_root().join("main");
1179
1180 std::fs::write(main_path.join("file"), "contents").unwrap();
1181
1182 test_env
1183 .run_jj_in(&main_path, ["workspace", "add", "../second"])
1184 .success();
1185 test_env
1186 .run_jj_in(&main_path, ["workspace", "add", "../third"])
1187 .success();
1188 test_env
1189 .run_jj_in(&main_path, ["workspace", "add", "../fourth"])
1190 .success();
1191 let third_path = test_env.env_root().join("third");
1192 test_env
1193 .run_jj_in(&third_path, ["edit", "second@"])
1194 .success();
1195 let fourth_path = test_env.env_root().join("fourth");
1196 test_env
1197 .run_jj_in(&fourth_path, ["edit", "second@"])
1198 .success();
1199
1200 // there should be four workspaces, three of which are at the same empty commit
1201 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1202 insta::assert_snapshot!(output, @r"
1203 default: qpvuntsm 4e8f9d2b (no description set)
1204 fourth: uuqppmxq 57d63245 (empty) (no description set)
1205 second: uuqppmxq 57d63245 (empty) (no description set)
1206 third: uuqppmxq 57d63245 (empty) (no description set)
1207 [EOF]
1208 ");
1209 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
1210 @ 4e8f9d2be039 default@
1211 │ ○ 57d63245a308 fourth@ second@ third@
1212 ├─╯
1213 ◆ 000000000000
1214 [EOF]
1215 ");
1216
1217 // delete the default workspace (should not abandon commit since not empty)
1218 test_env
1219 .run_jj_in(&main_path, ["workspace", "forget", "default"])
1220 .success();
1221 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
1222 ○ 57d63245a308 fourth@ second@ third@
1223 │ ○ 4e8f9d2be039
1224 ├─╯
1225 ◆ 000000000000
1226 [EOF]
1227 ");
1228
1229 // delete the second workspace (should not abandon commit since other workspaces
1230 // still have commit checked out)
1231 test_env
1232 .run_jj_in(&main_path, ["workspace", "forget", "second"])
1233 .success();
1234 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
1235 ○ 57d63245a308 fourth@ third@
1236 │ ○ 4e8f9d2be039
1237 ├─╯
1238 ◆ 000000000000
1239 [EOF]
1240 ");
1241
1242 // delete the last 2 workspaces (commit should be abandoned now even though
1243 // forgotten in same tx)
1244 test_env
1245 .run_jj_in(&main_path, ["workspace", "forget", "third", "fourth"])
1246 .success();
1247 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
1248 ○ 4e8f9d2be039
1249 ◆ 000000000000
1250 [EOF]
1251 ");
1252}
1253
1254/// Test context of commit summary template
1255#[test]
1256fn test_list_workspaces_template() {
1257 let test_env = TestEnvironment::default();
1258 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1259 test_env.add_config(
1260 r#"
1261 templates.commit_summary = """commit_id.short() ++ " " ++ description.first_line() ++
1262 if(current_working_copy, " (current)")"""
1263 "#,
1264 );
1265 let main_path = test_env.env_root().join("main");
1266 let secondary_path = test_env.env_root().join("secondary");
1267
1268 std::fs::write(main_path.join("file"), "contents").unwrap();
1269 test_env
1270 .run_jj_in(&main_path, ["commit", "-m", "initial"])
1271 .success();
1272 test_env
1273 .run_jj_in(
1274 &main_path,
1275 ["workspace", "add", "--name", "second", "../secondary"],
1276 )
1277 .success();
1278
1279 // "current_working_copy" should point to the workspace we operate on
1280 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1281 insta::assert_snapshot!(output, @r"
1282 default: 8183d0fcaa4c (current)
1283 second: 0a77a39d7d6f
1284 [EOF]
1285 ");
1286
1287 let output = test_env.run_jj_in(&secondary_path, ["workspace", "list"]);
1288 insta::assert_snapshot!(output, @r"
1289 default: 8183d0fcaa4c
1290 second: 0a77a39d7d6f (current)
1291 [EOF]
1292 ");
1293}
1294
1295/// Test getting the workspace root from primary and secondary workspaces
1296#[test]
1297fn test_workspaces_root() {
1298 let test_env = TestEnvironment::default();
1299 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1300 let main_path = test_env.env_root().join("main");
1301 let secondary_path = test_env.env_root().join("secondary");
1302
1303 let output = test_env.run_jj_in(&main_path, ["workspace", "root"]);
1304 insta::assert_snapshot!(output, @r"
1305 $TEST_ENV/main
1306 [EOF]
1307 ");
1308 let main_subdir_path = main_path.join("subdir");
1309 std::fs::create_dir(&main_subdir_path).unwrap();
1310 let output = test_env.run_jj_in(&main_subdir_path, ["workspace", "root"]);
1311 insta::assert_snapshot!(output, @r"
1312 $TEST_ENV/main
1313 [EOF]
1314 ");
1315
1316 test_env
1317 .run_jj_in(
1318 &main_path,
1319 ["workspace", "add", "--name", "secondary", "../secondary"],
1320 )
1321 .success();
1322 let output = test_env.run_jj_in(&secondary_path, ["workspace", "root"]);
1323 insta::assert_snapshot!(output, @r"
1324 $TEST_ENV/secondary
1325 [EOF]
1326 ");
1327 let secondary_subdir_path = secondary_path.join("subdir");
1328 std::fs::create_dir(&secondary_subdir_path).unwrap();
1329 let output = test_env.run_jj_in(&secondary_subdir_path, ["workspace", "root"]);
1330 insta::assert_snapshot!(output, @r"
1331 $TEST_ENV/secondary
1332 [EOF]
1333 ");
1334}
1335
1336#[test]
1337fn test_debug_snapshot() {
1338 let test_env = TestEnvironment::default();
1339 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1340 let repo_path = test_env.env_root().join("repo");
1341
1342 std::fs::write(repo_path.join("file"), "contents").unwrap();
1343 test_env
1344 .run_jj_in(&repo_path, ["debug", "snapshot"])
1345 .success();
1346 let output = test_env.run_jj_in(&repo_path, ["op", "log"]);
1347 insta::assert_snapshot!(output, @r"
1348 @ c55ebc67e3db test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
1349 │ snapshot working copy
1350 │ args: jj debug snapshot
1351 ○ eac759b9ab75 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
1352 │ add workspace 'default'
1353 ○ 000000000000 root()
1354 [EOF]
1355 ");
1356 test_env
1357 .run_jj_in(&repo_path, ["describe", "-m", "initial"])
1358 .success();
1359 let output = test_env.run_jj_in(&repo_path, ["op", "log"]);
1360 insta::assert_snapshot!(output, @r"
1361 @ c9a40b951848 test-username@host.example.com 2001-02-03 04:05:10.000 +07:00 - 2001-02-03 04:05:10.000 +07:00
1362 │ describe commit 4e8f9d2be039994f589b4e57ac5e9488703e604d
1363 │ args: jj describe -m initial
1364 ○ c55ebc67e3db test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
1365 │ snapshot working copy
1366 │ args: jj debug snapshot
1367 ○ eac759b9ab75 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
1368 │ add workspace 'default'
1369 ○ 000000000000 root()
1370 [EOF]
1371 ");
1372}
1373
1374#[test]
1375fn test_workspaces_rename_nothing_changed() {
1376 let test_env = TestEnvironment::default();
1377 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1378 let main_path = test_env.env_root().join("main");
1379 let output = test_env.run_jj_in(&main_path, ["workspace", "rename", "default"]);
1380 insta::assert_snapshot!(output, @r"
1381 ------- stderr -------
1382 Nothing changed.
1383 [EOF]
1384 ");
1385}
1386
1387#[test]
1388fn test_workspaces_rename_new_workspace_name_already_used() {
1389 let test_env = TestEnvironment::default();
1390 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1391 let main_path = test_env.env_root().join("main");
1392 test_env
1393 .run_jj_in(
1394 &main_path,
1395 ["workspace", "add", "--name", "second", "../secondary"],
1396 )
1397 .success();
1398 let output = test_env.run_jj_in(&main_path, ["workspace", "rename", "second"]);
1399 insta::assert_snapshot!(output, @r"
1400 ------- stderr -------
1401 Error: Failed to rename a workspace
1402 Caused by: Workspace second already exists
1403 [EOF]
1404 [exit status: 1]
1405 ");
1406}
1407
1408#[test]
1409fn test_workspaces_rename_forgotten_workspace() {
1410 let test_env = TestEnvironment::default();
1411 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1412 let main_path = test_env.env_root().join("main");
1413 test_env
1414 .run_jj_in(
1415 &main_path,
1416 ["workspace", "add", "--name", "second", "../secondary"],
1417 )
1418 .success();
1419 test_env
1420 .run_jj_in(&main_path, ["workspace", "forget", "second"])
1421 .success();
1422 let secondary_path = test_env.env_root().join("secondary");
1423 let output = test_env.run_jj_in(&secondary_path, ["workspace", "rename", "third"]);
1424 insta::assert_snapshot!(output, @r"
1425 ------- stderr -------
1426 Error: The current workspace 'second' is not tracked in the repo.
1427 [EOF]
1428 [exit status: 1]
1429 ");
1430}
1431
1432#[test]
1433fn test_workspaces_rename_workspace() {
1434 let test_env = TestEnvironment::default();
1435 test_env.run_jj_in(".", ["git", "init", "main"]).success();
1436 let main_path = test_env.env_root().join("main");
1437 test_env
1438 .run_jj_in(
1439 &main_path,
1440 ["workspace", "add", "--name", "second", "../secondary"],
1441 )
1442 .success();
1443 let secondary_path = test_env.env_root().join("secondary");
1444
1445 // Both workspaces show up when we list them
1446 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1447 insta::assert_snapshot!(output, @r"
1448 default: qpvuntsm 230dd059 (empty) (no description set)
1449 second: uuqppmxq 57d63245 (empty) (no description set)
1450 [EOF]
1451 ");
1452
1453 let output = test_env.run_jj_in(&secondary_path, ["workspace", "rename", "third"]);
1454 insta::assert_snapshot!(output, @"");
1455
1456 let output = test_env.run_jj_in(&main_path, ["workspace", "list"]);
1457 insta::assert_snapshot!(output, @r"
1458 default: qpvuntsm 230dd059 (empty) (no description set)
1459 third: uuqppmxq 57d63245 (empty) (no description set)
1460 [EOF]
1461 ");
1462
1463 // Can see the working-copy commit in each workspace in the log output.
1464 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r"
1465 @ 230dd059e1b0 default@
1466 │ ○ 57d63245a308 third@
1467 ├─╯
1468 ◆ 000000000000
1469 [EOF]
1470 ");
1471 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r"
1472 @ 57d63245a308 third@
1473 │ ○ 230dd059e1b0 default@
1474 ├─╯
1475 ◆ 000000000000
1476 [EOF]
1477 ");
1478}
1479
1480#[must_use]
1481fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> CommandOutput {
1482 let template = r#"
1483 separate(" ",
1484 commit_id.short(),
1485 working_copies,
1486 if(divergent, "(divergent)"),
1487 )
1488 "#;
1489 test_env.run_jj_in(cwd, ["log", "-T", template, "-r", "all()"])
1490}