just playing with tangled
at main 1639 lines 62 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::fmt::Write as _; 16use std::path::Path; 17 18use testutils::git; 19 20use crate::common::CommandOutput; 21use crate::common::TestEnvironment; 22use crate::common::TestWorkDir; 23 24#[test] 25fn test_git_colocated() { 26 let test_env = TestEnvironment::default(); 27 let work_dir = test_env.work_dir("repo"); 28 let git_repo = git::init(work_dir.root()); 29 30 // Create an initial commit in Git 31 let tree_id = git::add_commit( 32 &git_repo, 33 "refs/heads/master", 34 "file", 35 b"contents", 36 "initial", 37 &[], 38 ) 39 .tree_id; 40 git::checkout_tree_index(&git_repo, tree_id); 41 assert_eq!(work_dir.read_file("file"), b"contents"); 42 insta::assert_snapshot!( 43 git_repo.head_id().unwrap().to_string(), 44 @"97358f54806c7cd005ed5ade68a779595efbae7e" 45 ); 46 47 // Import the repo 48 work_dir 49 .run_jj(["git", "init", "--git-repo", "."]) 50 .success(); 51 insta::assert_snapshot!(get_log_output(&work_dir), @r" 52 @ 524826059adc6f74de30f6be8f8eb86715d75b62 53 ○ 97358f54806c7cd005ed5ade68a779595efbae7e master git_head() initial 54 ◆ 0000000000000000000000000000000000000000 55 [EOF] 56 "); 57 insta::assert_snapshot!( 58 git_repo.head_id().unwrap().to_string(), 59 @"97358f54806c7cd005ed5ade68a779595efbae7e" 60 ); 61 62 // Modify the working copy. The working-copy commit should changed, but the Git 63 // HEAD commit should not 64 work_dir.write_file("file", "modified"); 65 insta::assert_snapshot!(get_log_output(&work_dir), @r" 66 @ f40534d1cfee0e0916dcfbc65c31970b3c705269 67 ○ 97358f54806c7cd005ed5ade68a779595efbae7e master git_head() initial 68 ◆ 0000000000000000000000000000000000000000 69 [EOF] 70 "); 71 insta::assert_snapshot!( 72 git_repo.head_id().unwrap().to_string(), 73 @"97358f54806c7cd005ed5ade68a779595efbae7e" 74 ); 75 76 // Create a new change from jj and check that it's reflected in Git 77 work_dir.run_jj(["new"]).success(); 78 insta::assert_snapshot!(get_log_output(&work_dir), @r" 79 @ b369903b66e2dba03f3f6b24433670784f6180d7 80 ○ f40534d1cfee0e0916dcfbc65c31970b3c705269 git_head() 81 ○ 97358f54806c7cd005ed5ade68a779595efbae7e master initial 82 ◆ 0000000000000000000000000000000000000000 83 [EOF] 84 "); 85 assert!(git_repo.head().unwrap().is_detached()); 86 insta::assert_snapshot!( 87 git_repo.head_id().unwrap().to_string(), 88 @"f40534d1cfee0e0916dcfbc65c31970b3c705269" 89 ); 90} 91 92#[test] 93fn test_git_colocated_intent_to_add() { 94 let test_env = TestEnvironment::default(); 95 test_env 96 .run_jj_in(".", ["git", "init", "--colocate", "repo"]) 97 .success(); 98 let work_dir = test_env.work_dir("repo"); 99 100 // A file added directly on top of the root commit should be marked as 101 // intent-to-add 102 work_dir.write_file("file1.txt", "contents"); 103 work_dir.run_jj(["status"]).success(); 104 insta::assert_snapshot!(get_index_state(work_dir.root()), @"Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 file1.txt"); 105 106 // Another new file should be marked as intent-to-add 107 work_dir.run_jj(["new"]).success(); 108 work_dir.write_file("file2.txt", "contents"); 109 work_dir.run_jj(["status"]).success(); 110 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 111 Unconflicted Mode(FILE) 0839b2e9412b ctime=0:0 mtime=0:0 size=0 flags=0 file1.txt 112 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 file2.txt 113 "); 114 115 // After creating a new commit, it should not longer be marked as intent-to-add 116 work_dir.run_jj(["new"]).success(); 117 work_dir.write_file("file2.txt", "contents"); 118 work_dir.run_jj(["status"]).success(); 119 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 120 Unconflicted Mode(FILE) 0839b2e9412b ctime=0:0 mtime=0:0 size=0 flags=0 file1.txt 121 Unconflicted Mode(FILE) 0839b2e9412b ctime=0:0 mtime=0:0 size=0 flags=0 file2.txt 122 "); 123 124 // If we edit an existing commit, new files are marked as intent-to-add 125 work_dir.run_jj(["edit", "@-"]).success(); 126 work_dir.run_jj(["status"]).success(); 127 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 128 Unconflicted Mode(FILE) 0839b2e9412b ctime=0:0 mtime=0:0 size=0 flags=0 file1.txt 129 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 file2.txt 130 "); 131 132 // If we remove the added file, it's removed from the index 133 work_dir.remove_file("file2.txt"); 134 work_dir.run_jj(["status"]).success(); 135 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 136 Unconflicted Mode(FILE) 0839b2e9412b ctime=0:0 mtime=0:0 size=0 flags=0 file1.txt 137 "); 138} 139 140#[test] 141fn test_git_colocated_unborn_bookmark() { 142 let test_env = TestEnvironment::default(); 143 let work_dir = test_env.work_dir("repo"); 144 let git_repo = git::init(work_dir.root()); 145 146 // add a file to an (in memory) index 147 let add_file_to_index = |name: &str, data: &str| { 148 let mut index_manager = git::IndexManager::new(&git_repo); 149 index_manager.add_file(name, data.as_bytes()); 150 index_manager.sync_index(); 151 }; 152 153 // checkout index (i.e., drop the in-memory changes) 154 let checkout_index = || { 155 let mut index = git_repo.open_index().unwrap(); 156 let objects = git_repo.objects.clone(); 157 gix::worktree::state::checkout( 158 &mut index, 159 git_repo.workdir().unwrap(), 160 objects, 161 &gix::progress::Discard, 162 &gix::progress::Discard, 163 &gix::interrupt::IS_INTERRUPTED, 164 gix::worktree::state::checkout::Options::default(), 165 ) 166 .unwrap(); 167 }; 168 169 // Initially, HEAD isn't set. 170 work_dir 171 .run_jj(["git", "init", "--git-repo", "."]) 172 .success(); 173 assert!(git_repo.head().unwrap().is_unborn()); 174 assert_eq!( 175 git_repo.head_name().unwrap().unwrap().as_bstr(), 176 b"refs/heads/master" 177 ); 178 insta::assert_snapshot!(get_log_output(&work_dir), @r" 179 @ e8849ae12c709f2321908879bc724fdb2ab8a781 180 ◆ 0000000000000000000000000000000000000000 181 [EOF] 182 "); 183 184 // Stage some change, and check out root. This shouldn't clobber the HEAD. 185 add_file_to_index("file0", ""); 186 let output = work_dir.run_jj(["new", "root()"]); 187 insta::assert_snapshot!(output, @r" 188 ------- stderr ------- 189 Working copy (@) now at: kkmpptxz 2b17ac71 (empty) (no description set) 190 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 191 Added 0 files, modified 0 files, removed 1 files 192 [EOF] 193 "); 194 assert!(git_repo.head().unwrap().is_unborn()); 195 assert_eq!( 196 git_repo.head_name().unwrap().unwrap().as_bstr(), 197 b"refs/heads/master" 198 ); 199 insta::assert_snapshot!(get_log_output(&work_dir), @r" 200 @ 2b17ac719c7db025e2514f5708d2b0328fc6b268 201 │ ○ 1d68db605e7f3722d6869beab15183f0e41fd45c 202 ├─╯ 203 ◆ 0000000000000000000000000000000000000000 204 [EOF] 205 "); 206 // Staged change shouldn't persist. 207 checkout_index(); 208 insta::assert_snapshot!(work_dir.run_jj(["status"]), @r" 209 The working copy has no changes. 210 Working copy (@) : kkmpptxz 2b17ac71 (empty) (no description set) 211 Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set) 212 [EOF] 213 "); 214 215 // Stage some change, and create new HEAD. This shouldn't move the default 216 // bookmark. 217 add_file_to_index("file1", ""); 218 let output = work_dir.run_jj(["new"]); 219 insta::assert_snapshot!(output, @r" 220 ------- stderr ------- 221 Working copy (@) now at: royxmykx c5b52bf2 (empty) (no description set) 222 Parent commit (@-) : kkmpptxz 54ca7830 (no description set) 223 [EOF] 224 "); 225 assert!(git_repo.head().unwrap().is_detached()); 226 insta::assert_snapshot!( 227 git_repo.head_id().unwrap().to_string(), 228 @"54ca78301ccd2e0da397694ab34160d539a40e86" 229 ); 230 insta::assert_snapshot!(get_log_output(&work_dir), @r" 231 @ c5b52bf20a14ca728cbb2a56b9dffabc266251bd 232 ○ 54ca78301ccd2e0da397694ab34160d539a40e86 git_head() 233 │ ○ 1d68db605e7f3722d6869beab15183f0e41fd45c 234 ├─╯ 235 ◆ 0000000000000000000000000000000000000000 236 [EOF] 237 "); 238 // Staged change shouldn't persist. 239 checkout_index(); 240 insta::assert_snapshot!(work_dir.run_jj(["status"]), @r" 241 The working copy has no changes. 242 Working copy (@) : royxmykx c5b52bf2 (empty) (no description set) 243 Parent commit (@-): kkmpptxz 54ca7830 (no description set) 244 [EOF] 245 "); 246 247 // Assign the default bookmark. The bookmark is no longer "unborn". 248 work_dir 249 .run_jj(["bookmark", "create", "-r@-", "master"]) 250 .success(); 251 252 // Stage some change, and check out root again. This should unset the HEAD. 253 // https://github.com/jj-vcs/jj/issues/1495 254 add_file_to_index("file2", ""); 255 let output = work_dir.run_jj(["new", "root()"]); 256 insta::assert_snapshot!(output, @r" 257 ------- stderr ------- 258 Working copy (@) now at: znkkpsqq 2b2f7cb0 (empty) (no description set) 259 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 260 Added 0 files, modified 0 files, removed 2 files 261 [EOF] 262 "); 263 assert!(git_repo.head().unwrap().is_unborn()); 264 insta::assert_snapshot!(get_log_output(&work_dir), @r" 265 @ 2b2f7cb00d53f5c0675efb09cbe1a826ce1167a4 266 │ ○ 6c3d40f5a3260d762cd52a8ff6d09883c88d8db5 267 │ ○ 54ca78301ccd2e0da397694ab34160d539a40e86 master 268 ├─╯ 269 │ ○ 1d68db605e7f3722d6869beab15183f0e41fd45c 270 ├─╯ 271 ◆ 0000000000000000000000000000000000000000 272 [EOF] 273 "); 274 // Staged change shouldn't persist. 275 checkout_index(); 276 insta::assert_snapshot!(work_dir.run_jj(["status"]), @r" 277 The working copy has no changes. 278 Working copy (@) : znkkpsqq 2b2f7cb0 (empty) (no description set) 279 Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set) 280 [EOF] 281 "); 282 283 // New snapshot and commit can be created after the HEAD got unset. 284 work_dir.write_file("file3", ""); 285 let output = work_dir.run_jj(["new"]); 286 insta::assert_snapshot!(output, @r" 287 ------- stderr ------- 288 Working copy (@) now at: wqnwkozp 4253b9c0 (empty) (no description set) 289 Parent commit (@-) : znkkpsqq b8df84db (no description set) 290 [EOF] 291 "); 292 insta::assert_snapshot!(get_log_output(&work_dir), @r" 293 @ 4253b9c0f70fd5287c2af4e96b779da6066757fd 294 ○ b8df84db65f6a75ace38ceebca6ed8be781ec754 git_head() 295 │ ○ 6c3d40f5a3260d762cd52a8ff6d09883c88d8db5 296 │ ○ 54ca78301ccd2e0da397694ab34160d539a40e86 master 297 ├─╯ 298 │ ○ 1d68db605e7f3722d6869beab15183f0e41fd45c 299 ├─╯ 300 ◆ 0000000000000000000000000000000000000000 301 [EOF] 302 "); 303} 304 305#[test] 306fn test_git_colocated_export_bookmarks_on_snapshot() { 307 // Checks that we export bookmarks that were changed only because the working 308 // copy was snapshotted 309 310 let test_env = TestEnvironment::default(); 311 let work_dir = test_env.work_dir("repo"); 312 let git_repo = git::init(work_dir.root()); 313 work_dir 314 .run_jj(["git", "init", "--git-repo", "."]) 315 .success(); 316 317 // Create bookmark pointing to the initial commit 318 work_dir.write_file("file", "initial"); 319 work_dir 320 .run_jj(["bookmark", "create", "-r@", "foo"]) 321 .success(); 322 insta::assert_snapshot!(get_log_output(&work_dir), @r" 323 @ 82a10a4d9ef783fd68b661f40ce10dd80d599d9e foo 324 ◆ 0000000000000000000000000000000000000000 325 [EOF] 326 "); 327 328 // The bookmark gets updated when we modify the working copy, and it should get 329 // exported to Git without requiring any other changes 330 work_dir.write_file("file", "modified"); 331 insta::assert_snapshot!(get_log_output(&work_dir), @r" 332 @ 00fc09f48ccf5c8b025a0f93b0ec3b0e4294a598 foo 333 ◆ 0000000000000000000000000000000000000000 334 [EOF] 335 "); 336 insta::assert_snapshot!(git_repo 337 .find_reference("refs/heads/foo") 338 .unwrap() 339 .id() 340 .to_string(), @"00fc09f48ccf5c8b025a0f93b0ec3b0e4294a598"); 341} 342 343#[test] 344fn test_git_colocated_rebase_on_import() { 345 let test_env = TestEnvironment::default(); 346 let work_dir = test_env.work_dir("repo"); 347 let git_repo = git::init(work_dir.root()); 348 work_dir 349 .run_jj(["git", "init", "--git-repo", "."]) 350 .success(); 351 352 // Make some changes in jj and check that they're reflected in git 353 work_dir.write_file("file", "contents"); 354 work_dir.run_jj(["commit", "-m", "add a file"]).success(); 355 work_dir.write_file("file", "modified"); 356 work_dir 357 .run_jj(["bookmark", "create", "-r@", "master"]) 358 .success(); 359 work_dir.run_jj(["commit", "-m", "modify a file"]).success(); 360 // TODO: We shouldn't need this command here to trigger an import of the 361 // refs/heads/master we just exported 362 work_dir.run_jj(["st"]).success(); 363 364 // Move `master` backwards, which should result in commit2 getting hidden, 365 // and the working-copy commit rebased. 366 let parent_commit = git_repo 367 .find_reference("refs/heads/master") 368 .unwrap() 369 .peel_to_commit() 370 .unwrap() 371 .parent_ids() 372 .next() 373 .unwrap() 374 .detach(); 375 git_repo 376 .reference( 377 "refs/heads/master", 378 parent_commit, 379 gix::refs::transaction::PreviousValue::Any, 380 "update ref", 381 ) 382 .unwrap(); 383 insta::assert_snapshot!(get_log_output(&work_dir), @r" 384 @ d46583362b91d0e172aec469ea1689995540de81 385 ○ cbd6c887108743a4abb0919305646a6a914a665e master git_head() add a file 386 ◆ 0000000000000000000000000000000000000000 387 [EOF] 388 ------- stderr ------- 389 Abandoned 1 commits that are no longer reachable. 390 Rebased 1 descendant commits off of commits rewritten from git 391 Working copy (@) now at: zsuskuln d4658336 (empty) (no description set) 392 Parent commit (@-) : qpvuntsm cbd6c887 master | add a file 393 Added 0 files, modified 1 files, removed 0 files 394 Done importing changes from the underlying Git repo. 395 [EOF] 396 "); 397} 398 399#[test] 400fn test_git_colocated_bookmarks() { 401 let test_env = TestEnvironment::default(); 402 let work_dir = test_env.work_dir("repo"); 403 let git_repo = git::init(work_dir.root()); 404 work_dir 405 .run_jj(["git", "init", "--git-repo", "."]) 406 .success(); 407 work_dir.run_jj(["new", "-m", "foo"]).success(); 408 work_dir.run_jj(["new", "@-", "-m", "bar"]).success(); 409 insta::assert_snapshot!(get_log_output(&work_dir), @r" 410 @ 95e79774f8e7c785fc36da2b798ecfe0dc864e02 bar 411 │ ○ b51ab2e2c88fe2d38bd7ca6946c4d87f281ce7e2 foo 412 ├─╯ 413 ○ e8849ae12c709f2321908879bc724fdb2ab8a781 git_head() 414 ◆ 0000000000000000000000000000000000000000 415 [EOF] 416 "); 417 418 // Create a bookmark in jj. It should be exported to Git even though it points 419 // to the working- copy commit. 420 work_dir 421 .run_jj(["bookmark", "create", "-r@", "master"]) 422 .success(); 423 insta::assert_snapshot!( 424 git_repo.find_reference("refs/heads/master").unwrap().target().id().to_string(), 425 @"95e79774f8e7c785fc36da2b798ecfe0dc864e02" 426 ); 427 assert!(git_repo.head().unwrap().is_detached()); 428 insta::assert_snapshot!( 429 git_repo.head_id().unwrap().to_string(), 430 @"e8849ae12c709f2321908879bc724fdb2ab8a781" 431 ); 432 433 // Update the bookmark in Git 434 let target_id = work_dir 435 .run_jj(["log", "--no-graph", "-T=commit_id", "-r=description(foo)"]) 436 .success() 437 .stdout 438 .into_raw(); 439 git_repo 440 .reference( 441 "refs/heads/master", 442 gix::ObjectId::from_hex(target_id.as_bytes()).unwrap(), 443 gix::refs::transaction::PreviousValue::Any, 444 "test", 445 ) 446 .unwrap(); 447 insta::assert_snapshot!(get_log_output(&work_dir), @r" 448 @ 507c0edcfc028f714f3c7a3027cb141f6610e867 449 │ ○ b51ab2e2c88fe2d38bd7ca6946c4d87f281ce7e2 master foo 450 ├─╯ 451 ○ e8849ae12c709f2321908879bc724fdb2ab8a781 git_head() 452 ◆ 0000000000000000000000000000000000000000 453 [EOF] 454 ------- stderr ------- 455 Abandoned 1 commits that are no longer reachable. 456 Working copy (@) now at: yqosqzyt 507c0edc (empty) (no description set) 457 Parent commit (@-) : qpvuntsm e8849ae1 (empty) (no description set) 458 Done importing changes from the underlying Git repo. 459 [EOF] 460 "); 461} 462 463#[test] 464fn test_git_colocated_bookmark_forget() { 465 let test_env = TestEnvironment::default(); 466 let work_dir = test_env.work_dir("repo"); 467 git::init(work_dir.root()); 468 work_dir 469 .run_jj(["git", "init", "--git-repo", "."]) 470 .success(); 471 work_dir.run_jj(["new"]).success(); 472 work_dir 473 .run_jj(["bookmark", "create", "-r@", "foo"]) 474 .success(); 475 insta::assert_snapshot!(get_log_output(&work_dir), @r" 476 @ 43444d88b0096888ebfd664c0cf792c9d15e3f14 foo 477 ○ e8849ae12c709f2321908879bc724fdb2ab8a781 git_head() 478 ◆ 0000000000000000000000000000000000000000 479 [EOF] 480 "); 481 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 482 foo: rlvkpnrz 43444d88 (empty) (no description set) 483 @git: rlvkpnrz 43444d88 (empty) (no description set) 484 [EOF] 485 "); 486 487 let output = work_dir.run_jj(["bookmark", "forget", "--include-remotes", "foo"]); 488 insta::assert_snapshot!(output, @r" 489 ------- stderr ------- 490 Forgot 1 local bookmarks. 491 Forgot 1 remote bookmarks. 492 [EOF] 493 "); 494 // A forgotten bookmark is deleted in the git repo. For a detailed demo 495 // explaining this, see `test_bookmark_forget_export` in 496 // `test_bookmark_command.rs`. 497 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 498} 499 500#[test] 501fn test_git_colocated_bookmark_at_root() { 502 let test_env = TestEnvironment::default(); 503 test_env 504 .run_jj_in(".", ["git", "init", "--colocate", "repo"]) 505 .success(); 506 let work_dir = test_env.work_dir("repo"); 507 508 let output = work_dir.run_jj(["bookmark", "create", "foo", "-r=root()"]); 509 insta::assert_snapshot!(output, @r" 510 ------- stderr ------- 511 Created 1 bookmarks pointing to zzzzzzzz 00000000 foo | (empty) (no description set) 512 Warning: Failed to export some bookmarks: 513 foo@git: Ref cannot point to the root commit in Git 514 [EOF] 515 "); 516 517 let output = work_dir.run_jj(["bookmark", "move", "foo", "--to=@"]); 518 insta::assert_snapshot!(output, @r" 519 ------- stderr ------- 520 Moved 1 bookmarks to qpvuntsm e8849ae1 foo | (empty) (no description set) 521 [EOF] 522 "); 523 524 let output = work_dir.run_jj([ 525 "bookmark", 526 "move", 527 "foo", 528 "--allow-backwards", 529 "--to=root()", 530 ]); 531 insta::assert_snapshot!(output, @r" 532 ------- stderr ------- 533 Moved 1 bookmarks to zzzzzzzz 00000000 foo* | (empty) (no description set) 534 Warning: Failed to export some bookmarks: 535 foo@git: Ref cannot point to the root commit in Git 536 [EOF] 537 "); 538} 539 540#[test] 541fn test_git_colocated_conflicting_git_refs() { 542 let test_env = TestEnvironment::default(); 543 let work_dir = test_env.work_dir("repo"); 544 git::init(work_dir.root()); 545 work_dir 546 .run_jj(["git", "init", "--git-repo", "."]) 547 .success(); 548 work_dir 549 .run_jj(["bookmark", "create", "-r@", "main"]) 550 .success(); 551 let output = work_dir.run_jj(["bookmark", "create", "-r@", "main/sub"]); 552 insta::with_settings!({filters => vec![("Failed to set: .*", "Failed to set: ...")]}, { 553 insta::assert_snapshot!(output, @r#" 554 ------- stderr ------- 555 Created 1 bookmarks pointing to qpvuntsm e8849ae1 main main/sub | (empty) (no description set) 556 Warning: Failed to export some bookmarks: 557 main/sub@git: Failed to set: ... 558 Hint: Git doesn't allow a branch name that looks like a parent directory of 559 another (e.g. `foo` and `foo/bar`). Try to rename the bookmarks that failed to 560 export or their "parent" bookmarks. 561 [EOF] 562 "#); 563 }); 564} 565 566#[test] 567fn test_git_colocated_checkout_non_empty_working_copy() { 568 let test_env = TestEnvironment::default(); 569 let work_dir = test_env.work_dir("repo"); 570 let git_repo = git::init(work_dir.root()); 571 work_dir 572 .run_jj(["git", "init", "--git-repo", "."]) 573 .success(); 574 575 // Create an initial commit in Git 576 // We use this to set HEAD to master 577 let tree_id = git::add_commit( 578 &git_repo, 579 "refs/heads/master", 580 "file", 581 b"contents", 582 "initial", 583 &[], 584 ) 585 .tree_id; 586 git::checkout_tree_index(&git_repo, tree_id); 587 assert_eq!(work_dir.read_file("file"), b"contents"); 588 insta::assert_snapshot!( 589 git_repo.head_id().unwrap().to_string(), 590 @"97358f54806c7cd005ed5ade68a779595efbae7e" 591 ); 592 593 work_dir.write_file("two", "y"); 594 595 work_dir.run_jj(["describe", "-m", "two"]).success(); 596 work_dir.run_jj(["new", "@-"]).success(); 597 let output = work_dir.run_jj(["describe", "-m", "new"]); 598 insta::assert_snapshot!(output, @r" 599 ------- stderr ------- 600 Working copy (@) now at: kkmpptxz 986aa548 (empty) new 601 Parent commit (@-) : slsumksp 97358f54 master | initial 602 [EOF] 603 "); 604 605 assert_eq!( 606 git_repo.head_name().unwrap().unwrap().as_bstr(), 607 b"refs/heads/master" 608 ); 609 610 insta::assert_snapshot!(get_log_output(&work_dir), @r" 611 @ 986aa548466ed43b48c059854720e70d8ec2bf71 new 612 │ ○ 6b0f7d59e0749d3a6ff2ecf686d5fa48023b7b93 two 613 ├─╯ 614 ○ 97358f54806c7cd005ed5ade68a779595efbae7e master git_head() initial 615 ◆ 0000000000000000000000000000000000000000 616 [EOF] 617 "); 618} 619 620#[test] 621fn test_git_colocated_fetch_deleted_or_moved_bookmark() { 622 let test_env = TestEnvironment::default(); 623 test_env.add_config("git.auto-local-bookmark = true"); 624 let origin_dir = test_env.work_dir("origin"); 625 git::init(origin_dir.root()); 626 origin_dir.run_jj(["git", "init", "--git-repo=."]).success(); 627 origin_dir.run_jj(["describe", "-m=A"]).success(); 628 origin_dir 629 .run_jj(["bookmark", "create", "-r@", "A"]) 630 .success(); 631 origin_dir.run_jj(["new", "-m=B_to_delete"]).success(); 632 origin_dir 633 .run_jj(["bookmark", "create", "-r@", "B_to_delete"]) 634 .success(); 635 origin_dir.run_jj(["new", "-m=original C", "@-"]).success(); 636 origin_dir 637 .run_jj(["bookmark", "create", "-r@", "C_to_move"]) 638 .success(); 639 640 let clone_dir = test_env.work_dir("clone"); 641 git::clone(clone_dir.root(), origin_dir.root().to_str().unwrap(), None); 642 clone_dir.run_jj(["git", "init", "--git-repo=."]).success(); 643 clone_dir.run_jj(["new", "A"]).success(); 644 insta::assert_snapshot!(get_log_output(&clone_dir), @r" 645 @ 0060713e4c7c46c4ce0d69a43ac16451582eda79 646 │ ○ dd905babf5b4ad4689f2da1350fd4f0ac5568209 C_to_move original C 647 ├─╯ 648 │ ○ b2ea51c027e11c0f2871cce2a52e648e194df771 B_to_delete B_to_delete 649 ├─╯ 650 ◆ 8777db25171cace71ad014598663d5ffc4fae6b1 A git_head() A 651 ◆ 0000000000000000000000000000000000000000 652 [EOF] 653 "); 654 655 origin_dir 656 .run_jj(["bookmark", "delete", "B_to_delete"]) 657 .success(); 658 // Move bookmark C sideways 659 origin_dir 660 .run_jj(["describe", "C_to_move", "-m", "moved C"]) 661 .success(); 662 let output = clone_dir.run_jj(["git", "fetch"]); 663 insta::assert_snapshot!(output, @r" 664 ------- stderr ------- 665 bookmark: B_to_delete@origin [deleted] untracked 666 bookmark: C_to_move@origin [updated] tracked 667 Abandoned 2 commits that are no longer reachable. 668 [EOF] 669 "); 670 // "original C" and "B_to_delete" are abandoned, as the corresponding bookmarks 671 // were deleted or moved on the remote (#864) 672 insta::assert_snapshot!(get_log_output(&clone_dir), @r" 673 @ 0060713e4c7c46c4ce0d69a43ac16451582eda79 674 │ ○ fb297975e4ef98dc057f65b761aed2cdb0386598 C_to_move moved C 675 ├─╯ 676 ◆ 8777db25171cace71ad014598663d5ffc4fae6b1 A git_head() A 677 ◆ 0000000000000000000000000000000000000000 678 [EOF] 679 "); 680} 681 682#[test] 683fn test_git_colocated_rebase_dirty_working_copy() { 684 let test_env = TestEnvironment::default(); 685 let work_dir = test_env.work_dir("repo"); 686 let git_repo = git::init(work_dir.root()); 687 work_dir.run_jj(["git", "init", "--git-repo=."]).success(); 688 689 work_dir.write_file("file", "base"); 690 work_dir.run_jj(["new"]).success(); 691 work_dir.write_file("file", "old"); 692 work_dir 693 .run_jj(["bookmark", "create", "-r@", "feature"]) 694 .success(); 695 696 // Make the working-copy dirty, delete the checked out bookmark. 697 work_dir.write_file("file", "new"); 698 git_repo 699 .find_reference("refs/heads/feature") 700 .unwrap() 701 .delete() 702 .unwrap(); 703 704 // Because the working copy is dirty, the new working-copy commit will be 705 // diverged. Therefore, the feature bookmark has change-delete conflict. 706 let output = work_dir.run_jj(["status"]); 707 insta::assert_snapshot!(output, @r" 708 Working copy changes: 709 M file 710 Working copy (@) : rlvkpnrz e23559e3 feature?? | (no description set) 711 Parent commit (@-): qpvuntsm f99015d7 (no description set) 712 Warning: These bookmarks have conflicts: 713 feature 714 Hint: Use `jj bookmark list` to see details. Use `jj bookmark set <name> -r <rev>` to resolve. 715 [EOF] 716 ------- stderr ------- 717 Warning: Failed to export some bookmarks: 718 feature@git: Modified ref had been deleted in Git 719 Done importing changes from the underlying Git repo. 720 [EOF] 721 "); 722 insta::assert_snapshot!(get_log_output(&work_dir), @r" 723 @ e23559e3bc6f22a5562297696fc357e2c581df77 feature?? 724 ○ f99015d7d9b82a5912ec4d96a18d2a4afbd8dd49 git_head() 725 ◆ 0000000000000000000000000000000000000000 726 [EOF] 727 "); 728 729 // The working-copy content shouldn't be lost. 730 insta::assert_snapshot!(work_dir.read_file("file"), @"new"); 731} 732 733#[test] 734fn test_git_colocated_external_checkout() { 735 let test_env = TestEnvironment::default(); 736 let work_dir = test_env.work_dir("repo"); 737 let git_repo = git::init(work_dir.root()); 738 let git_check_out_ref = |name| { 739 let target = git_repo 740 .find_reference(name) 741 .unwrap() 742 .into_fully_peeled_id() 743 .unwrap() 744 .detach(); 745 git::set_head_to_id(&git_repo, target); 746 }; 747 748 work_dir.run_jj(["git", "init", "--git-repo=."]).success(); 749 work_dir.run_jj(["ci", "-m=A"]).success(); 750 work_dir 751 .run_jj(["bookmark", "create", "-r@-", "master"]) 752 .success(); 753 work_dir.run_jj(["new", "-m=B", "root()"]).success(); 754 work_dir.run_jj(["new"]).success(); 755 756 // Checked out anonymous bookmark 757 insta::assert_snapshot!(get_log_output(&work_dir), @r" 758 @ 6f8612f0e7f6d52efd8a72615796df06f8d64cdc 759 ○ 319eaafc8fd04c763a0683a000bba5452082feb3 git_head() B 760 │ ○ 8777db25171cace71ad014598663d5ffc4fae6b1 master A 761 ├─╯ 762 ◆ 0000000000000000000000000000000000000000 763 [EOF] 764 "); 765 766 // Check out another bookmark by external command 767 git_check_out_ref("refs/heads/master"); 768 769 // The old working-copy commit gets abandoned, but the whole bookmark should not 770 // be abandoned. (#1042) 771 insta::assert_snapshot!(get_log_output(&work_dir), @r" 772 @ 7ceeaaae54c8ac99ad34eeed7fe1e896f535be99 773 ○ 8777db25171cace71ad014598663d5ffc4fae6b1 master git_head() A 774 │ ○ 319eaafc8fd04c763a0683a000bba5452082feb3 B 775 ├─╯ 776 ◆ 0000000000000000000000000000000000000000 777 [EOF] 778 ------- stderr ------- 779 Reset the working copy parent to the new Git HEAD. 780 [EOF] 781 "); 782 783 // Edit non-head commit 784 work_dir.run_jj(["new", "description(B)"]).success(); 785 work_dir.run_jj(["new", "-m=C", "--no-edit"]).success(); 786 insta::assert_snapshot!(get_log_output(&work_dir), @r" 787 ○ 823204bc895aad19d46b895bc510fb3e9d0c97c7 C 788 @ c6abf242550b7c4116d3821b69c79326889aeba0 789 ○ 319eaafc8fd04c763a0683a000bba5452082feb3 git_head() B 790 │ ○ 8777db25171cace71ad014598663d5ffc4fae6b1 master A 791 ├─╯ 792 ◆ 0000000000000000000000000000000000000000 793 [EOF] 794 "); 795 796 // Check out another bookmark by external command 797 git_check_out_ref("refs/heads/master"); 798 799 // The old working-copy commit shouldn't be abandoned. (#3747) 800 insta::assert_snapshot!(get_log_output(&work_dir), @r" 801 @ 277b693c61dcdea59ac26d6982370f78751f6ef5 802 ○ 8777db25171cace71ad014598663d5ffc4fae6b1 master git_head() A 803 │ ○ 823204bc895aad19d46b895bc510fb3e9d0c97c7 C 804 │ ○ c6abf242550b7c4116d3821b69c79326889aeba0 805 │ ○ 319eaafc8fd04c763a0683a000bba5452082feb3 B 806 ├─╯ 807 ◆ 0000000000000000000000000000000000000000 808 [EOF] 809 ------- stderr ------- 810 Reset the working copy parent to the new Git HEAD. 811 [EOF] 812 "); 813} 814 815#[test] 816#[cfg_attr(windows, ignore = "uses POSIX sh")] 817fn test_git_colocated_concurrent_checkout() { 818 let test_env = TestEnvironment::default(); 819 test_env 820 .run_jj_in(".", ["git", "init", "--colocate", "repo"]) 821 .success(); 822 let work_dir = test_env.work_dir("repo"); 823 824 work_dir.run_jj(["new", "-mcommit1"]).success(); 825 work_dir.write_file("file1", ""); 826 work_dir.run_jj(["new", "-mcommit2"]).success(); 827 work_dir.write_file("file2", ""); 828 work_dir.run_jj(["new", "-mcommit3"]).success(); 829 830 // Run "jj commit" and "git checkout" concurrently 831 let output = work_dir.run_jj([ 832 "commit", 833 "--config=ui.editor=['sh', '-c', 'git checkout -q HEAD^']", 834 ]); 835 insta::assert_snapshot!(output, @r#" 836 ------- stderr ------- 837 Warning: Failed to update Git HEAD ref 838 Caused by: The reference "HEAD" should have content dc0b92dfa0af129b2929fa1789fc896b075782b2, actual content was 091e39feb0aba632ab9a9503ceb1dddeac4dd496 839 Working copy (@) now at: mzvwutvl cf0ddbb4 (empty) (no description set) 840 Parent commit (@-) : zsuskuln b6786455 (empty) commit3 841 [EOF] 842 "#); 843 844 // git_head() isn't updated because the export failed 845 insta::assert_snapshot!(work_dir.run_jj(["log", "--summary", "--ignore-working-copy"]), @r" 846 @ mzvwutvl test.user@example.com 2001-02-03 08:05:11 cf0ddbb4 847 │ (empty) (no description set) 848 ○ zsuskuln test.user@example.com 2001-02-03 08:05:11 b6786455 849 │ (empty) commit3 850 ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 git_head() dc0b92df 851 │ commit2 852 │ A file2 853 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 091e39fe 854 │ commit1 855 │ A file1 856 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:07 e8849ae1 857 │ (empty) (no description set) 858 ◆ zzzzzzzz root() 00000000 859 [EOF] 860 "); 861 862 // The current Git HEAD is imported on the next jj invocation 863 insta::assert_snapshot!(work_dir.run_jj(["log", "--summary"]), @r" 864 @ yqosqzyt test.user@example.com 2001-02-03 08:05:13 9529e8f5 865 │ (empty) (no description set) 866 │ ○ zsuskuln test.user@example.com 2001-02-03 08:05:11 b6786455 867 │ │ (empty) commit3 868 │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 dc0b92df 869 ├─╯ commit2 870 │ A file2 871 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 git_head() 091e39fe 872 │ commit1 873 │ A file1 874 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:07 e8849ae1 875 │ (empty) (no description set) 876 ◆ zzzzzzzz root() 00000000 877 [EOF] 878 ------- stderr ------- 879 Reset the working copy parent to the new Git HEAD. 880 [EOF] 881 "); 882} 883 884#[test] 885fn test_git_colocated_squash_undo() { 886 let test_env = TestEnvironment::default(); 887 let work_dir = test_env.work_dir("repo"); 888 git::init(work_dir.root()); 889 work_dir.run_jj(["git", "init", "--git-repo=."]).success(); 890 work_dir.run_jj(["ci", "-m=A"]).success(); 891 // Test the setup 892 insta::assert_snapshot!(get_log_output_divergence(&work_dir), @r" 893 @ rlvkpnrzqnoo 682c866b0a2f 894 ○ qpvuntsmwlqt 8777db25171c A git_head() 895 ◆ zzzzzzzzzzzz 000000000000 896 [EOF] 897 "); 898 899 work_dir.run_jj(["squash"]).success(); 900 insta::assert_snapshot!(get_log_output_divergence(&work_dir), @r" 901 @ zsuskulnrvyr e1c3034f23b9 902 ○ qpvuntsmwlqt ba304e200f4f A git_head() 903 ◆ zzzzzzzzzzzz 000000000000 904 [EOF] 905 "); 906 work_dir.run_jj(["undo"]).success(); 907 // TODO: There should be no divergence here; 2f376ea1478c should be hidden 908 // (#922) 909 insta::assert_snapshot!(get_log_output_divergence(&work_dir), @r" 910 @ rlvkpnrzqnoo 682c866b0a2f 911 ○ qpvuntsmwlqt 8777db25171c A git_head() 912 ◆ zzzzzzzzzzzz 000000000000 913 [EOF] 914 "); 915} 916 917#[test] 918fn test_git_colocated_undo_head_move() { 919 let test_env = TestEnvironment::default(); 920 let work_dir = test_env.work_dir("repo"); 921 let git_repo = git::init(work_dir.root()); 922 work_dir.run_jj(["git", "init", "--git-repo=."]).success(); 923 924 // Create new HEAD 925 work_dir.run_jj(["new"]).success(); 926 assert!(git_repo.head().unwrap().is_detached()); 927 insta::assert_snapshot!( 928 git_repo.head_id().unwrap().to_string(), 929 @"e8849ae12c709f2321908879bc724fdb2ab8a781"); 930 insta::assert_snapshot!(get_log_output(&work_dir), @r" 931 @ 43444d88b0096888ebfd664c0cf792c9d15e3f14 932 ○ e8849ae12c709f2321908879bc724fdb2ab8a781 git_head() 933 ◆ 0000000000000000000000000000000000000000 934 [EOF] 935 "); 936 937 // HEAD should be unset 938 work_dir.run_jj(["undo"]).success(); 939 assert!(git_repo.head().unwrap().is_unborn()); 940 insta::assert_snapshot!(get_log_output(&work_dir), @r" 941 @ e8849ae12c709f2321908879bc724fdb2ab8a781 942 ◆ 0000000000000000000000000000000000000000 943 [EOF] 944 "); 945 946 // Create commit on non-root commit 947 work_dir.run_jj(["new"]).success(); 948 work_dir.run_jj(["new"]).success(); 949 insta::assert_snapshot!(get_log_output(&work_dir), @r" 950 @ 47762194c5b3d9a9280ee7cfd2b9db16158b1b3c 951 ○ e7d0d5fdaf96051d0dacec1e74d9413d64a15822 git_head() 952 ○ e8849ae12c709f2321908879bc724fdb2ab8a781 953 ◆ 0000000000000000000000000000000000000000 954 [EOF] 955 "); 956 assert!(git_repo.head().unwrap().is_detached()); 957 insta::assert_snapshot!( 958 git_repo.head_id().unwrap().to_string(), 959 @"e7d0d5fdaf96051d0dacec1e74d9413d64a15822"); 960 961 // HEAD should be moved back 962 let output = work_dir.run_jj(["undo"]); 963 insta::assert_snapshot!(output, @r" 964 ------- stderr ------- 965 Undid operation: f349e313234e (2001-02-03 08:05:13) new empty commit 966 Working copy (@) now at: royxmykx e7d0d5fd (empty) (no description set) 967 Parent commit (@-) : qpvuntsm e8849ae1 (empty) (no description set) 968 [EOF] 969 "); 970 assert!(git_repo.head().unwrap().is_detached()); 971 insta::assert_snapshot!( 972 git_repo.head_id().unwrap().to_string(), 973 @"e8849ae12c709f2321908879bc724fdb2ab8a781"); 974 insta::assert_snapshot!(get_log_output(&work_dir), @r" 975 @ e7d0d5fdaf96051d0dacec1e74d9413d64a15822 976 ○ e8849ae12c709f2321908879bc724fdb2ab8a781 git_head() 977 ◆ 0000000000000000000000000000000000000000 978 [EOF] 979 "); 980} 981 982#[test] 983fn test_git_colocated_update_index_preserves_timestamps() { 984 let test_env = TestEnvironment::default(); 985 test_env 986 .run_jj_in(".", ["git", "init", "--colocate", "repo"]) 987 .success(); 988 let work_dir = test_env.work_dir("repo"); 989 990 // Create a commit with some files 991 work_dir.write_file("file1.txt", "will be unchanged\n"); 992 work_dir.write_file("file2.txt", "will be modified\n"); 993 work_dir.write_file("file3.txt", "will be deleted\n"); 994 work_dir 995 .run_jj(["bookmark", "create", "-r@", "commit1"]) 996 .success(); 997 work_dir.run_jj(["new"]).success(); 998 999 // Create a commit with some changes to the files 1000 work_dir.write_file("file2.txt", "modified\n"); 1001 work_dir.remove_file("file3.txt"); 1002 work_dir.write_file("file4.txt", "added\n"); 1003 work_dir 1004 .run_jj(["bookmark", "create", "-r@", "commit2"]) 1005 .success(); 1006 work_dir.run_jj(["new"]).success(); 1007 1008 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1009 @ a1886a45815f0dcca5cefcc334d11ffb908a1eb8 1010 ○ 8b0c962ef1fea901fb16f8a484e692a1f0dcbc59 commit2 git_head() 1011 ○ d37eac5eea00fa74a41c1512839711f42aca2c35 commit1 1012 ◆ 0000000000000000000000000000000000000000 1013 [EOF] 1014 "); 1015 1016 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1017 Unconflicted Mode(FILE) ed48318d9bf4 ctime=0:0 mtime=0:0 size=0 flags=0 file1.txt 1018 Unconflicted Mode(FILE) 2e0996000b7e ctime=0:0 mtime=0:0 size=0 flags=0 file2.txt 1019 Unconflicted Mode(FILE) d5f7fc3f74f7 ctime=0:0 mtime=0:0 size=0 flags=0 file4.txt 1020 "); 1021 1022 // Update index with stats for all files. We may want to do this automatically 1023 // in the future after we update the index in `git::reset_head` (#3786), but for 1024 // now, we at least want to preserve existing stat information when possible. 1025 update_git_index(work_dir.root()); 1026 1027 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1028 Unconflicted Mode(FILE) ed48318d9bf4 ctime=[nonzero] mtime=[nonzero] size=18 flags=0 file1.txt 1029 Unconflicted Mode(FILE) 2e0996000b7e ctime=[nonzero] mtime=[nonzero] size=9 flags=0 file2.txt 1030 Unconflicted Mode(FILE) d5f7fc3f74f7 ctime=[nonzero] mtime=[nonzero] size=6 flags=0 file4.txt 1031 "); 1032 1033 // Edit parent commit, causing the changes to be removed from the index without 1034 // touching the working copy 1035 work_dir.run_jj(["edit", "commit2"]).success(); 1036 1037 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1038 @ 8b0c962ef1fea901fb16f8a484e692a1f0dcbc59 commit2 1039 ○ d37eac5eea00fa74a41c1512839711f42aca2c35 commit1 git_head() 1040 ◆ 0000000000000000000000000000000000000000 1041 [EOF] 1042 "); 1043 1044 // Index should contain stat for unchanged file still. 1045 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1046 Unconflicted Mode(FILE) ed48318d9bf4 ctime=[nonzero] mtime=[nonzero] size=18 flags=0 file1.txt 1047 Unconflicted Mode(FILE) 28d2718c947b ctime=0:0 mtime=0:0 size=0 flags=0 file2.txt 1048 Unconflicted Mode(FILE) 528557ab3a42 ctime=0:0 mtime=0:0 size=0 flags=0 file3.txt 1049 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 file4.txt 1050 "); 1051 1052 // Create sibling commit, causing working copy to match index 1053 work_dir.run_jj(["new", "commit1"]).success(); 1054 1055 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1056 @ d9c7f1932e1135856d5905f1a0fc194ce2657065 1057 │ ○ 8b0c962ef1fea901fb16f8a484e692a1f0dcbc59 commit2 1058 ├─╯ 1059 ○ d37eac5eea00fa74a41c1512839711f42aca2c35 commit1 git_head() 1060 ◆ 0000000000000000000000000000000000000000 1061 [EOF] 1062 "); 1063 1064 // Index should contain stat for unchanged file still. 1065 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1066 Unconflicted Mode(FILE) ed48318d9bf4 ctime=[nonzero] mtime=[nonzero] size=18 flags=0 file1.txt 1067 Unconflicted Mode(FILE) 28d2718c947b ctime=0:0 mtime=0:0 size=0 flags=0 file2.txt 1068 Unconflicted Mode(FILE) 528557ab3a42 ctime=0:0 mtime=0:0 size=0 flags=0 file3.txt 1069 "); 1070} 1071 1072#[test] 1073fn test_git_colocated_update_index_merge_conflict() { 1074 let test_env = TestEnvironment::default(); 1075 test_env 1076 .run_jj_in(".", ["git", "init", "--colocate", "repo"]) 1077 .success(); 1078 let work_dir = test_env.work_dir("repo"); 1079 1080 // Set up conflict files 1081 work_dir.write_file("conflict.txt", "base\n"); 1082 work_dir.write_file("base.txt", "base\n"); 1083 work_dir 1084 .run_jj(["bookmark", "create", "-r@", "base"]) 1085 .success(); 1086 1087 work_dir.run_jj(["new", "base"]).success(); 1088 work_dir.write_file("conflict.txt", "left\n"); 1089 work_dir.write_file("left.txt", "left\n"); 1090 work_dir 1091 .run_jj(["bookmark", "create", "-r@", "left"]) 1092 .success(); 1093 1094 work_dir.run_jj(["new", "base"]).success(); 1095 work_dir.write_file("conflict.txt", "right\n"); 1096 work_dir.write_file("right.txt", "right\n"); 1097 work_dir 1098 .run_jj(["bookmark", "create", "-r@", "right"]) 1099 .success(); 1100 1101 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1102 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 base.txt 1103 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1104 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 right.txt 1105 "); 1106 1107 // Update index with stat for base.txt 1108 update_git_index(work_dir.root()); 1109 1110 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1111 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1112 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1113 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 right.txt 1114 "); 1115 1116 // Create merge conflict 1117 work_dir.run_jj(["new", "left", "right"]).success(); 1118 1119 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1120 @ 8b05232ad2cda6f6d06b290486e07251f53c0958 1121 ├─╮ 1122 │ ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right 1123 ○ │ d0f55ffafa1e0e72980202c349af23d093f825be left git_head() 1124 ├─╯ 1125 ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base 1126 ◆ 0000000000000000000000000000000000000000 1127 [EOF] 1128 "); 1129 1130 // Conflict should be added in index with correct blob IDs. The stat for 1131 // base.txt should not change. 1132 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1133 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1134 Base Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=1000 conflict.txt 1135 Ours Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 flags=2000 conflict.txt 1136 Theirs Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=3000 conflict.txt 1137 Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 flags=0 left.txt 1138 Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=0 right.txt 1139 "); 1140 1141 work_dir.run_jj(["new"]).success(); 1142 1143 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1144 @ 7c98aa1e17acd7829c9ccb9eaae705df9b255bd1 1145 × 8b05232ad2cda6f6d06b290486e07251f53c0958 git_head() 1146 ├─╮ 1147 │ ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right 1148 ○ │ d0f55ffafa1e0e72980202c349af23d093f825be left 1149 ├─╯ 1150 ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base 1151 ◆ 0000000000000000000000000000000000000000 1152 [EOF] 1153 "); 1154 1155 // Index should be the same after `jj new`. 1156 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1157 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1158 Base Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=1000 conflict.txt 1159 Ours Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 flags=2000 conflict.txt 1160 Theirs Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=3000 conflict.txt 1161 Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 flags=0 left.txt 1162 Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=0 right.txt 1163 "); 1164} 1165 1166#[test] 1167fn test_git_colocated_update_index_rebase_conflict() { 1168 let test_env = TestEnvironment::default(); 1169 test_env 1170 .run_jj_in(".", ["git", "init", "--colocate", "repo"]) 1171 .success(); 1172 let work_dir = test_env.work_dir("repo"); 1173 1174 // Set up conflict files 1175 work_dir.write_file("conflict.txt", "base\n"); 1176 work_dir.write_file("base.txt", "base\n"); 1177 work_dir 1178 .run_jj(["bookmark", "create", "-r@", "base"]) 1179 .success(); 1180 1181 work_dir.run_jj(["new", "base"]).success(); 1182 work_dir.write_file("conflict.txt", "left\n"); 1183 work_dir.write_file("left.txt", "left\n"); 1184 work_dir 1185 .run_jj(["bookmark", "create", "-r@", "left"]) 1186 .success(); 1187 1188 work_dir.run_jj(["new", "base"]).success(); 1189 work_dir.write_file("conflict.txt", "right\n"); 1190 work_dir.write_file("right.txt", "right\n"); 1191 work_dir 1192 .run_jj(["bookmark", "create", "-r@", "right"]) 1193 .success(); 1194 1195 work_dir.run_jj(["edit", "left"]).success(); 1196 1197 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1198 @ d0f55ffafa1e0e72980202c349af23d093f825be left 1199 │ ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right 1200 ├─╯ 1201 ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base git_head() 1202 ◆ 0000000000000000000000000000000000000000 1203 [EOF] 1204 "); 1205 1206 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1207 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 base.txt 1208 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1209 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 left.txt 1210 "); 1211 1212 // Update index with stat for base.txt 1213 update_git_index(work_dir.root()); 1214 1215 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1216 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1217 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1218 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 left.txt 1219 "); 1220 1221 // Create rebase conflict 1222 work_dir 1223 .run_jj(["rebase", "-r", "left", "-d", "right"]) 1224 .success(); 1225 1226 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1227 @ 535388c5aab1b3a33fdc04a4bf8033de0d1b86ec left 1228 ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right git_head() 1229 ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base 1230 ◆ 0000000000000000000000000000000000000000 1231 [EOF] 1232 "); 1233 1234 // Index should contain files from parent commit, so there should be no conflict 1235 // in conflict.txt yet. The stat for base.txt should not change. 1236 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1237 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1238 Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1239 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 left.txt 1240 Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=0 right.txt 1241 "); 1242 1243 work_dir.run_jj(["new"]).success(); 1244 1245 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1246 @ 04ebd7523ac6107ccdd5bc34600a073b94e43299 1247 × 535388c5aab1b3a33fdc04a4bf8033de0d1b86ec left git_head() 1248 ○ 620e15db9fcd05fff912c52d2cafd36c9e01523c right 1249 ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base 1250 ◆ 0000000000000000000000000000000000000000 1251 [EOF] 1252 "); 1253 1254 // Now the working copy commit's parent is conflicted, so the index should have 1255 // a conflict with correct blob IDs. 1256 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1257 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1258 Base Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=1000 conflict.txt 1259 Ours Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=2000 conflict.txt 1260 Theirs Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 flags=3000 conflict.txt 1261 Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 flags=0 left.txt 1262 Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 flags=0 right.txt 1263 "); 1264} 1265 1266#[test] 1267fn test_git_colocated_update_index_3_sided_conflict() { 1268 let test_env = TestEnvironment::default(); 1269 test_env 1270 .run_jj_in(".", ["git", "init", "--colocate", "repo"]) 1271 .success(); 1272 let work_dir = test_env.work_dir("repo"); 1273 1274 // Set up conflict files 1275 work_dir.write_file("conflict.txt", "base\n"); 1276 work_dir.write_file("base.txt", "base\n"); 1277 work_dir 1278 .run_jj(["bookmark", "create", "-r@", "base"]) 1279 .success(); 1280 1281 work_dir.run_jj(["new", "base"]).success(); 1282 work_dir.write_file("conflict.txt", "side-1\n"); 1283 work_dir.write_file("side-1.txt", "side-1\n"); 1284 work_dir 1285 .run_jj(["bookmark", "create", "-r@", "side-1"]) 1286 .success(); 1287 1288 work_dir.run_jj(["new", "base"]).success(); 1289 work_dir.write_file("conflict.txt", "side-2\n"); 1290 work_dir.write_file("side-2.txt", "side-2\n"); 1291 work_dir 1292 .run_jj(["bookmark", "create", "-r@", "side-2"]) 1293 .success(); 1294 1295 work_dir.run_jj(["new", "base"]).success(); 1296 work_dir.write_file("conflict.txt", "side-3\n"); 1297 work_dir.write_file("side-3.txt", "side-3\n"); 1298 work_dir 1299 .run_jj(["bookmark", "create", "-r@", "side-3"]) 1300 .success(); 1301 1302 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1303 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 base.txt 1304 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1305 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 side-3.txt 1306 "); 1307 1308 // Update index with stat for base.txt 1309 update_git_index(work_dir.root()); 1310 1311 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1312 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1313 Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1314 Unconflicted Mode(FILE) e69de29bb2d1 ctime=0:0 mtime=0:0 size=0 flags=20004000 side-3.txt 1315 "); 1316 1317 // Create 3-sided merge conflict 1318 work_dir 1319 .run_jj(["new", "side-1", "side-2", "side-3"]) 1320 .success(); 1321 1322 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1323 @ 3105daa0d68e3cdc22b2533d7d1b231cd41c76ec 1324 ├─┬─╮ 1325 │ │ ○ 5008c8807feaa955d02e96cb1b0dcf51536fefb8 side-3 1326 │ ○ │ da6e0a03f8b72f6868a9ea33836123fe965c0cb4 side-2 1327 │ ├─╯ 1328 ○ │ ad7eaf61b769dce99884d2ceb0ddf48fc4eac463 side-1 git_head() 1329 ├─╯ 1330 ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base 1331 ◆ 0000000000000000000000000000000000000000 1332 [EOF] 1333 "); 1334 1335 // We can't add conflicts with more than 2 sides to the index, so we add a dummy 1336 // conflict instead. The stat for base.txt should not change. 1337 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1338 Ours Mode(FILE) eb8299123d2a ctime=0:0 mtime=0:0 size=0 flags=2000 .jj-do-not-resolve-this-conflict 1339 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1340 Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1341 Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 flags=0 side-1.txt 1342 Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 flags=0 side-2.txt 1343 Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 flags=0 side-3.txt 1344 "); 1345 1346 work_dir.run_jj(["new"]).success(); 1347 1348 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1349 @ 5b4266a02e8fe9febc6294c7d0a02fc8463221e8 1350 × 3105daa0d68e3cdc22b2533d7d1b231cd41c76ec git_head() 1351 ├─┬─╮ 1352 │ │ ○ 5008c8807feaa955d02e96cb1b0dcf51536fefb8 side-3 1353 │ ○ │ da6e0a03f8b72f6868a9ea33836123fe965c0cb4 side-2 1354 │ ├─╯ 1355 ○ │ ad7eaf61b769dce99884d2ceb0ddf48fc4eac463 side-1 1356 ├─╯ 1357 ○ 1861378a9167e6561bf8ce4a6fef2d7c0897dd87 base 1358 ◆ 0000000000000000000000000000000000000000 1359 [EOF] 1360 "); 1361 1362 // Index should be the same after `jj new`. 1363 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1364 Ours Mode(FILE) eb8299123d2a ctime=0:0 mtime=0:0 size=0 flags=2000 .jj-do-not-resolve-this-conflict 1365 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1366 Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1367 Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 flags=0 side-1.txt 1368 Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 flags=0 side-2.txt 1369 Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 flags=0 side-3.txt 1370 "); 1371 1372 // If we add a file named ".jj-do-not-resolve-this-conflict", it should take 1373 // precedence over the dummy conflict. 1374 work_dir.write_file(".jj-do-not-resolve-this-conflict", "file\n"); 1375 work_dir.run_jj(["new"]).success(); 1376 insta::assert_snapshot!(get_index_state(work_dir.root()), @r" 1377 Unconflicted Mode(FILE) f73f3093ff86 ctime=0:0 mtime=0:0 size=0 flags=0 .jj-do-not-resolve-this-conflict 1378 Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 flags=0 base.txt 1379 Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 flags=0 conflict.txt 1380 Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 flags=0 side-1.txt 1381 Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 flags=0 side-2.txt 1382 Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 flags=0 side-3.txt 1383 "); 1384} 1385 1386#[must_use] 1387fn get_log_output_divergence(work_dir: &TestWorkDir) -> CommandOutput { 1388 let template = r#" 1389 separate(" ", 1390 change_id.short(), 1391 commit_id.short(), 1392 description.first_line(), 1393 bookmarks, 1394 if(git_head, "git_head()"), 1395 if(divergent, "!divergence!"), 1396 ) 1397 "#; 1398 work_dir.run_jj(["log", "-T", template]) 1399} 1400 1401#[must_use] 1402fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput { 1403 let template = r#" 1404 separate(" ", 1405 commit_id, 1406 bookmarks, 1407 if(git_head, "git_head()"), 1408 description, 1409 ) 1410 "#; 1411 work_dir.run_jj(["log", "-T", template, "-r=all()"]) 1412} 1413 1414fn update_git_index(repo_path: &Path) { 1415 let mut iter = git::open(repo_path) 1416 .status(gix::progress::Discard) 1417 .unwrap() 1418 .into_index_worktree_iter(None) 1419 .unwrap(); 1420 1421 // need to explicitly iterate over the changes to recreate the index 1422 1423 for item in iter.by_ref() { 1424 item.unwrap(); 1425 } 1426 1427 iter.outcome_mut() 1428 .unwrap() 1429 .write_changes() 1430 .unwrap() 1431 .unwrap(); 1432} 1433 1434fn get_index_state(repo_path: &Path) -> String { 1435 let git_repo = gix::open(repo_path).expect("git repo should exist"); 1436 let mut buffer = String::new(); 1437 // We can't use the real time from disk, since it would change each time the 1438 // tests are run. Instead, we just show whether it's zero or nonzero. 1439 let format_time = |time: gix::index::entry::stat::Time| { 1440 if time.secs == 0 && time.nsecs == 0 { 1441 "0:0" 1442 } else { 1443 "[nonzero]" 1444 } 1445 }; 1446 let index = git_repo.index_or_empty().unwrap(); 1447 for entry in index.entries() { 1448 writeln!( 1449 &mut buffer, 1450 "{:12} {:?} {} ctime={} mtime={} size={} flags={:x} {}", 1451 format!("{:?}", entry.stage()), 1452 entry.mode, 1453 entry.id.to_hex_with_len(12), 1454 format_time(entry.stat.ctime), 1455 format_time(entry.stat.mtime), 1456 entry.stat.size, 1457 entry.flags.bits(), 1458 entry.path_in(index.path_backing()), 1459 ) 1460 .unwrap(); 1461 } 1462 buffer 1463} 1464 1465#[test] 1466fn test_git_colocated_unreachable_commits() { 1467 let test_env = TestEnvironment::default(); 1468 let work_dir = test_env.work_dir("repo"); 1469 let git_repo = git::init(work_dir.root()); 1470 1471 // Create an initial commit in Git 1472 let commit1 = git::add_commit( 1473 &git_repo, 1474 "refs/heads/master", 1475 "some-file", 1476 b"some content", 1477 "initial", 1478 &[], 1479 ) 1480 .commit_id; 1481 insta::assert_snapshot!( 1482 git_repo.head_id().unwrap().to_string(), 1483 @"cd740e230992f334de13a0bd0b35709b3f7a89af" 1484 ); 1485 1486 // Add a second commit in Git 1487 let commit2 = git::add_commit( 1488 &git_repo, 1489 "refs/heads/dummy", 1490 "next-file", 1491 b"more content", 1492 "next", 1493 &[commit1], 1494 ) 1495 .commit_id; 1496 git_repo 1497 .find_reference("refs/heads/dummy") 1498 .unwrap() 1499 .delete() 1500 .unwrap(); 1501 insta::assert_snapshot!( 1502 git_repo.head_id().unwrap().to_string(), 1503 @"cd740e230992f334de13a0bd0b35709b3f7a89af" 1504 ); 1505 1506 // Import the repo while there is no path to the second commit 1507 work_dir 1508 .run_jj(["git", "init", "--git-repo", "."]) 1509 .success(); 1510 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1511 @ f3677b3e3b95a34e7017655ab612e1d11b59c713 1512 ○ cd740e230992f334de13a0bd0b35709b3f7a89af master git_head() initial 1513 ◆ 0000000000000000000000000000000000000000 1514 [EOF] 1515 "); 1516 insta::assert_snapshot!( 1517 git_repo.head_id().unwrap().to_string(), 1518 @"cd740e230992f334de13a0bd0b35709b3f7a89af" 1519 ); 1520 1521 // Check that trying to look up the second commit fails gracefully 1522 let output = work_dir.run_jj(["show", &commit2.to_string()]); 1523 insta::assert_snapshot!(output, @r" 1524 ------- stderr ------- 1525 Error: Revision `b23bb53bdce25f0e03ff9e484eadb77626256041` doesn't exist 1526 [EOF] 1527 [exit status: 1] 1528 "); 1529} 1530 1531#[test] 1532fn test_git_colocated_operation_cleanup() { 1533 let test_env = TestEnvironment::default(); 1534 let output = test_env.run_jj_in(".", ["git", "init", "--colocate", "repo"]); 1535 insta::assert_snapshot!(output, @r#" 1536 ------- stderr ------- 1537 Initialized repo in "repo" 1538 [EOF] 1539 "#); 1540 1541 let work_dir = test_env.work_dir("repo"); 1542 1543 work_dir.write_file("file", "1"); 1544 work_dir.run_jj(["describe", "-m1"]).success(); 1545 work_dir.run_jj(["new"]).success(); 1546 1547 work_dir.write_file("file", "2"); 1548 work_dir.run_jj(["describe", "-m2"]).success(); 1549 work_dir 1550 .run_jj(["bookmark", "create", "-r@", "main"]) 1551 .success(); 1552 work_dir.run_jj(["new", "root()+"]).success(); 1553 1554 work_dir.write_file("file", "3"); 1555 work_dir.run_jj(["describe", "-m3"]).success(); 1556 work_dir 1557 .run_jj(["bookmark", "create", "-r@", "feature"]) 1558 .success(); 1559 work_dir.run_jj(["new"]).success(); 1560 1561 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1562 @ 40638ce20b8b74e94460e95709cb077f4307ad7c 1563 ○ a50e55141dcd5f8f8d549acd2232ce4839eaa798 feature git_head() 3 1564 │ ○ cf3bb116ded416d9b202e71303f260e504c2eeb9 main 2 1565 ├─╯ 1566 ○ 87f64775047d7ce62b7ee81412b8e4cc07aea40a 1 1567 ◆ 0000000000000000000000000000000000000000 1568 [EOF] 1569 "); 1570 1571 // Start a rebase in Git and expect a merge conflict. 1572 let output = std::process::Command::new("git") 1573 .current_dir(work_dir.root()) 1574 .args(["rebase", "main"]) 1575 .output() 1576 .unwrap(); 1577 assert!(!output.status.success()); 1578 1579 // Check that we’re in the middle of a conflicted rebase. 1580 assert!(std::fs::exists(work_dir.root().join(".git").join("rebase-merge")).unwrap()); 1581 let output = std::process::Command::new("git") 1582 .current_dir(work_dir.root()) 1583 .args(["status", "--porcelain=v1"]) 1584 .output() 1585 .unwrap(); 1586 assert!(output.status.success()); 1587 insta::assert_snapshot!(String::from_utf8(output.stdout).unwrap(), @r#" 1588 UU file 1589 "#); 1590 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1591 @ 588c505e689d116180684778b29c540fe7180268 1592 ○ cf3bb116ded416d9b202e71303f260e504c2eeb9 main git_head() 2 1593 │ ○ a50e55141dcd5f8f8d549acd2232ce4839eaa798 feature 3 1594 ├─╯ 1595 ○ 87f64775047d7ce62b7ee81412b8e4cc07aea40a 1 1596 ◆ 0000000000000000000000000000000000000000 1597 [EOF] 1598 ------- stderr ------- 1599 Reset the working copy parent to the new Git HEAD. 1600 [EOF] 1601 "); 1602 1603 // Reset the Git HEAD with Jujutsu. 1604 let output = work_dir.run_jj(["new", "main"]); 1605 insta::assert_snapshot!(output, @r" 1606 ------- stderr ------- 1607 Working copy (@) now at: kmkuslsw aa14563c (empty) (no description set) 1608 Parent commit (@-) : kkmpptxz cf3bb116 main | 2 1609 Added 0 files, modified 1 files, removed 0 files 1610 [EOF] 1611 "); 1612 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1613 @ aa14563cf5d892238f1e60260c5c284627d76e7c 1614 │ ○ 588c505e689d116180684778b29c540fe7180268 1615 ├─╯ 1616 ○ cf3bb116ded416d9b202e71303f260e504c2eeb9 main git_head() 2 1617 │ ○ a50e55141dcd5f8f8d549acd2232ce4839eaa798 feature 3 1618 ├─╯ 1619 ○ 87f64775047d7ce62b7ee81412b8e4cc07aea40a 1 1620 ◆ 0000000000000000000000000000000000000000 1621 [EOF] 1622 "); 1623 1624 // Check that the operation was correctly aborted. 1625 assert!(!std::fs::exists(work_dir.root().join(".git").join("rebase-merge")).unwrap()); 1626 let output = std::process::Command::new("git") 1627 .current_dir(work_dir.root()) 1628 .args(["status", "--porcelain=v1"]) 1629 .output() 1630 .unwrap(); 1631 assert!(output.status.success()); 1632 insta::assert_snapshot!(String::from_utf8(output.stdout).unwrap(), @""); 1633} 1634 1635#[must_use] 1636fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput { 1637 // --quiet to suppress deleted bookmarks hint 1638 work_dir.run_jj(["bookmark", "list", "--all-remotes", "--quiet"]) 1639}