just playing with tangled
at diffedit3 802 lines 33 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 git2::Oid; 18 19use crate::common::TestEnvironment; 20 21#[test] 22fn test_git_colocated() { 23 let test_env = TestEnvironment::default(); 24 let workspace_root = test_env.env_root().join("repo"); 25 let git_repo = git2::Repository::init(&workspace_root).unwrap(); 26 27 // Create an initial commit in Git 28 std::fs::write(workspace_root.join("file"), "contents").unwrap(); 29 git_repo 30 .index() 31 .unwrap() 32 .add_path(Path::new("file")) 33 .unwrap(); 34 let tree1_oid = git_repo.index().unwrap().write_tree().unwrap(); 35 let tree1 = git_repo.find_tree(tree1_oid).unwrap(); 36 let signature = git2::Signature::new( 37 "Someone", 38 "someone@example.com", 39 &git2::Time::new(1234567890, 60), 40 ) 41 .unwrap(); 42 git_repo 43 .commit( 44 Some("refs/heads/master"), 45 &signature, 46 &signature, 47 "initial", 48 &tree1, 49 &[], 50 ) 51 .unwrap(); 52 insta::assert_snapshot!( 53 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 54 @"e61b6729ff4292870702f2f72b2a60165679ef37" 55 ); 56 57 // Import the repo 58 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 59 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 60 @ 3e9369cd54227eb88455e1834dbc08aad6a16ac4 61 ◉ e61b6729ff4292870702f2f72b2a60165679ef37 master HEAD@git initial 62 ◉ 0000000000000000000000000000000000000000 63 "###); 64 insta::assert_snapshot!( 65 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 66 @"e61b6729ff4292870702f2f72b2a60165679ef37" 67 ); 68 69 // Modify the working copy. The working-copy commit should changed, but the Git 70 // HEAD commit should not 71 std::fs::write(workspace_root.join("file"), "modified").unwrap(); 72 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 73 @ b26951a9c6f5c270e4d039880208952fd5faae5e 74 ◉ e61b6729ff4292870702f2f72b2a60165679ef37 master HEAD@git initial 75 ◉ 0000000000000000000000000000000000000000 76 "###); 77 insta::assert_snapshot!( 78 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 79 @"e61b6729ff4292870702f2f72b2a60165679ef37" 80 ); 81 82 // Create a new change from jj and check that it's reflected in Git 83 test_env.jj_cmd_ok(&workspace_root, &["new"]); 84 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 85 @ 9dbb23ff2ff5e66c43880f1042369d704f7a321e 86 ◉ b26951a9c6f5c270e4d039880208952fd5faae5e HEAD@git 87 ◉ e61b6729ff4292870702f2f72b2a60165679ef37 master initial 88 ◉ 0000000000000000000000000000000000000000 89 "###); 90 insta::assert_snapshot!( 91 git_repo.head().unwrap().target().unwrap().to_string(), 92 @"b26951a9c6f5c270e4d039880208952fd5faae5e" 93 ); 94} 95 96#[test] 97fn test_git_colocated_unborn_branch() { 98 let test_env = TestEnvironment::default(); 99 let workspace_root = test_env.env_root().join("repo"); 100 let git_repo = git2::Repository::init(&workspace_root).unwrap(); 101 102 let add_file_to_index = |name: &str, data: &str| { 103 std::fs::write(workspace_root.join(name), data).unwrap(); 104 let mut index = git_repo.index().unwrap(); 105 index.add_path(Path::new(name)).unwrap(); 106 index.write().unwrap(); 107 }; 108 let checkout_index = || { 109 let mut index = git_repo.index().unwrap(); 110 index.read(true).unwrap(); // discard in-memory cache 111 git_repo.checkout_index(Some(&mut index), None).unwrap(); 112 }; 113 114 // Initially, HEAD isn't set. 115 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 116 assert!(git_repo.head().is_err()); 117 assert_eq!( 118 git_repo.find_reference("HEAD").unwrap().symbolic_target(), 119 Some("refs/heads/master") 120 ); 121 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 122 @ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 123 ◉ 0000000000000000000000000000000000000000 124 "###); 125 126 // Stage some change, and check out root. This shouldn't clobber the HEAD. 127 add_file_to_index("file0", ""); 128 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["new", "root()"]); 129 insta::assert_snapshot!(stdout, @""); 130 insta::assert_snapshot!(stderr, @r###" 131 Working copy now at: kkmpptxz fcdbbd73 (empty) (no description set) 132 Parent commit : zzzzzzzz 00000000 (empty) (no description set) 133 Added 0 files, modified 0 files, removed 1 files 134 "###); 135 assert!(git_repo.head().is_err()); 136 assert_eq!( 137 git_repo.find_reference("HEAD").unwrap().symbolic_target(), 138 Some("refs/heads/master") 139 ); 140 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 141 @ fcdbbd731496cae17161cd6be9b6cf1f759655a8 142 │ ◉ 1de814dbef9641cc6c5c80d2689b80778edcce09 143 ├─╯ 144 ◉ 0000000000000000000000000000000000000000 145 "###); 146 // Staged change shouldn't persist. 147 checkout_index(); 148 insta::assert_snapshot!(test_env.jj_cmd_success(&workspace_root, &["status"]), @r###" 149 The working copy is clean 150 Working copy : kkmpptxz fcdbbd73 (empty) (no description set) 151 Parent commit: zzzzzzzz 00000000 (empty) (no description set) 152 "###); 153 154 // Stage some change, and create new HEAD. This shouldn't move the default 155 // branch. 156 add_file_to_index("file1", ""); 157 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["new"]); 158 insta::assert_snapshot!(stdout, @""); 159 insta::assert_snapshot!(stderr, @r###" 160 Working copy now at: royxmykx 76c60bf0 (empty) (no description set) 161 Parent commit : kkmpptxz f8d5bc77 (no description set) 162 "###); 163 assert!(git_repo.head().unwrap().symbolic_target().is_none()); 164 insta::assert_snapshot!( 165 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 166 @"f8d5bc772d1147351fd6e8cea52a4f935d3b31e7" 167 ); 168 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 169 @ 76c60bf0a66dcbe74d74d58c23848d96f9e86e84 170 ◉ f8d5bc772d1147351fd6e8cea52a4f935d3b31e7 HEAD@git 171 │ ◉ 1de814dbef9641cc6c5c80d2689b80778edcce09 172 ├─╯ 173 ◉ 0000000000000000000000000000000000000000 174 "###); 175 // Staged change shouldn't persist. 176 checkout_index(); 177 insta::assert_snapshot!(test_env.jj_cmd_success(&workspace_root, &["status"]), @r###" 178 The working copy is clean 179 Working copy : royxmykx 76c60bf0 (empty) (no description set) 180 Parent commit: kkmpptxz f8d5bc77 (no description set) 181 "###); 182 183 // Assign the default branch. The branch is no longer "unborn". 184 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "-r@-", "master"]); 185 186 // Stage some change, and check out root again. This should unset the HEAD. 187 // https://github.com/martinvonz/jj/issues/1495 188 add_file_to_index("file2", ""); 189 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["new", "root()"]); 190 insta::assert_snapshot!(stdout, @""); 191 insta::assert_snapshot!(stderr, @r###" 192 Working copy now at: znkkpsqq 10dd328b (empty) (no description set) 193 Parent commit : zzzzzzzz 00000000 (empty) (no description set) 194 Added 0 files, modified 0 files, removed 2 files 195 "###); 196 assert!(git_repo.head().is_err()); 197 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 198 @ 10dd328bb906e15890e55047740eab2812a3b2f7 199 │ ◉ 2c576a57d2e6e8494616629cfdbb8fe5e3fea73b 200 │ ◉ f8d5bc772d1147351fd6e8cea52a4f935d3b31e7 master 201 ├─╯ 202 │ ◉ 1de814dbef9641cc6c5c80d2689b80778edcce09 203 ├─╯ 204 ◉ 0000000000000000000000000000000000000000 205 "###); 206 // Staged change shouldn't persist. 207 checkout_index(); 208 insta::assert_snapshot!(test_env.jj_cmd_success(&workspace_root, &["status"]), @r###" 209 The working copy is clean 210 Working copy : znkkpsqq 10dd328b (empty) (no description set) 211 Parent commit: zzzzzzzz 00000000 (empty) (no description set) 212 "###); 213 214 // New snapshot and commit can be created after the HEAD got unset. 215 std::fs::write(workspace_root.join("file3"), "").unwrap(); 216 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["new"]); 217 insta::assert_snapshot!(stdout, @""); 218 insta::assert_snapshot!(stderr, @r###" 219 Working copy now at: wqnwkozp cab23370 (empty) (no description set) 220 Parent commit : znkkpsqq 8f5b2638 (no description set) 221 "###); 222 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 223 @ cab233704a5c0b21bde070943055f22142fb2043 224 ◉ 8f5b263819457712a2937428b9c58a2a84afbb1c HEAD@git 225 │ ◉ 2c576a57d2e6e8494616629cfdbb8fe5e3fea73b 226 │ ◉ f8d5bc772d1147351fd6e8cea52a4f935d3b31e7 master 227 ├─╯ 228 │ ◉ 1de814dbef9641cc6c5c80d2689b80778edcce09 229 ├─╯ 230 ◉ 0000000000000000000000000000000000000000 231 "###); 232} 233 234#[test] 235fn test_git_colocated_export_branches_on_snapshot() { 236 // Checks that we export branches that were changed only because the working 237 // copy was snapshotted 238 239 let test_env = TestEnvironment::default(); 240 let workspace_root = test_env.env_root().join("repo"); 241 let git_repo = git2::Repository::init(&workspace_root).unwrap(); 242 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 243 244 // Create branch pointing to the initial commit 245 std::fs::write(workspace_root.join("file"), "initial").unwrap(); 246 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "foo"]); 247 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 248 @ 438471f3fbf1004298d8fb01eeb13663a051a643 foo 249 ◉ 0000000000000000000000000000000000000000 250 "###); 251 252 // The branch gets updated when we modify the working copy, and it should get 253 // exported to Git without requiring any other changes 254 std::fs::write(workspace_root.join("file"), "modified").unwrap(); 255 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 256 @ fab22d1acf5bb9c5aa48cb2c3dd2132072a359ca foo 257 ◉ 0000000000000000000000000000000000000000 258 "###); 259 insta::assert_snapshot!(git_repo 260 .find_reference("refs/heads/foo") 261 .unwrap() 262 .target() 263 .unwrap() 264 .to_string(), @"fab22d1acf5bb9c5aa48cb2c3dd2132072a359ca"); 265} 266 267#[test] 268fn test_git_colocated_rebase_on_import() { 269 let test_env = TestEnvironment::default(); 270 let workspace_root = test_env.env_root().join("repo"); 271 let git_repo = git2::Repository::init(&workspace_root).unwrap(); 272 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 273 274 // Make some changes in jj and check that they're reflected in git 275 std::fs::write(workspace_root.join("file"), "contents").unwrap(); 276 test_env.jj_cmd_ok(&workspace_root, &["commit", "-m", "add a file"]); 277 std::fs::write(workspace_root.join("file"), "modified").unwrap(); 278 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "master"]); 279 test_env.jj_cmd_ok(&workspace_root, &["commit", "-m", "modify a file"]); 280 // TODO: We shouldn't need this command here to trigger an import of the 281 // refs/heads/master we just exported 282 test_env.jj_cmd_ok(&workspace_root, &["st"]); 283 284 // Move `master` and HEAD backwards, which should result in commit2 getting 285 // hidden, and a new working-copy commit at the new position. 286 let commit2_oid = git_repo 287 .find_branch("master", git2::BranchType::Local) 288 .unwrap() 289 .get() 290 .target() 291 .unwrap(); 292 let commit2 = git_repo.find_commit(commit2_oid).unwrap(); 293 let commit1 = commit2.parents().next().unwrap(); 294 git_repo.branch("master", &commit1, true).unwrap(); 295 git_repo.set_head("refs/heads/master").unwrap(); 296 let (stdout, stderr) = get_log_output_with_stderr(&test_env, &workspace_root); 297 insta::assert_snapshot!(stdout, @r###" 298 @ 7f96185cfbe36341d0f9a86ebfaeab67a5922c7e 299 ◉ 4bcbeaba9a4b309c5f45a8807fbf5499b9714315 master HEAD@git add a file 300 ◉ 0000000000000000000000000000000000000000 301 "###); 302 insta::assert_snapshot!(stderr, @r###" 303 Reset the working copy parent to the new Git HEAD. 304 Abandoned 1 commits that are no longer reachable. 305 Done importing changes from the underlying Git repo. 306 "###); 307} 308 309#[test] 310fn test_git_colocated_branches() { 311 let test_env = TestEnvironment::default(); 312 let workspace_root = test_env.env_root().join("repo"); 313 let git_repo = git2::Repository::init(&workspace_root).unwrap(); 314 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 315 test_env.jj_cmd_ok(&workspace_root, &["new", "-m", "foo"]); 316 test_env.jj_cmd_ok(&workspace_root, &["new", "@-", "-m", "bar"]); 317 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 318 @ 3560559274ab431feea00b7b7e0b9250ecce951f bar 319 │ ◉ 1e6f0b403ed2ff9713b5d6b1dc601e4804250cda foo 320 ├─╯ 321 ◉ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 HEAD@git 322 ◉ 0000000000000000000000000000000000000000 323 "###); 324 325 // Create a branch in jj. It should be exported to Git even though it points to 326 // the working- copy commit. 327 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "master"]); 328 insta::assert_snapshot!( 329 git_repo.find_reference("refs/heads/master").unwrap().target().unwrap().to_string(), 330 @"3560559274ab431feea00b7b7e0b9250ecce951f" 331 ); 332 insta::assert_snapshot!( 333 git_repo.head().unwrap().target().unwrap().to_string(), 334 @"230dd059e1b059aefc0da06a2e5a7dbf22362f22" 335 ); 336 337 // Update the branch in Git 338 let target_id = test_env.jj_cmd_success( 339 &workspace_root, 340 &["log", "--no-graph", "-T=commit_id", "-r=description(foo)"], 341 ); 342 git_repo 343 .reference( 344 "refs/heads/master", 345 Oid::from_str(&target_id).unwrap(), 346 true, 347 "test", 348 ) 349 .unwrap(); 350 let (stdout, stderr) = get_log_output_with_stderr(&test_env, &workspace_root); 351 insta::assert_snapshot!(stdout, @r###" 352 @ 096dc80da67094fbaa6683e2a205dddffa31f9a8 353 │ ◉ 1e6f0b403ed2ff9713b5d6b1dc601e4804250cda master foo 354 ├─╯ 355 ◉ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 HEAD@git 356 ◉ 0000000000000000000000000000000000000000 357 "###); 358 insta::assert_snapshot!(stderr, @r###" 359 Abandoned 1 commits that are no longer reachable. 360 Working copy now at: yqosqzyt 096dc80d (empty) (no description set) 361 Parent commit : qpvuntsm 230dd059 (empty) (no description set) 362 Done importing changes from the underlying Git repo. 363 "###); 364} 365 366#[test] 367fn test_git_colocated_branch_forget() { 368 let test_env = TestEnvironment::default(); 369 let workspace_root = test_env.env_root().join("repo"); 370 let _git_repo = git2::Repository::init(&workspace_root).unwrap(); 371 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 372 test_env.jj_cmd_ok(&workspace_root, &["new"]); 373 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "foo"]); 374 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 375 @ 65b6b74e08973b88d38404430f119c8c79465250 foo 376 ◉ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 HEAD@git 377 ◉ 0000000000000000000000000000000000000000 378 "###); 379 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]); 380 insta::assert_snapshot!(stdout, @r###" 381 foo: rlvkpnrz 65b6b74e (empty) (no description set) 382 @git: rlvkpnrz 65b6b74e (empty) (no description set) 383 "###); 384 385 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["branch", "forget", "foo"]); 386 insta::assert_snapshot!(stdout, @""); 387 insta::assert_snapshot!(stderr, @""); 388 // A forgotten branch is deleted in the git repo. For a detailed demo explaining 389 // this, see `test_branch_forget_export` in `test_branch_command.rs`. 390 let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list", "--all-remotes"]); 391 insta::assert_snapshot!(stdout, @""); 392} 393 394#[test] 395fn test_git_colocated_conflicting_git_refs() { 396 let test_env = TestEnvironment::default(); 397 let workspace_root = test_env.env_root().join("repo"); 398 git2::Repository::init(&workspace_root).unwrap(); 399 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 400 test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "main"]); 401 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["branch", "create", "main/sub"]); 402 insta::assert_snapshot!(stdout, @""); 403 insta::with_settings!({filters => vec![(": The lock for resource.*", ": ...")]}, { 404 insta::assert_snapshot!(stderr, @r###" 405 Warning: Failed to export some branches: 406 main/sub: Failed to set: A lock could not be obtained for reference "refs/heads/main/sub": ... 407 Hint: Git doesn't allow a branch name that looks like a parent directory of 408 another (e.g. `foo` and `foo/bar`). Try to rename the branches that failed to 409 export or their "parent" branches. 410 "###); 411 }); 412} 413 414#[test] 415fn test_git_colocated_checkout_non_empty_working_copy() { 416 let test_env = TestEnvironment::default(); 417 let workspace_root = test_env.env_root().join("repo"); 418 let git_repo = git2::Repository::init(&workspace_root).unwrap(); 419 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 420 421 // Create an initial commit in Git 422 // We use this to set HEAD to master 423 std::fs::write(workspace_root.join("file"), "contents").unwrap(); 424 git_repo 425 .index() 426 .unwrap() 427 .add_path(Path::new("file")) 428 .unwrap(); 429 let tree1_oid = git_repo.index().unwrap().write_tree().unwrap(); 430 let tree1 = git_repo.find_tree(tree1_oid).unwrap(); 431 let signature = git2::Signature::new( 432 "Someone", 433 "someone@example.com", 434 &git2::Time::new(1234567890, 60), 435 ) 436 .unwrap(); 437 git_repo 438 .commit( 439 Some("refs/heads/master"), 440 &signature, 441 &signature, 442 "initial", 443 &tree1, 444 &[], 445 ) 446 .unwrap(); 447 insta::assert_snapshot!( 448 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 449 @"e61b6729ff4292870702f2f72b2a60165679ef37" 450 ); 451 452 std::fs::write(workspace_root.join("two"), "y").unwrap(); 453 454 test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "two"]); 455 test_env.jj_cmd_ok(&workspace_root, &["new", "@-"]); 456 let (_, stderr) = test_env.jj_cmd_ok(&workspace_root, &["describe", "-m", "new"]); 457 insta::assert_snapshot!(stderr, @r###" 458 Working copy now at: kkmpptxz 4c049607 (empty) new 459 Parent commit : lnksqltp e61b6729 master | initial 460 "###); 461 462 let git_head = git_repo.find_reference("HEAD").unwrap(); 463 let git_head_target = git_head.symbolic_target().unwrap(); 464 465 assert_eq!(git_head_target, "refs/heads/master"); 466 467 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 468 @ 4c04960765ca906d0cb25b15a946be4c0dd71b8e new 469 │ ◉ 4ec6f6506bd1903410f15b80058a7f0d8f62deea two 470 ├─╯ 471 ◉ e61b6729ff4292870702f2f72b2a60165679ef37 master HEAD@git initial 472 ◉ 0000000000000000000000000000000000000000 473 "###); 474} 475 476#[test] 477fn test_git_colocated_fetch_deleted_or_moved_branch() { 478 let test_env = TestEnvironment::default(); 479 test_env.add_config("git.auto-local-branch = true"); 480 let origin_path = test_env.env_root().join("origin"); 481 git2::Repository::init(&origin_path).unwrap(); 482 test_env.jj_cmd_ok(&origin_path, &["init", "--git-repo=."]); 483 test_env.jj_cmd_ok(&origin_path, &["describe", "-m=A"]); 484 test_env.jj_cmd_ok(&origin_path, &["branch", "create", "A"]); 485 test_env.jj_cmd_ok(&origin_path, &["new", "-m=B_to_delete"]); 486 test_env.jj_cmd_ok(&origin_path, &["branch", "create", "B_to_delete"]); 487 test_env.jj_cmd_ok(&origin_path, &["new", "-m=original C", "@-"]); 488 test_env.jj_cmd_ok(&origin_path, &["branch", "create", "C_to_move"]); 489 490 let clone_path = test_env.env_root().join("clone"); 491 git2::Repository::clone(origin_path.to_str().unwrap(), &clone_path).unwrap(); 492 test_env.jj_cmd_ok(&clone_path, &["init", "--git-repo=."]); 493 test_env.jj_cmd_ok(&clone_path, &["new", "A"]); 494 insta::assert_snapshot!(get_log_output(&test_env, &clone_path), @r###" 495 @ 0335878796213c3a701f1c9c34dcae242bee4131 496 │ ◉ 8d4e006fd63547965fbc3a26556a9aa531076d32 C_to_move original C 497 ├─╯ 498 │ ◉ 929e298ae9edf969b405a304c75c10457c47d52c B_to_delete B_to_delete 499 ├─╯ 500 ◉ a86754f975f953fa25da4265764adc0c62e9ce6b A HEAD@git A 501 ◉ 0000000000000000000000000000000000000000 502 "###); 503 504 test_env.jj_cmd_ok(&origin_path, &["branch", "delete", "B_to_delete"]); 505 // Move branch C sideways 506 test_env.jj_cmd_ok(&origin_path, &["describe", "C_to_move", "-m", "moved C"]); 507 let (stdout, stderr) = test_env.jj_cmd_ok(&clone_path, &["git", "fetch"]); 508 insta::assert_snapshot!(stdout, @""); 509 insta::assert_snapshot!(stderr, @r###" 510 branch: B_to_delete@origin [deleted] untracked 511 branch: C_to_move@origin [updated] tracked 512 Abandoned 2 commits that are no longer reachable. 513 "###); 514 // "original C" and "B_to_delete" are abandoned, as the corresponding branches 515 // were deleted or moved on the remote (#864) 516 insta::assert_snapshot!(get_log_output(&test_env, &clone_path), @r###" 517 ◉ 04fd29df05638156b20044b3b6136b42abcb09ab C_to_move moved C 518 │ @ 0335878796213c3a701f1c9c34dcae242bee4131 519 ├─╯ 520 ◉ a86754f975f953fa25da4265764adc0c62e9ce6b A HEAD@git A 521 ◉ 0000000000000000000000000000000000000000 522 "###); 523} 524 525#[test] 526fn test_git_colocated_rebase_dirty_working_copy() { 527 let test_env = TestEnvironment::default(); 528 let repo_path = test_env.env_root().join("repo"); 529 let git_repo = git2::Repository::init(&repo_path).unwrap(); 530 test_env.jj_cmd_ok(&repo_path, &["init", "--git-repo=."]); 531 532 std::fs::write(repo_path.join("file"), "base").unwrap(); 533 test_env.jj_cmd_ok(&repo_path, &["new"]); 534 std::fs::write(repo_path.join("file"), "old").unwrap(); 535 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "feature"]); 536 537 // Make the working-copy dirty, delete the checked out branch. 538 std::fs::write(repo_path.join("file"), "new").unwrap(); 539 git_repo 540 .find_reference("refs/heads/feature") 541 .unwrap() 542 .delete() 543 .unwrap(); 544 545 // Because the working copy is dirty, the new working-copy commit will be 546 // diverged. Therefore, the feature branch has change-delete conflict. 547 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["status"]); 548 insta::assert_snapshot!(stdout, @r###" 549 Working copy changes: 550 M file 551 Working copy : rlvkpnrz d6c5e664 feature?? | (no description set) 552 Parent commit: qpvuntsm 5973d373 (no description set) 553 These branches have conflicts: 554 feature 555 Use `jj branch list` to see details. Use `jj branch set <name> -r <rev>` to resolve. 556 "###); 557 insta::assert_snapshot!(stderr, @r###" 558 Warning: Failed to export some branches: 559 feature: Modified ref had been deleted in Git 560 Done importing changes from the underlying Git repo. 561 "###); 562 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" 563 @ d6c5e66473426f5ed3a24ecce8ce8b44ff23cf81 feature?? 564 ◉ 5973d3731aba9dd86c00b4a765fbc4cc13f1e14b HEAD@git 565 ◉ 0000000000000000000000000000000000000000 566 "###); 567 568 // The working-copy content shouldn't be lost. 569 insta::assert_snapshot!( 570 std::fs::read_to_string(repo_path.join("file")).unwrap(), @"new"); 571} 572 573#[test] 574fn test_git_colocated_external_checkout() { 575 let test_env = TestEnvironment::default(); 576 let repo_path = test_env.env_root().join("repo"); 577 let git_repo = git2::Repository::init(&repo_path).unwrap(); 578 test_env.jj_cmd_ok(&repo_path, &["init", "--git-repo=."]); 579 test_env.jj_cmd_ok(&repo_path, &["ci", "-m=A"]); 580 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "-r@-", "master"]); 581 test_env.jj_cmd_ok(&repo_path, &["new", "-m=B", "root()"]); 582 test_env.jj_cmd_ok(&repo_path, &["new"]); 583 584 // Checked out anonymous branch 585 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" 586 @ f8a23336e41840ed1757ef323402a770427dc89a 587 ◉ eccedddfa5152d99fc8ddd1081b375387a8a382a HEAD@git B 588 │ ◉ a86754f975f953fa25da4265764adc0c62e9ce6b master A 589 ├─╯ 590 ◉ 0000000000000000000000000000000000000000 591 "###); 592 593 // Check out another branch by external command 594 git_repo 595 .set_head_detached( 596 git_repo 597 .find_reference("refs/heads/master") 598 .unwrap() 599 .target() 600 .unwrap(), 601 ) 602 .unwrap(); 603 604 // The old working-copy commit gets abandoned, but the whole branch should not 605 // be abandoned. (#1042) 606 let (stdout, stderr) = get_log_output_with_stderr(&test_env, &repo_path); 607 insta::assert_snapshot!(stdout, @r###" 608 @ adadbd65a794e2294962b3c3da9aada09fe1b472 609 ◉ a86754f975f953fa25da4265764adc0c62e9ce6b master HEAD@git A 610 │ ◉ eccedddfa5152d99fc8ddd1081b375387a8a382a B 611 ├─╯ 612 ◉ 0000000000000000000000000000000000000000 613 "###); 614 insta::assert_snapshot!(stderr, @r###" 615 Reset the working copy parent to the new Git HEAD. 616 "###); 617} 618 619#[test] 620fn test_git_colocated_squash_undo() { 621 let test_env = TestEnvironment::default(); 622 let repo_path = test_env.env_root().join("repo"); 623 git2::Repository::init(&repo_path).unwrap(); 624 test_env.jj_cmd_ok(&repo_path, &["init", "--git-repo=."]); 625 test_env.jj_cmd_ok(&repo_path, &["ci", "-m=A"]); 626 // Test the setup 627 insta::assert_snapshot!(get_log_output_divergence(&test_env, &repo_path), @r###" 628 @ rlvkpnrzqnoo 8f71e3b6a3be 629 ◉ qpvuntsmwlqt a86754f975f9 A HEAD@git 630 ◉ zzzzzzzzzzzz 000000000000 631 "###); 632 633 test_env.jj_cmd_ok(&repo_path, &["squash"]); 634 insta::assert_snapshot!(get_log_output_divergence(&test_env, &repo_path), @r###" 635 @ zsuskulnrvyr f0c12b0396d9 636 ◉ qpvuntsmwlqt 2f376ea1478c A HEAD@git 637 ◉ zzzzzzzzzzzz 000000000000 638 "###); 639 test_env.jj_cmd_ok(&repo_path, &["undo"]); 640 // TODO: There should be no divergence here; 2f376ea1478c should be hidden 641 // (#922) 642 insta::assert_snapshot!(get_log_output_divergence(&test_env, &repo_path), @r###" 643 @ rlvkpnrzqnoo 8f71e3b6a3be 644 ◉ qpvuntsmwlqt a86754f975f9 A HEAD@git 645 ◉ zzzzzzzzzzzz 000000000000 646 "###); 647} 648 649#[test] 650fn test_git_colocated_undo_head_move() { 651 let test_env = TestEnvironment::default(); 652 let repo_path = test_env.env_root().join("repo"); 653 let git_repo = git2::Repository::init(&repo_path).unwrap(); 654 test_env.jj_cmd_ok(&repo_path, &["init", "--git-repo=."]); 655 656 // Create new HEAD 657 test_env.jj_cmd_ok(&repo_path, &["new"]); 658 insta::assert_snapshot!( 659 git_repo.head().unwrap().target().unwrap().to_string(), 660 @"230dd059e1b059aefc0da06a2e5a7dbf22362f22"); 661 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" 662 @ 65b6b74e08973b88d38404430f119c8c79465250 663 ◉ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 HEAD@git 664 ◉ 0000000000000000000000000000000000000000 665 "###); 666 667 // HEAD should be unset 668 test_env.jj_cmd_ok(&repo_path, &["undo"]); 669 assert!(git_repo.head().is_err()); 670 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" 671 @ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 672 ◉ 0000000000000000000000000000000000000000 673 "###); 674 675 // Create commit on non-root commit 676 test_env.jj_cmd_ok(&repo_path, &["new"]); 677 test_env.jj_cmd_ok(&repo_path, &["new"]); 678 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" 679 @ 69b19f73cf584f162f078fb0d91c55ca39d10bc7 680 ◉ eb08b363bb5ef8ee549314260488980d7bbe8f63 HEAD@git 681 ◉ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 682 ◉ 0000000000000000000000000000000000000000 683 "###); 684 insta::assert_snapshot!( 685 git_repo.head().unwrap().target().unwrap().to_string(), 686 @"eb08b363bb5ef8ee549314260488980d7bbe8f63"); 687 688 // HEAD should be moved back 689 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["undo"]); 690 insta::assert_snapshot!(stdout, @""); 691 insta::assert_snapshot!(stderr, @r###" 692 Working copy now at: royxmykx eb08b363 (empty) (no description set) 693 Parent commit : qpvuntsm 230dd059 (empty) (no description set) 694 "###); 695 insta::assert_snapshot!( 696 git_repo.head().unwrap().target().unwrap().to_string(), 697 @"230dd059e1b059aefc0da06a2e5a7dbf22362f22"); 698 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" 699 @ eb08b363bb5ef8ee549314260488980d7bbe8f63 700 ◉ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 HEAD@git 701 ◉ 0000000000000000000000000000000000000000 702 "###); 703} 704 705fn get_log_output_divergence(test_env: &TestEnvironment, repo_path: &Path) -> String { 706 let template = r#" 707 separate(" ", 708 change_id.short(), 709 commit_id.short(), 710 description.first_line(), 711 branches, 712 git_head, 713 if(divergent, "!divergence!"), 714 ) 715 "#; 716 test_env.jj_cmd_success(repo_path, &["log", "-T", template]) 717} 718 719fn get_log_output(test_env: &TestEnvironment, workspace_root: &Path) -> String { 720 let template = r#"separate(" ", commit_id, branches, git_head, description)"#; 721 test_env.jj_cmd_success(workspace_root, &["log", "-T", template, "-r=all()"]) 722} 723 724fn get_log_output_with_stderr( 725 test_env: &TestEnvironment, 726 workspace_root: &Path, 727) -> (String, String) { 728 let template = r#"separate(" ", commit_id, branches, git_head, description)"#; 729 test_env.jj_cmd_ok(workspace_root, &["log", "-T", template, "-r=all()"]) 730} 731 732#[test] 733fn test_git_colocated_unreachable_commits() { 734 let test_env = TestEnvironment::default(); 735 let workspace_root = test_env.env_root().join("repo"); 736 let git_repo = git2::Repository::init(&workspace_root).unwrap(); 737 738 // Create an initial commit in Git 739 let empty_tree_oid = git_repo.treebuilder(None).unwrap().write().unwrap(); 740 let tree1 = git_repo.find_tree(empty_tree_oid).unwrap(); 741 let signature = git2::Signature::new( 742 "Someone", 743 "someone@example.com", 744 &git2::Time::new(1234567890, 60), 745 ) 746 .unwrap(); 747 let oid1 = git_repo 748 .commit( 749 Some("refs/heads/master"), 750 &signature, 751 &signature, 752 "initial", 753 &tree1, 754 &[], 755 ) 756 .unwrap(); 757 insta::assert_snapshot!( 758 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 759 @"2ee37513d2b5e549f7478c671a780053614bff19" 760 ); 761 762 // Add a second commit in Git 763 let tree2 = git_repo.find_tree(empty_tree_oid).unwrap(); 764 let signature = git2::Signature::new( 765 "Someone", 766 "someone@example.com", 767 &git2::Time::new(1234567890, 62), 768 ) 769 .unwrap(); 770 let oid2 = git_repo 771 .commit( 772 None, 773 &signature, 774 &signature, 775 "next", 776 &tree2, 777 &[&git_repo.find_commit(oid1).unwrap()], 778 ) 779 .unwrap(); 780 insta::assert_snapshot!( 781 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 782 @"2ee37513d2b5e549f7478c671a780053614bff19" 783 ); 784 785 // Import the repo while there is no path to the second commit 786 test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]); 787 insta::assert_snapshot!(get_log_output(&test_env, &workspace_root), @r###" 788 @ 66ae47cee4f8c28ee8d7e4f5d9401b03c07e22f2 789 ◉ 2ee37513d2b5e549f7478c671a780053614bff19 master HEAD@git initial 790 ◉ 0000000000000000000000000000000000000000 791 "###); 792 insta::assert_snapshot!( 793 git_repo.head().unwrap().peel_to_commit().unwrap().id().to_string(), 794 @"2ee37513d2b5e549f7478c671a780053614bff19" 795 ); 796 797 // Check that trying to look up the second commit fails gracefully 798 let stderr = test_env.jj_cmd_failure(&workspace_root, &["show", &oid2.to_string()]); 799 insta::assert_snapshot!(stderr, @r###" 800 Error: Revision "8e713ff77b54928dd4a82aaabeca44b1ae91722c" doesn't exist 801 "###); 802}