just playing with tangled
at diffedit3 794 lines 30 kB view raw
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 563564 "###); 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}