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 crate::common::TestEnvironment;
18
19/// Test adding a second workspace
20#[test]
21fn test_workspaces_add_second_workspace() {
22 let test_env = TestEnvironment::default();
23 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
24 let main_path = test_env.env_root().join("main");
25 let secondary_path = test_env.env_root().join("secondary");
26
27 std::fs::write(main_path.join("file"), "contents").unwrap();
28 test_env.jj_cmd_ok(&main_path, &["commit", "-m", "initial"]);
29
30 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
31 insta::assert_snapshot!(stdout, @r###"
32 default: rlvkpnrz e0e6d567 (empty) (no description set)
33 "###);
34
35 let (stdout, stderr) = test_env.jj_cmd_ok(
36 &main_path,
37 &["workspace", "add", "--name", "second", "../secondary"],
38 );
39 insta::assert_snapshot!(stdout.replace('\\', "/"), @"");
40 insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
41 Created workspace in "../secondary"
42 Working copy now at: rzvqmyuk 397eac93 (empty) (no description set)
43 Parent commit : qpvuntsm 7d308bc9 initial
44 Added 1 files, modified 0 files, removed 0 files
45 "###);
46
47 // Can see the working-copy commit in each workspace in the log output. The "@"
48 // node in the graph indicates the current workspace's working-copy commit.
49 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
50 ◉ 397eac932ad3 second@
51 │ @ e0e6d5672858 default@
52 ├─╯
53 ◉ 7d308bc9d934
54 ◉ 000000000000
55 "###);
56 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r###"
57 @ 397eac932ad3 second@
58 │ ◉ e0e6d5672858 default@
59 ├─╯
60 ◉ 7d308bc9d934
61 ◉ 000000000000
62 "###);
63
64 // Both workspaces show up when we list them
65 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
66 insta::assert_snapshot!(stdout, @r###"
67 default: rlvkpnrz e0e6d567 (empty) (no description set)
68 second: rzvqmyuk 397eac93 (empty) (no description set)
69 "###);
70}
71
72/// Test how sparse patterns are inherited
73#[test]
74fn test_workspaces_sparse_patterns() {
75 let test_env = TestEnvironment::default();
76 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "ws1"]);
77 let ws1_path = test_env.env_root().join("ws1");
78 let ws2_path = test_env.env_root().join("ws2");
79 let ws3_path = test_env.env_root().join("ws3");
80
81 test_env.jj_cmd_ok(&ws1_path, &["sparse", "set", "--clear", "--add=foo"]);
82 test_env.jj_cmd_ok(&ws1_path, &["workspace", "add", "../ws2"]);
83 let stdout = test_env.jj_cmd_success(&ws2_path, &["sparse", "list"]);
84 insta::assert_snapshot!(stdout, @r###"
85 foo
86 "###);
87 test_env.jj_cmd_ok(&ws2_path, &["sparse", "set", "--add=bar"]);
88 test_env.jj_cmd_ok(&ws2_path, &["workspace", "add", "../ws3"]);
89 let stdout = test_env.jj_cmd_success(&ws3_path, &["sparse", "list"]);
90 insta::assert_snapshot!(stdout, @r###"
91 bar
92 foo
93 "###);
94}
95
96/// Test adding a second workspace while the current workspace is editing a
97/// merge
98#[test]
99fn test_workspaces_add_second_workspace_on_merge() {
100 let test_env = TestEnvironment::default();
101 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
102 let main_path = test_env.env_root().join("main");
103
104 test_env.jj_cmd_ok(&main_path, &["describe", "-m=left"]);
105 test_env.jj_cmd_ok(&main_path, &["new", "@-", "-m=right"]);
106 test_env.jj_cmd_ok(&main_path, &["new", "all:@-+", "-m=merge"]);
107
108 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
109 insta::assert_snapshot!(stdout, @r###"
110 default: zsuskuln 21a0ea6d (empty) merge
111 "###);
112
113 test_env.jj_cmd_ok(
114 &main_path,
115 &["workspace", "add", "--name", "second", "../secondary"],
116 );
117
118 // The new workspace's working-copy commit shares all parents with the old one.
119 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
120 ◉ 6d4c2b8ab610 second@
121 ├─╮
122 │ │ @ 21a0ea6d1c86 default@
123 ╭─┬─╯
124 │ ◉ 09ba8d9dfa21
125 ◉ │ 1694f2ddf8ec
126 ├─╯
127 ◉ 000000000000
128 "###);
129}
130
131/// Test adding a workspace, but at a specific revision using '-r'
132#[test]
133fn test_workspaces_add_workspace_at_revision() {
134 let test_env = TestEnvironment::default();
135 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
136 let main_path = test_env.env_root().join("main");
137 let secondary_path = test_env.env_root().join("secondary");
138
139 std::fs::write(main_path.join("file-1"), "contents").unwrap();
140 test_env.jj_cmd_ok(&main_path, &["commit", "-m", "first"]);
141
142 std::fs::write(main_path.join("file-2"), "contents").unwrap();
143 test_env.jj_cmd_ok(&main_path, &["commit", "-m", "second"]);
144
145 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
146 insta::assert_snapshot!(stdout, @r###"
147 default: kkmpptxz 2801c219 (empty) (no description set)
148 "###);
149
150 let (_, stderr) = test_env.jj_cmd_ok(
151 &main_path,
152 &[
153 "workspace",
154 "add",
155 "--name",
156 "second",
157 "../secondary",
158 "-r",
159 "@--",
160 ],
161 );
162 insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
163 Created workspace in "../secondary"
164 Working copy now at: zxsnswpr e6baf9d9 (empty) (no description set)
165 Parent commit : qpvuntsm e7d7dbb9 first
166 Added 1 files, modified 0 files, removed 0 files
167 "###);
168
169 // Can see the working-copy commit in each workspace in the log output. The "@"
170 // node in the graph indicates the current workspace's working-copy commit.
171 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
172 ◉ e6baf9d9cfd0 second@
173 │ @ 2801c219094d default@
174 │ ◉ 4ec5df5a189c
175 ├─╯
176 ◉ e7d7dbb91c5a
177 ◉ 000000000000
178 "###);
179 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r###"
180 @ e6baf9d9cfd0 second@
181 │ ◉ 2801c219094d default@
182 │ ◉ 4ec5df5a189c
183 ├─╯
184 ◉ e7d7dbb91c5a
185 ◉ 000000000000
186 "###);
187}
188
189/// Test multiple `-r` flags to `workspace add` to create a workspace
190/// working-copy commit with multiple parents.
191#[test]
192fn test_workspaces_add_workspace_multiple_revisions() {
193 let test_env = TestEnvironment::default();
194 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
195 let main_path = test_env.env_root().join("main");
196
197 std::fs::write(main_path.join("file-1"), "contents").unwrap();
198 test_env.jj_cmd_ok(&main_path, &["commit", "-m", "first"]);
199 test_env.jj_cmd_ok(&main_path, &["new", "-r", "root()"]);
200
201 std::fs::write(main_path.join("file-2"), "contents").unwrap();
202 test_env.jj_cmd_ok(&main_path, &["commit", "-m", "second"]);
203 test_env.jj_cmd_ok(&main_path, &["new", "-r", "root()"]);
204
205 std::fs::write(main_path.join("file-3"), "contents").unwrap();
206 test_env.jj_cmd_ok(&main_path, &["commit", "-m", "third"]);
207 test_env.jj_cmd_ok(&main_path, &["new", "-r", "root()"]);
208
209 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
210 @ 5b36783cd11c
211 │ ◉ 23881f07b53c
212 ├─╯
213 │ ◉ 1f6a15f0af2a
214 ├─╯
215 │ ◉ e7d7dbb91c5a
216 ├─╯
217 ◉ 000000000000
218 "###);
219
220 let (_, stderr) = test_env.jj_cmd_ok(
221 &main_path,
222 &[
223 "workspace",
224 "add",
225 "--name=merge",
226 "../merged",
227 "-r=238",
228 "-r=1f6",
229 "-r=e7d",
230 ],
231 );
232 insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
233 Created workspace in "../merged"
234 Working copy now at: wmwvqwsz fa8fdc28 (empty) (no description set)
235 Parent commit : mzvwutvl 23881f07 third
236 Parent commit : kkmpptxz 1f6a15f0 second
237 Parent commit : qpvuntsm e7d7dbb9 first
238 Added 3 files, modified 0 files, removed 0 files
239 "###);
240
241 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
242 ◉ fa8fdc28af12 merge@
243 ├─┬─╮
244 │ │ ◉ e7d7dbb91c5a
245 │ ◉ │ 1f6a15f0af2a
246 │ ├─╯
247 ◉ │ 23881f07b53c
248 ├─╯
249 │ @ 5b36783cd11c default@
250 ├─╯
251 ◉ 000000000000
252 "###);
253}
254
255/// Test making changes to the working copy in a workspace as it gets rewritten
256/// from another workspace
257#[test]
258fn test_workspaces_conflicting_edits() {
259 let test_env = TestEnvironment::default();
260 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
261 let main_path = test_env.env_root().join("main");
262 let secondary_path = test_env.env_root().join("secondary");
263
264 std::fs::write(main_path.join("file"), "contents\n").unwrap();
265 test_env.jj_cmd_ok(&main_path, &["new"]);
266
267 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../secondary"]);
268
269 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
270 ◉ 265af0cdbcc7 secondary@
271 │ @ 351099fa72cf default@
272 ├─╯
273 ◉ cf911c223d3e
274 ◉ 000000000000
275 "###);
276
277 // Make changes in both working copies
278 std::fs::write(main_path.join("file"), "changed in main\n").unwrap();
279 std::fs::write(secondary_path.join("file"), "changed in second\n").unwrap();
280 // Squash the changes from the main workspace into the initial commit (before
281 // running any command in the secondary workspace
282 let (stdout, stderr) = test_env.jj_cmd_ok(&main_path, &["squash"]);
283 insta::assert_snapshot!(stdout, @"");
284 insta::assert_snapshot!(stderr, @r###"
285 Rebased 1 descendant commits
286 Working copy now at: mzvwutvl fe8f41ed (empty) (no description set)
287 Parent commit : qpvuntsm c0d4a99e (no description set)
288 "###);
289
290 // The secondary workspace's working-copy commit was updated
291 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
292 @ fe8f41ed01d6 default@
293 │ ◉ a1896a17282f secondary@
294 ├─╯
295 ◉ c0d4a99ef98a
296 ◉ 000000000000
297 "###);
298 let stderr = test_env.jj_cmd_failure(&secondary_path, &["st"]);
299 insta::assert_snapshot!(stderr, @r###"
300 Error: The working copy is stale (not updated since operation 58b580b12eee).
301 Hint: Run `jj workspace update-stale` to update it.
302 See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information.
303 "###);
304 // Same error on second run, and from another command
305 let stderr = test_env.jj_cmd_failure(&secondary_path, &["log"]);
306 insta::assert_snapshot!(stderr, @r###"
307 Error: The working copy is stale (not updated since operation 58b580b12eee).
308 Hint: Run `jj workspace update-stale` to update it.
309 See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information.
310 "###);
311 let (stdout, stderr) = test_env.jj_cmd_ok(&secondary_path, &["workspace", "update-stale"]);
312 // It was detected that the working copy is now stale.
313 // Since there was an uncommitted change in the working copy, it should
314 // have been committed first (causing divergence)
315 insta::assert_snapshot!(stdout, @"");
316 insta::assert_snapshot!(stderr, @r###"
317 Concurrent modification detected, resolving automatically.
318 Rebased 1 descendant commits onto commits rewritten by other operation
319 Working copy now at: pmmvwywv?? a1896a17 (empty) (no description set)
320 Added 0 files, modified 1 files, removed 0 files
321 "###);
322 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path),
323 @r###"
324 ◉ 92498bf020d4 (divergent)
325 │ ◉ fe8f41ed01d6 default@
326 ├─╯
327 │ @ a1896a17282f secondary@ (divergent)
328 ├─╯
329 ◉ c0d4a99ef98a
330 ◉ 000000000000
331 "###);
332 // The stale working copy should have been resolved by the previous command
333 let stdout = get_log_output(&test_env, &secondary_path);
334 assert!(!stdout.starts_with("The working copy is stale"));
335 insta::assert_snapshot!(stdout, @r###"
336 ◉ 92498bf020d4 (divergent)
337 │ ◉ fe8f41ed01d6 default@
338 ├─╯
339 │ @ a1896a17282f secondary@ (divergent)
340 ├─╯
341 ◉ c0d4a99ef98a
342 ◉ 000000000000
343 "###);
344}
345
346/// Test a clean working copy that gets rewritten from another workspace
347#[test]
348fn test_workspaces_updated_by_other() {
349 let test_env = TestEnvironment::default();
350 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
351 let main_path = test_env.env_root().join("main");
352 let secondary_path = test_env.env_root().join("secondary");
353
354 std::fs::write(main_path.join("file"), "contents\n").unwrap();
355 test_env.jj_cmd_ok(&main_path, &["new"]);
356
357 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../secondary"]);
358
359 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
360 ◉ 265af0cdbcc7 secondary@
361 │ @ 351099fa72cf default@
362 ├─╯
363 ◉ cf911c223d3e
364 ◉ 000000000000
365 "###);
366
367 // Rewrite the check-out commit in one workspace.
368 std::fs::write(main_path.join("file"), "changed in main\n").unwrap();
369 let (stdout, stderr) = test_env.jj_cmd_ok(&main_path, &["squash"]);
370 insta::assert_snapshot!(stdout, @"");
371 insta::assert_snapshot!(stderr, @r###"
372 Rebased 1 descendant commits
373 Working copy now at: mzvwutvl fe8f41ed (empty) (no description set)
374 Parent commit : qpvuntsm c0d4a99e (no description set)
375 "###);
376
377 // The secondary workspace's working-copy commit was updated.
378 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
379 @ fe8f41ed01d6 default@
380 │ ◉ a1896a17282f secondary@
381 ├─╯
382 ◉ c0d4a99ef98a
383 ◉ 000000000000
384 "###);
385 let stderr = test_env.jj_cmd_failure(&secondary_path, &["st"]);
386 insta::assert_snapshot!(stderr, @r###"
387 Error: The working copy is stale (not updated since operation 58b580b12eee).
388 Hint: Run `jj workspace update-stale` to update it.
389 See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information.
390 "###);
391 let (stdout, stderr) = test_env.jj_cmd_ok(&secondary_path, &["workspace", "update-stale"]);
392 // It was detected that the working copy is now stale, but clean. So no
393 // divergent commit should be created.
394 insta::assert_snapshot!(stdout, @"");
395 insta::assert_snapshot!(stderr, @r###"
396 Working copy now at: pmmvwywv a1896a17 (empty) (no description set)
397 Added 0 files, modified 1 files, removed 0 files
398 "###);
399 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path),
400 @r###"
401 ◉ fe8f41ed01d6 default@
402 │ @ a1896a17282f secondary@
403 ├─╯
404 ◉ c0d4a99ef98a
405 ◉ 000000000000
406 "###);
407}
408
409#[test]
410fn test_workspaces_current_op_discarded_by_other() {
411 let test_env = TestEnvironment::default();
412 // Use the local backend because GitBackend::gc() depends on the git CLI.
413 test_env.jj_cmd_ok(
414 test_env.env_root(),
415 &["init", "main", "--config-toml=ui.allow-init-native=true"],
416 );
417 let main_path = test_env.env_root().join("main");
418 let secondary_path = test_env.env_root().join("secondary");
419
420 std::fs::write(main_path.join("modified"), "base\n").unwrap();
421 std::fs::write(main_path.join("deleted"), "base\n").unwrap();
422 std::fs::write(main_path.join("sparse"), "base\n").unwrap();
423 test_env.jj_cmd_ok(&main_path, &["new"]);
424 std::fs::write(main_path.join("modified"), "main\n").unwrap();
425 test_env.jj_cmd_ok(&main_path, &["new"]);
426
427 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../secondary"]);
428 // Make unsnapshotted writes in the secondary working copy
429 test_env.jj_cmd_ok(
430 &secondary_path,
431 &[
432 "sparse",
433 "set",
434 "--clear",
435 "--add=modified",
436 "--add=deleted",
437 "--add=added",
438 ],
439 );
440 std::fs::write(secondary_path.join("modified"), "secondary\n").unwrap();
441 std::fs::remove_file(secondary_path.join("deleted")).unwrap();
442 std::fs::write(secondary_path.join("added"), "secondary\n").unwrap();
443
444 // Create an op by abandoning the parent commit. Importantly, that commit also
445 // changes the target tree in the secondary workspace.
446 test_env.jj_cmd_ok(&main_path, &["abandon", "@-"]);
447
448 let stdout = test_env.jj_cmd_success(
449 &main_path,
450 &[
451 "operation",
452 "log",
453 "--template",
454 r#"id.short(10) ++ " " ++ description"#,
455 ],
456 );
457 insta::assert_snapshot!(stdout, @r###"
458 @ 716b8d737e abandon commit 8ac26d0060e2be7f3fce2b5ebd2eb0c75053666f6cbc41bee50bb6da463868704a0bcf1ed9848761206d77694a71e3c657e5e250245e342779df1b00f0da9009
459 ◉ bb8aec2a1c Create initial working-copy commit in workspace secondary
460 ◉ af6f39b411 add workspace 'secondary'
461 ◉ 05c14c7e78 new empty commit
462 ◉ 92bb962606 snapshot working copy
463 ◉ 553e0ea3a4 new empty commit
464 ◉ b3755a9026 snapshot working copy
465 ◉ 17dbb2fe40 add workspace 'default'
466 ◉ cecfee9647 initialize repo
467 ◉ 0000000000
468 "###);
469
470 // Abandon ops, including the one the secondary workspace is currently on.
471 test_env.jj_cmd_ok(&main_path, &["operation", "abandon", "..@-"]);
472 test_env.jj_cmd_ok(&main_path, &["util", "gc", "--expire=now"]);
473
474 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
475 ◉ ec4904a30161 secondary@
476 │ @ 74769415363f default@
477 ├─╯
478 ◉ bd711986720f
479 ◉ 000000000000
480 "###);
481
482 let stderr = test_env.jj_cmd_failure(&secondary_path, &["st"]);
483 insta::assert_snapshot!(stderr, @r###"
484 Error: Could not read working copy's operation.
485 Hint: Run `jj workspace update-stale` to recover.
486 See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information.
487 "###);
488
489 let (stdout, stderr) = test_env.jj_cmd_ok(&secondary_path, &["workspace", "update-stale"]);
490 insta::assert_snapshot!(stderr, @r###"
491 Failed to read working copy's current operation; attempting recovery. Error message from read attempt: Object bb8aec2a1ca33ebafdfe8866bc4ad3464dffd25634fde19d1025625880791b141d35753e10737c41b2bc133ab84047312f3021d905bb711960253e7f430100fc of type operation not found
492 Created and checked out recovery commit 30ee0d1fbd7a
493 "###);
494 insta::assert_snapshot!(stdout, @"");
495
496 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
497 ◉ b93a924213f3 secondary@
498 ◉ ec4904a30161
499 │ @ 74769415363f default@
500 ├─╯
501 ◉ bd711986720f
502 ◉ 000000000000
503 "###);
504
505 // The sparse patterns should remain
506 let stdout = test_env.jj_cmd_success(&secondary_path, &["sparse", "list"]);
507 insta::assert_snapshot!(stdout, @r###"
508 added
509 deleted
510 modified
511 "###);
512 let (stdout, stderr) = test_env.jj_cmd_ok(&secondary_path, &["st"]);
513 insta::assert_snapshot!(stderr, @"");
514 insta::assert_snapshot!(stdout, @r###"
515 Working copy changes:
516 A added
517 D deleted
518 M modified
519 Working copy : kmkuslsw b93a9242 (no description set)
520 Parent commit: rzvqmyuk ec4904a3 (empty) (no description set)
521 "###);
522 // The modified file should have the same contents it had before (not reset to
523 // the base contents)
524 insta::assert_snapshot!(std::fs::read_to_string(secondary_path.join("modified")).unwrap(), @r###"
525 secondary
526 "###);
527
528 let (stdout, stderr) = test_env.jj_cmd_ok(&secondary_path, &["obslog"]);
529 insta::assert_snapshot!(stderr, @"");
530 insta::assert_snapshot!(stdout, @r###"
531 @ kmkuslsw test.user@example.com 2001-02-03 08:05:18 secondary@ b93a9242
532 │ (no description set)
533 ◉ kmkuslsw hidden test.user@example.com 2001-02-03 08:05:18 30ee0d1f
534 (empty) (no description set)
535 "###);
536}
537
538#[test]
539fn test_workspaces_update_stale_noop() {
540 let test_env = TestEnvironment::default();
541 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
542 let main_path = test_env.env_root().join("main");
543
544 let (stdout, stderr) = test_env.jj_cmd_ok(&main_path, &["workspace", "update-stale"]);
545 insta::assert_snapshot!(stdout, @"");
546 insta::assert_snapshot!(stderr, @r###"
547 Nothing to do (the working copy is not stale).
548 "###);
549
550 let stderr = test_env.jj_cmd_failure(
551 &main_path,
552 &["workspace", "update-stale", "--ignore-working-copy"],
553 );
554 insta::assert_snapshot!(stderr, @r###"
555 Error: This command must be able to update the working copy.
556 Hint: Don't use --ignore-working-copy.
557 "###);
558
559 let stdout = test_env.jj_cmd_success(&main_path, &["op", "log", "-Tdescription"]);
560 insta::assert_snapshot!(stdout, @r###"
561 @ add workspace 'default'
562 ◉ initialize repo
563 ◉
564 "###);
565}
566
567/// Test "update-stale" in a dirty, but not stale working copy.
568#[test]
569fn test_workspaces_update_stale_snapshot() {
570 let test_env = TestEnvironment::default();
571 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
572 let main_path = test_env.env_root().join("main");
573 let secondary_path = test_env.env_root().join("secondary");
574
575 std::fs::write(main_path.join("file"), "changed in main\n").unwrap();
576 test_env.jj_cmd_ok(&main_path, &["new"]);
577 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../secondary"]);
578
579 // Record new operation in one workspace.
580 test_env.jj_cmd_ok(&main_path, &["new"]);
581
582 // Snapshot the other working copy, which unfortunately results in concurrent
583 // operations, but should be resolved cleanly.
584 std::fs::write(secondary_path.join("file"), "changed in second\n").unwrap();
585 let (stdout, stderr) = test_env.jj_cmd_ok(&secondary_path, &["workspace", "update-stale"]);
586 insta::assert_snapshot!(stdout, @"");
587 insta::assert_snapshot!(stderr, @r###"
588 Concurrent modification detected, resolving automatically.
589 Nothing to do (the working copy is not stale).
590 "###);
591
592 insta::assert_snapshot!(get_log_output(&test_env, &secondary_path), @r###"
593 @ 4976dfa88529 secondary@
594 │ ◉ 8357b22214ba default@
595 │ ◉ 1a769966ed69
596 ├─╯
597 ◉ b4a6c25e7778
598 ◉ 000000000000
599 "###);
600}
601
602/// Test forgetting workspaces
603#[test]
604fn test_workspaces_forget() {
605 let test_env = TestEnvironment::default();
606 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
607 let main_path = test_env.env_root().join("main");
608
609 std::fs::write(main_path.join("file"), "contents").unwrap();
610 test_env.jj_cmd_ok(&main_path, &["new"]);
611
612 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../secondary"]);
613 let (stdout, stderr) = test_env.jj_cmd_ok(&main_path, &["workspace", "forget"]);
614 insta::assert_snapshot!(stdout, @"");
615 insta::assert_snapshot!(stderr, @"");
616
617 // When listing workspaces, only the secondary workspace shows up
618 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
619 insta::assert_snapshot!(stdout, @r###"
620 secondary: pmmvwywv feda1c4e (empty) (no description set)
621 "###);
622
623 // `jj status` tells us that there's no working copy here
624 let (stdout, stderr) = test_env.jj_cmd_ok(&main_path, &["st"]);
625 insta::assert_snapshot!(stdout, @r###"
626 No working copy
627 "###);
628 insta::assert_snapshot!(stderr, @"");
629
630 // The old working copy doesn't get an "@" in the log output
631 // TODO: We should abandon the empty working copy commit
632 // TODO: It seems useful to still have the "secondary@" marker here even though
633 // there's only one workspace. We should show it when the command is not run
634 // from that workspace.
635 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r###"
636 ◉ feda1c4e5ffe
637 │ ◉ e949be04e93e
638 ├─╯
639 ◉ 123ed18e4c4c
640 ◉ 000000000000
641 "###);
642
643 // Revision "@" cannot be used
644 let stderr = test_env.jj_cmd_failure(&main_path, &["log", "-r", "@"]);
645 insta::assert_snapshot!(stderr, @r###"
646 Error: Workspace "default" doesn't have a working copy
647 "###);
648
649 // Try to add back the workspace
650 // TODO: We should make this just add it back instead of failing
651 let stderr = test_env.jj_cmd_failure(&main_path, &["workspace", "add", "."]);
652 insta::assert_snapshot!(stderr, @r###"
653 Error: Workspace already exists
654 "###);
655
656 // Add a third workspace...
657 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../third"]);
658 // ... and then forget it, and the secondary workspace too
659 let (stdout, stderr) =
660 test_env.jj_cmd_ok(&main_path, &["workspace", "forget", "secondary", "third"]);
661 insta::assert_snapshot!(stdout, @"");
662 insta::assert_snapshot!(stderr, @"");
663 // No workspaces left
664 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
665 insta::assert_snapshot!(stdout, @"");
666}
667
668#[test]
669fn test_workspaces_forget_multi_transaction() {
670 let test_env = TestEnvironment::default();
671 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
672 let main_path = test_env.env_root().join("main");
673
674 std::fs::write(main_path.join("file"), "contents").unwrap();
675 test_env.jj_cmd_ok(&main_path, &["new"]);
676
677 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../second"]);
678 test_env.jj_cmd_ok(&main_path, &["workspace", "add", "../third"]);
679
680 // there should be three workspaces
681 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
682 insta::assert_snapshot!(stdout, @r###"
683 default: rlvkpnrz e949be04 (empty) (no description set)
684 second: pmmvwywv feda1c4e (empty) (no description set)
685 third: rzvqmyuk 485853ed (empty) (no description set)
686 "###);
687
688 // delete two at once, in a single tx
689 test_env.jj_cmd_ok(&main_path, &["workspace", "forget", "second", "third"]);
690 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
691 insta::assert_snapshot!(stdout, @r###"
692 default: rlvkpnrz e949be04 (empty) (no description set)
693 "###);
694
695 // the op log should have multiple workspaces forgotten in a single tx
696 let stdout = test_env.jj_cmd_success(&main_path, &["op", "log", "--limit", "1"]);
697 insta::assert_snapshot!(stdout, @r###"
698 @ c28e1481737d test-username@host.example.com 2001-02-03 04:05:12.000 +07:00 - 2001-02-03 04:05:12.000 +07:00
699 │ forget workspaces second, third
700 │ args: jj workspace forget second third
701 "###);
702
703 // now, undo, and that should restore both workspaces
704 test_env.jj_cmd_ok(&main_path, &["op", "undo"]);
705
706 // finally, there should be three workspaces at the end
707 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
708 insta::assert_snapshot!(stdout, @r###"
709 default: rlvkpnrz e949be04 (empty) (no description set)
710 second: pmmvwywv feda1c4e (empty) (no description set)
711 third: rzvqmyuk 485853ed (empty) (no description set)
712 "###);
713}
714
715/// Test context of commit summary template
716#[test]
717fn test_list_workspaces_template() {
718 let test_env = TestEnvironment::default();
719 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
720 test_env.add_config(
721 r#"
722 templates.commit_summary = """commit_id.short() ++ " " ++ description.first_line() ++
723 if(current_working_copy, " (current)")"""
724 "#,
725 );
726 let main_path = test_env.env_root().join("main");
727 let secondary_path = test_env.env_root().join("secondary");
728
729 std::fs::write(main_path.join("file"), "contents").unwrap();
730 test_env.jj_cmd_ok(&main_path, &["commit", "-m", "initial"]);
731 test_env.jj_cmd_ok(
732 &main_path,
733 &["workspace", "add", "--name", "second", "../secondary"],
734 );
735
736 // "current_working_copy" should point to the workspace we operate on
737 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "list"]);
738 insta::assert_snapshot!(stdout, @r###"
739 default: e0e6d5672858 (current)
740 second: f68da2d114f1
741 "###);
742
743 let stdout = test_env.jj_cmd_success(&secondary_path, &["workspace", "list"]);
744 insta::assert_snapshot!(stdout, @r###"
745 default: e0e6d5672858
746 second: f68da2d114f1 (current)
747 "###);
748}
749
750/// Test getting the workspace root from primary and secondary workspaces
751#[test]
752fn test_workspaces_root() {
753 let test_env = TestEnvironment::default();
754 test_env.jj_cmd_ok(test_env.env_root(), &["init", "--git", "main"]);
755 let main_path = test_env.env_root().join("main");
756 let secondary_path = test_env.env_root().join("secondary");
757
758 let stdout = test_env.jj_cmd_success(&main_path, &["workspace", "root"]);
759 insta::assert_snapshot!(stdout, @r###"
760 $TEST_ENV/main
761 "###);
762 let main_subdir_path = main_path.join("subdir");
763 std::fs::create_dir(&main_subdir_path).unwrap();
764 let stdout = test_env.jj_cmd_success(&main_subdir_path, &["workspace", "root"]);
765 insta::assert_snapshot!(stdout, @r###"
766 $TEST_ENV/main
767 "###);
768
769 test_env.jj_cmd_ok(
770 &main_path,
771 &["workspace", "add", "--name", "secondary", "../secondary"],
772 );
773 let stdout = test_env.jj_cmd_success(&secondary_path, &["workspace", "root"]);
774 insta::assert_snapshot!(stdout, @r###"
775 $TEST_ENV/secondary
776 "###);
777 let secondary_subdir_path = secondary_path.join("subdir");
778 std::fs::create_dir(&secondary_subdir_path).unwrap();
779 let stdout = test_env.jj_cmd_success(&secondary_subdir_path, &["workspace", "root"]);
780 insta::assert_snapshot!(stdout, @r###"
781 $TEST_ENV/secondary
782 "###);
783}
784
785fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> String {
786 let template = r#"
787 separate(" ",
788 commit_id.short(),
789 working_copies,
790 if(divergent, "(divergent)"),
791 )
792 "#;
793 test_env.jj_cmd_success(cwd, &["log", "-T", template, "-r", "all()"])
794}