just playing with tangled
1// Copyright 2024 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;
16use std::path::PathBuf;
17
18use indoc::formatdoc;
19use test_case::test_case;
20use testutils::git;
21
22use crate::common::to_toml_value;
23use crate::common::CommandOutput;
24use crate::common::TestEnvironment;
25use crate::common::TestWorkDir;
26
27fn init_git_repo(git_repo_path: &Path, bare: bool) -> gix::Repository {
28 let git_repo = if bare {
29 git::init_bare(git_repo_path)
30 } else {
31 git::init(git_repo_path)
32 };
33
34 let git::CommitResult { commit_id, .. } = git::add_commit(
35 &git_repo,
36 "refs/heads/my-bookmark",
37 "some-file",
38 b"some content",
39 "My commit message",
40 &[],
41 );
42 git::set_head_to_id(&git_repo, commit_id);
43 git_repo
44}
45
46#[must_use]
47fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput {
48 work_dir.run_jj(["bookmark", "list", "--all-remotes"])
49}
50
51#[must_use]
52fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
53 let template = r#"
54 separate(" ",
55 commit_id.short(),
56 bookmarks,
57 if(git_head, "git_head()"),
58 description,
59 )"#;
60 work_dir.run_jj(["log", "-T", template, "-r=all()"])
61}
62
63fn read_git_target(work_dir: &TestWorkDir) -> String {
64 String::from_utf8(work_dir.read_file(".jj/repo/store/git_target").into()).unwrap()
65}
66
67#[test]
68fn test_git_init_internal() {
69 let test_env = TestEnvironment::default();
70 let output = test_env.run_jj_in(".", ["git", "init", "repo"]);
71 insta::assert_snapshot!(output, @r#"
72 ------- stderr -------
73 Initialized repo in "repo"
74 [EOF]
75 "#);
76
77 let work_dir = test_env.work_dir("repo");
78 let jj_path = work_dir.root().join(".jj");
79 let repo_path = jj_path.join("repo");
80 let store_path = repo_path.join("store");
81 assert!(work_dir.root().is_dir());
82 assert!(jj_path.is_dir());
83 assert!(jj_path.join("working_copy").is_dir());
84 assert!(repo_path.is_dir());
85 assert!(store_path.is_dir());
86 assert!(store_path.join("git").is_dir());
87 assert_eq!(read_git_target(&work_dir), "git");
88}
89
90#[test]
91fn test_git_init_internal_ignore_working_copy() {
92 let test_env = TestEnvironment::default();
93 let work_dir = test_env.work_dir("").create_dir("repo");
94 work_dir.write_file("file1", "");
95
96 let output = work_dir.run_jj(["git", "init", "--ignore-working-copy"]);
97 insta::assert_snapshot!(output, @r"
98 ------- stderr -------
99 Error: --ignore-working-copy is not respected
100 [EOF]
101 [exit status: 2]
102 ");
103}
104
105#[test]
106fn test_git_init_internal_at_operation() {
107 let test_env = TestEnvironment::default();
108 let work_dir = test_env.work_dir("").create_dir("repo");
109
110 let output = work_dir.run_jj(["git", "init", "--at-op=@-"]);
111 insta::assert_snapshot!(output, @r"
112 ------- stderr -------
113 Error: --at-op is not respected
114 [EOF]
115 [exit status: 2]
116 ");
117}
118
119#[test_case(false; "full")]
120#[test_case(true; "bare")]
121fn test_git_init_external(bare: bool) {
122 let test_env = TestEnvironment::default();
123 let git_repo_path = test_env.env_root().join("git-repo");
124 init_git_repo(&git_repo_path, bare);
125
126 let output = test_env.run_jj_in(
127 ".",
128 [
129 "git",
130 "init",
131 "repo",
132 "--git-repo",
133 git_repo_path.to_str().unwrap(),
134 ],
135 );
136 insta::allow_duplicates! {
137 insta::assert_snapshot!(output, @r#"
138 ------- stderr -------
139 Done importing changes from the underlying Git repo.
140 Working copy (@) now at: sqpuoqvx ed6b5138 (empty) (no description set)
141 Parent commit (@-) : nntyzxmz e80a42cc my-bookmark | My commit message
142 Added 1 files, modified 0 files, removed 0 files
143 Initialized repo in "repo"
144 [EOF]
145 "#);
146 }
147
148 let work_dir = test_env.work_dir("repo");
149 let jj_path = work_dir.root().join(".jj");
150 let repo_path = jj_path.join("repo");
151 let store_path = repo_path.join("store");
152 assert!(work_dir.root().is_dir());
153 assert!(jj_path.is_dir());
154 assert!(jj_path.join("working_copy").is_dir());
155 assert!(repo_path.is_dir());
156 assert!(store_path.is_dir());
157 let unix_git_target_file_contents = read_git_target(&work_dir).replace('\\', "/");
158 if bare {
159 assert!(unix_git_target_file_contents.ends_with("/git-repo"));
160 } else {
161 assert!(unix_git_target_file_contents.ends_with("/git-repo/.git"));
162 }
163
164 // Check that the Git repo's HEAD got checked out
165 insta::allow_duplicates! {
166 insta::assert_snapshot!(get_log_output(&work_dir), @r"
167 @ ed6b513890ae
168 ○ e80a42cccd06 my-bookmark git_head() My commit message
169 ◆ 000000000000
170 [EOF]
171 ");
172 }
173}
174
175#[test_case(false; "full")]
176#[test_case(true; "bare")]
177fn test_git_init_external_import_trunk(bare: bool) {
178 let test_env = TestEnvironment::default();
179 let git_repo_path = test_env.env_root().join("git-repo");
180 let git_repo = init_git_repo(&git_repo_path, bare);
181
182 // Add remote bookmark "trunk" for remote "origin", and set it as "origin/HEAD"
183 let oid = git_repo
184 .find_reference("refs/heads/my-bookmark")
185 .unwrap()
186 .id();
187
188 git_repo
189 .reference(
190 "refs/remotes/origin/trunk",
191 oid.detach(),
192 gix::refs::transaction::PreviousValue::MustNotExist,
193 "create remote ref",
194 )
195 .unwrap();
196
197 git::set_symbolic_reference(
198 &git_repo,
199 "refs/remotes/origin/HEAD",
200 "refs/remotes/origin/trunk",
201 );
202
203 let output = test_env.run_jj_in(
204 ".",
205 [
206 "git",
207 "init",
208 "repo",
209 "--git-repo",
210 git_repo_path.to_str().unwrap(),
211 ],
212 );
213 insta::allow_duplicates! {
214 insta::assert_snapshot!(output, @r#"
215 ------- stderr -------
216 Done importing changes from the underlying Git repo.
217 Setting the revset alias `trunk()` to `trunk@origin`
218 Working copy (@) now at: sqpuoqvx ed6b5138 (empty) (no description set)
219 Parent commit (@-) : nntyzxmz e80a42cc my-bookmark trunk@origin | My commit message
220 Added 1 files, modified 0 files, removed 0 files
221 Initialized repo in "repo"
222 [EOF]
223 "#);
224 }
225
226 // "trunk()" alias should be set to remote "origin"'s default bookmark "trunk"
227 let work_dir = test_env.work_dir("repo");
228 let output = work_dir.run_jj(["config", "list", "--repo", "revset-aliases.\"trunk()\""]);
229 insta::allow_duplicates! {
230 insta::assert_snapshot!(output, @r#"
231 revset-aliases."trunk()" = "trunk@origin"
232 [EOF]
233 "#);
234 }
235}
236
237#[test]
238fn test_git_init_external_ignore_working_copy() {
239 let test_env = TestEnvironment::default();
240 let git_repo_path = test_env.env_root().join("git-repo");
241 init_git_repo(&git_repo_path, false);
242 let work_dir = test_env.work_dir("").create_dir("repo");
243 work_dir.write_file("file1", "");
244
245 // No snapshot should be taken
246 let output = work_dir.run_jj([
247 "git",
248 "init",
249 "--ignore-working-copy",
250 "--git-repo",
251 git_repo_path.to_str().unwrap(),
252 ]);
253 insta::assert_snapshot!(output, @r"
254 ------- stderr -------
255 Error: --ignore-working-copy is not respected
256 [EOF]
257 [exit status: 2]
258 ");
259}
260
261#[test]
262fn test_git_init_external_at_operation() {
263 let test_env = TestEnvironment::default();
264 let git_repo_path = test_env.env_root().join("git-repo");
265 init_git_repo(&git_repo_path, false);
266 let work_dir = test_env.work_dir("").create_dir("repo");
267
268 let output = work_dir.run_jj([
269 "git",
270 "init",
271 "--at-op=@-",
272 "--git-repo",
273 git_repo_path.to_str().unwrap(),
274 ]);
275 insta::assert_snapshot!(output, @r"
276 ------- stderr -------
277 Error: --at-op is not respected
278 [EOF]
279 [exit status: 2]
280 ");
281}
282
283#[test]
284fn test_git_init_external_non_existent_directory() {
285 let test_env = TestEnvironment::default();
286 let output = test_env.run_jj_in(".", ["git", "init", "repo", "--git-repo", "non-existent"]);
287 insta::assert_snapshot!(output.strip_stderr_last_line(), @r"
288 ------- stderr -------
289 Error: Failed to access the repository
290 Caused by:
291 1: Cannot access $TEST_ENV/non-existent
292 [EOF]
293 [exit status: 1]
294 ");
295}
296
297#[test]
298fn test_git_init_external_non_existent_git_directory() {
299 let test_env = TestEnvironment::default();
300 let work_dir = test_env.work_dir("repo");
301 let output = test_env.run_jj_in(".", ["git", "init", "repo", "--git-repo", "repo"]);
302 insta::assert_snapshot!(output, @r#"
303 ------- stderr -------
304 Error: Failed to access the repository
305 Caused by:
306 1: Failed to open git repository
307 2: "$TEST_ENV/repo" does not appear to be a git repository
308 3: Missing HEAD at '.git/HEAD'
309 [EOF]
310 [exit status: 1]
311 "#);
312 let jj_path = work_dir.root().join(".jj");
313 assert!(!jj_path.exists());
314}
315
316#[test]
317fn test_git_init_colocated_via_git_repo_path() {
318 let test_env = TestEnvironment::default();
319 let work_dir = test_env.work_dir("repo");
320 init_git_repo(work_dir.root(), false);
321 let output = work_dir.run_jj(["git", "init", "--git-repo", "."]);
322 insta::assert_snapshot!(output, @r#"
323 ------- stderr -------
324 Done importing changes from the underlying Git repo.
325 Initialized repo in "."
326 [EOF]
327 "#);
328
329 let jj_path = work_dir.root().join(".jj");
330 let repo_path = jj_path.join("repo");
331 let store_path = repo_path.join("store");
332 assert!(work_dir.root().is_dir());
333 assert!(jj_path.is_dir());
334 assert!(jj_path.join("working_copy").is_dir());
335 assert!(repo_path.is_dir());
336 assert!(store_path.is_dir());
337 assert!(read_git_target(&work_dir)
338 .replace('\\', "/")
339 .ends_with("../../../.git"));
340
341 // Check that the Git repo's HEAD got checked out
342 insta::assert_snapshot!(get_log_output(&work_dir), @r"
343 @ f3fe58bc88cc
344 ○ e80a42cccd06 my-bookmark git_head() My commit message
345 ◆ 000000000000
346 [EOF]
347 ");
348
349 // Check that the Git repo's HEAD moves
350 work_dir.run_jj(["new"]).success();
351 insta::assert_snapshot!(get_log_output(&work_dir), @r"
352 @ 0c77f9e21b55
353 ○ f3fe58bc88cc git_head()
354 ○ e80a42cccd06 my-bookmark My commit message
355 ◆ 000000000000
356 [EOF]
357 ");
358}
359
360#[test]
361fn test_git_init_colocated_via_git_repo_path_gitlink() {
362 let test_env = TestEnvironment::default();
363 // <jj_work_dir>/.git -> <git_repo_path>
364 let git_repo_path = test_env.env_root().join("git-repo");
365 let git_repo = init_git_repo(&git_repo_path, false);
366 let jj_work_dir = test_env.work_dir("").create_dir("repo");
367 git::create_gitlink(jj_work_dir.root(), git_repo.path());
368
369 assert!(jj_work_dir.root().join(".git").is_file());
370 let output = jj_work_dir.run_jj(["git", "init", "--git-repo", "."]);
371 insta::assert_snapshot!(output, @r#"
372 ------- stderr -------
373 Done importing changes from the underlying Git repo.
374 Initialized repo in "."
375 [EOF]
376 "#);
377 insta::assert_snapshot!(read_git_target(&jj_work_dir), @"../../../.git");
378
379 // Check that the Git repo's HEAD got checked out
380 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
381 @ f3fe58bc88cc
382 ○ e80a42cccd06 my-bookmark git_head() My commit message
383 ◆ 000000000000
384 [EOF]
385 ");
386
387 // Check that the Git repo's HEAD moves
388 jj_work_dir.run_jj(["new"]).success();
389 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
390 @ 0c77f9e21b55
391 ○ f3fe58bc88cc git_head()
392 ○ e80a42cccd06 my-bookmark My commit message
393 ◆ 000000000000
394 [EOF]
395 ");
396}
397
398#[cfg(unix)]
399#[test]
400fn test_git_init_colocated_via_git_repo_path_symlink_directory() {
401 let test_env = TestEnvironment::default();
402 // <jj_work_dir>/.git -> <git_repo_path>
403 let git_repo_path = test_env.env_root().join("git-repo");
404 init_git_repo(&git_repo_path, false);
405 let jj_work_dir = test_env.work_dir("").create_dir("repo");
406 std::os::unix::fs::symlink(git_repo_path.join(".git"), jj_work_dir.root().join(".git"))
407 .unwrap();
408 let output = jj_work_dir.run_jj(["git", "init", "--git-repo", "."]);
409 insta::assert_snapshot!(output, @r#"
410 ------- stderr -------
411 Done importing changes from the underlying Git repo.
412 Initialized repo in "."
413 [EOF]
414 "#);
415 insta::assert_snapshot!(read_git_target(&jj_work_dir), @"../../../.git");
416
417 // Check that the Git repo's HEAD got checked out
418 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
419 @ f3fe58bc88cc
420 ○ e80a42cccd06 my-bookmark git_head() My commit message
421 ◆ 000000000000
422 [EOF]
423 ");
424
425 // Check that the Git repo's HEAD moves
426 jj_work_dir.run_jj(["new"]).success();
427 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
428 @ 0c77f9e21b55
429 ○ f3fe58bc88cc git_head()
430 ○ e80a42cccd06 my-bookmark My commit message
431 ◆ 000000000000
432 [EOF]
433 ");
434}
435
436#[cfg(unix)]
437#[test]
438fn test_git_init_colocated_via_git_repo_path_symlink_directory_without_bare_config() {
439 let test_env = TestEnvironment::default();
440 // <jj_work_dir>/.git -> <git_repo_path>
441 let git_repo_path = test_env.env_root().join("git-repo.git");
442 let jj_work_dir = test_env.work_dir("repo");
443 // Set up git repo without core.bare set (as the "repo" tool would do.)
444 // The core.bare config is deduced from the directory name.
445 let git_repo = init_git_repo(jj_work_dir.root(), false);
446 git::remove_config_value(git_repo, "config", "bare");
447
448 std::fs::rename(jj_work_dir.root().join(".git"), &git_repo_path).unwrap();
449 std::os::unix::fs::symlink(&git_repo_path, jj_work_dir.root().join(".git")).unwrap();
450 let output = jj_work_dir.run_jj(["git", "init", "--git-repo", "."]);
451 insta::assert_snapshot!(output, @r#"
452 ------- stderr -------
453 Done importing changes from the underlying Git repo.
454 Initialized repo in "."
455 [EOF]
456 "#);
457 insta::assert_snapshot!(read_git_target(&jj_work_dir), @"../../../.git");
458
459 // Check that the Git repo's HEAD got checked out
460 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
461 @ f3fe58bc88cc
462 ○ e80a42cccd06 my-bookmark git_head() My commit message
463 ◆ 000000000000
464 [EOF]
465 ");
466
467 // Check that the Git repo's HEAD moves
468 jj_work_dir.run_jj(["new"]).success();
469 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
470 @ 0c77f9e21b55
471 ○ f3fe58bc88cc git_head()
472 ○ e80a42cccd06 my-bookmark My commit message
473 ◆ 000000000000
474 [EOF]
475 ");
476}
477
478#[cfg(unix)]
479#[test]
480fn test_git_init_colocated_via_git_repo_path_symlink_gitlink() {
481 let test_env = TestEnvironment::default();
482 // <jj_work_dir>/.git -> <git_workdir_path>/.git -> <git_repo_path>
483 let git_repo_path = test_env.env_root().join("git-repo");
484 let git_workdir_path = test_env.env_root().join("git-workdir");
485 let git_repo = init_git_repo(&git_repo_path, false);
486 std::fs::create_dir(&git_workdir_path).unwrap();
487 git::create_gitlink(&git_workdir_path, git_repo.path());
488 assert!(git_workdir_path.join(".git").is_file());
489 let jj_work_dir = test_env.work_dir("").create_dir("repo");
490 std::os::unix::fs::symlink(
491 git_workdir_path.join(".git"),
492 jj_work_dir.root().join(".git"),
493 )
494 .unwrap();
495 let output = jj_work_dir.run_jj(["git", "init", "--git-repo", "."]);
496 insta::assert_snapshot!(output, @r#"
497 ------- stderr -------
498 Done importing changes from the underlying Git repo.
499 Initialized repo in "."
500 [EOF]
501 "#);
502 insta::assert_snapshot!(read_git_target(&jj_work_dir), @"../../../.git");
503
504 // Check that the Git repo's HEAD got checked out
505 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
506 @ f3fe58bc88cc
507 ○ e80a42cccd06 my-bookmark git_head() My commit message
508 ◆ 000000000000
509 [EOF]
510 ");
511
512 // Check that the Git repo's HEAD moves
513 jj_work_dir.run_jj(["new"]).success();
514 insta::assert_snapshot!(get_log_output(&jj_work_dir), @r"
515 @ 0c77f9e21b55
516 ○ f3fe58bc88cc git_head()
517 ○ e80a42cccd06 my-bookmark My commit message
518 ◆ 000000000000
519 [EOF]
520 ");
521}
522
523#[test]
524fn test_git_init_colocated_via_git_repo_path_imported_refs() {
525 let test_env = TestEnvironment::default();
526 test_env.add_config("git.auto-local-bookmark = true");
527
528 // Set up remote refs
529 test_env.run_jj_in(".", ["git", "init", "remote"]).success();
530 let remote_dir = test_env.work_dir("remote");
531 remote_dir
532 .run_jj(["bookmark", "create", "-r@", "local-remote", "remote-only"])
533 .success();
534 remote_dir.run_jj(["new"]).success();
535 remote_dir.run_jj(["git", "export"]).success();
536
537 let remote_git_path = remote_dir
538 .root()
539 .join(PathBuf::from_iter([".jj", "repo", "store", "git"]));
540 let set_up_local_repo = |local_path: &Path| {
541 let git_repo = git::clone(local_path, remote_git_path.to_str().unwrap(), None);
542 let git_ref = git_repo
543 .find_reference("refs/remotes/origin/local-remote")
544 .unwrap();
545 git_repo
546 .reference(
547 "refs/heads/local-remote",
548 git_ref.target().id().to_owned(),
549 gix::refs::transaction::PreviousValue::MustNotExist,
550 "move local-remote bookmark",
551 )
552 .unwrap();
553 };
554
555 // With git.auto-local-bookmark = true
556 let local_dir = test_env.work_dir("local1");
557 set_up_local_repo(local_dir.root());
558 let output = local_dir.run_jj(["git", "init", "--git-repo=."]);
559 insta::assert_snapshot!(output, @r#"
560 ------- stderr -------
561 Done importing changes from the underlying Git repo.
562 Initialized repo in "."
563 [EOF]
564 "#);
565 insta::assert_snapshot!(get_bookmark_output(&local_dir), @r"
566 local-remote: qpvuntsm e8849ae1 (empty) (no description set)
567 @git: qpvuntsm e8849ae1 (empty) (no description set)
568 @origin: qpvuntsm e8849ae1 (empty) (no description set)
569 remote-only: qpvuntsm e8849ae1 (empty) (no description set)
570 @git: qpvuntsm e8849ae1 (empty) (no description set)
571 @origin: qpvuntsm e8849ae1 (empty) (no description set)
572 [EOF]
573 ");
574
575 // With git.auto-local-bookmark = false
576 test_env.add_config("git.auto-local-bookmark = false");
577 let local_dir = test_env.work_dir("local2");
578 set_up_local_repo(local_dir.root());
579 let output = local_dir.run_jj(["git", "init", "--git-repo=."]);
580 insta::assert_snapshot!(output, @r#"
581 ------- stderr -------
582 Done importing changes from the underlying Git repo.
583 Hint: The following remote bookmarks aren't associated with the existing local bookmarks:
584 local-remote@origin
585 Hint: Run the following command to keep local bookmarks updated on future pulls:
586 jj bookmark track local-remote@origin
587 Initialized repo in "."
588 [EOF]
589 "#);
590 insta::assert_snapshot!(get_bookmark_output(&local_dir), @r"
591 local-remote: qpvuntsm e8849ae1 (empty) (no description set)
592 @git: qpvuntsm e8849ae1 (empty) (no description set)
593 local-remote@origin: qpvuntsm e8849ae1 (empty) (no description set)
594 remote-only@origin: qpvuntsm e8849ae1 (empty) (no description set)
595 [EOF]
596 ");
597}
598
599#[test]
600fn test_git_init_colocated_dirty_working_copy() {
601 let test_env = TestEnvironment::default();
602 let work_dir = test_env.work_dir("repo");
603 let git_repo = init_git_repo(work_dir.root(), false);
604
605 let mut index_manager = git::IndexManager::new(&git_repo);
606
607 index_manager.add_file("new-staged-file", b"new content");
608 index_manager.add_file("some-file", b"new content");
609 index_manager.sync_index();
610
611 work_dir.write_file("unstaged-file", "new content");
612 insta::assert_debug_snapshot!(git::status(&git_repo), @r#"
613 [
614 GitStatus {
615 path: "new-staged-file",
616 status: Index(
617 Addition,
618 ),
619 },
620 GitStatus {
621 path: "some-file",
622 status: Index(
623 Modification,
624 ),
625 },
626 GitStatus {
627 path: "unstaged-file",
628 status: Worktree(
629 Added,
630 ),
631 },
632 ]
633 "#);
634
635 let output = work_dir.run_jj(["git", "init", "--git-repo", "."]);
636 insta::assert_snapshot!(output, @r#"
637 ------- stderr -------
638 Done importing changes from the underlying Git repo.
639 Initialized repo in "."
640 [EOF]
641 "#);
642
643 // Working-copy changes should have been snapshotted.
644 let output = work_dir.run_jj(["log", "-s", "--ignore-working-copy"]);
645 insta::assert_snapshot!(output, @r"
646 @ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 6efc2a53
647 │ (no description set)
648 │ C {some-file => new-staged-file}
649 │ M some-file
650 │ C {some-file => unstaged-file}
651 ○ nntyzxmz someone@example.org 1970-01-01 11:00:00 my-bookmark git_head() e80a42cc
652 │ My commit message
653 │ A some-file
654 ◆ zzzzzzzz root() 00000000
655 [EOF]
656 ");
657
658 // Git index should be consistent with the working copy parent. With the
659 // current implementation, the index is unchanged. Since jj created new
660 // working copy commit, it's also okay to update the index reflecting the
661 // working copy commit or the working copy parent.
662 insta::assert_debug_snapshot!(git::status(&git_repo), @r#"
663 [
664 GitStatus {
665 path: ".jj/.gitignore",
666 status: Worktree(
667 Ignored,
668 ),
669 },
670 GitStatus {
671 path: ".jj/repo",
672 status: Worktree(
673 Ignored,
674 ),
675 },
676 GitStatus {
677 path: ".jj/working_copy",
678 status: Worktree(
679 Ignored,
680 ),
681 },
682 GitStatus {
683 path: "new-staged-file",
684 status: Index(
685 Addition,
686 ),
687 },
688 GitStatus {
689 path: "some-file",
690 status: Index(
691 Modification,
692 ),
693 },
694 GitStatus {
695 path: "unstaged-file",
696 status: Worktree(
697 IntentToAdd,
698 ),
699 },
700 ]
701 "#);
702}
703
704#[test]
705fn test_git_init_colocated_ignore_working_copy() {
706 let test_env = TestEnvironment::default();
707 let work_dir = test_env.work_dir("repo");
708 init_git_repo(work_dir.root(), false);
709 work_dir.write_file("file1", "");
710
711 let output = work_dir.run_jj(["git", "init", "--ignore-working-copy", "--colocate"]);
712 insta::assert_snapshot!(output, @r"
713 ------- stderr -------
714 Error: --ignore-working-copy is not respected
715 [EOF]
716 [exit status: 2]
717 ");
718}
719
720#[test]
721fn test_git_init_colocated_at_operation() {
722 let test_env = TestEnvironment::default();
723 let work_dir = test_env.work_dir("repo");
724 init_git_repo(work_dir.root(), false);
725
726 let output = work_dir.run_jj(["git", "init", "--at-op=@-", "--colocate"]);
727 insta::assert_snapshot!(output, @r"
728 ------- stderr -------
729 Error: --at-op is not respected
730 [EOF]
731 [exit status: 2]
732 ");
733}
734
735#[test]
736fn test_git_init_external_but_git_dir_exists() {
737 let test_env = TestEnvironment::default();
738 let git_repo_path = test_env.env_root().join("git-repo");
739 let work_dir = test_env.work_dir("repo");
740 git::init(&git_repo_path);
741 init_git_repo(work_dir.root(), false);
742 let output = work_dir.run_jj(["git", "init", "--git-repo", git_repo_path.to_str().unwrap()]);
743 insta::assert_snapshot!(output, @r#"
744 ------- stderr -------
745 Initialized repo in "."
746 [EOF]
747 "#);
748
749 // The local ".git" repository is unrelated, so no commits should be imported
750 insta::assert_snapshot!(get_log_output(&work_dir), @r"
751 @ e8849ae12c70
752 ◆ 000000000000
753 [EOF]
754 ");
755
756 // Check that Git HEAD is not set because this isn't a colocated repo
757 work_dir.run_jj(["new"]).success();
758 insta::assert_snapshot!(get_log_output(&work_dir), @r"
759 @ 1c1c95df80e5
760 ○ e8849ae12c70
761 ◆ 000000000000
762 [EOF]
763 ");
764}
765
766#[test]
767fn test_git_init_colocated_via_flag_git_dir_exists() {
768 let test_env = TestEnvironment::default();
769 let work_dir = test_env.work_dir("repo");
770 init_git_repo(work_dir.root(), false);
771
772 let output = test_env.run_jj_in(".", ["git", "init", "--colocate", "repo"]);
773 insta::assert_snapshot!(output, @r#"
774 ------- stderr -------
775 Done importing changes from the underlying Git repo.
776 Initialized repo in "repo"
777 [EOF]
778 "#);
779
780 // Check that the Git repo's HEAD got checked out
781 insta::assert_snapshot!(get_log_output(&work_dir), @r"
782 @ f3fe58bc88cc
783 ○ e80a42cccd06 my-bookmark git_head() My commit message
784 ◆ 000000000000
785 [EOF]
786 ");
787
788 // Check that the Git repo's HEAD moves
789 work_dir.run_jj(["new"]).success();
790 insta::assert_snapshot!(get_log_output(&work_dir), @r"
791 @ 0c77f9e21b55
792 ○ f3fe58bc88cc git_head()
793 ○ e80a42cccd06 my-bookmark My commit message
794 ◆ 000000000000
795 [EOF]
796 ");
797}
798
799#[test]
800fn test_git_init_colocated_via_flag_git_dir_not_exists() {
801 let test_env = TestEnvironment::default();
802 let work_dir = test_env.work_dir("repo");
803 let output = test_env.run_jj_in(".", ["git", "init", "--colocate", "repo"]);
804 insta::assert_snapshot!(output, @r#"
805 ------- stderr -------
806 Initialized repo in "repo"
807 [EOF]
808 "#);
809 // No HEAD ref is available yet
810 insta::assert_snapshot!(get_log_output(&work_dir), @r"
811 @ e8849ae12c70
812 ◆ 000000000000
813 [EOF]
814 ");
815
816 // Create the default bookmark (create both in case we change the default)
817 work_dir
818 .run_jj(["bookmark", "create", "-r@", "main", "master"])
819 .success();
820
821 // If .git/HEAD pointed to the default bookmark, new working-copy commit would
822 // be created on top.
823 insta::assert_snapshot!(get_log_output(&work_dir), @r"
824 @ e8849ae12c70 main master
825 ◆ 000000000000
826 [EOF]
827 ");
828}
829
830#[test]
831fn test_git_init_conditional_config() {
832 let test_env = TestEnvironment::default();
833 let old_workspace_dir = test_env.work_dir("old");
834 let new_workspace_dir = test_env.work_dir("new");
835
836 let run_jj = |work_dir: &TestWorkDir, args: &[&str]| {
837 work_dir.run_jj_with(|cmd| {
838 cmd.args(args)
839 .env_remove("JJ_EMAIL")
840 .env_remove("JJ_OP_HOSTNAME")
841 .env_remove("JJ_OP_USERNAME")
842 })
843 };
844 let log_template = r#"separate(' ', author.email(), description.first_line()) ++ "\n""#;
845 let op_log_template = r#"separate(' ', user, description.first_line()) ++ "\n""#;
846
847 // Override user.email and operation.username conditionally
848 test_env.add_config(formatdoc! {"
849 user.email = 'base@example.org'
850 operation.hostname = 'base'
851 operation.username = 'base'
852 [[--scope]]
853 --when.repositories = [{new_workspace_root}]
854 user.email = 'new-repo@example.org'
855 operation.username = 'new-repo'
856 ",
857 new_workspace_root = to_toml_value(new_workspace_dir.root().to_str().unwrap()),
858 });
859
860 // Override operation.hostname by repo config, which should be loaded into
861 // the command settings, but shouldn't be copied to the new repo.
862 run_jj(&test_env.work_dir(""), &["git", "init", "old"]).success();
863 run_jj(
864 &old_workspace_dir,
865 &["config", "set", "--repo", "operation.hostname", "old-repo"],
866 )
867 .success();
868 run_jj(&old_workspace_dir, &["new"]).success();
869 let output = run_jj(&old_workspace_dir, &["op", "log", "-T", op_log_template]);
870 insta::assert_snapshot!(output, @r"
871 @ base@old-repo new empty commit
872 ○ base@base add workspace 'default'
873 ○ @
874 [EOF]
875 ");
876
877 // Create new repo at the old workspace directory.
878 let output = run_jj(&old_workspace_dir, &["git", "init", "../new"]);
879 insta::assert_snapshot!(output.normalize_backslash(), @r#"
880 ------- stderr -------
881 Initialized repo in "../new"
882 [EOF]
883 "#);
884 run_jj(&new_workspace_dir, &["new"]).success();
885 let output = run_jj(&new_workspace_dir, &["log", "-T", log_template]);
886 insta::assert_snapshot!(output, @r"
887 @ new-repo@example.org
888 ○ new-repo@example.org
889 ◆
890 [EOF]
891 ");
892 let output = run_jj(&new_workspace_dir, &["op", "log", "-T", op_log_template]);
893 insta::assert_snapshot!(output, @r"
894 @ new-repo@base new empty commit
895 ○ new-repo@base add workspace 'default'
896 ○ @
897 [EOF]
898 ");
899}
900
901#[test]
902fn test_git_init_bad_wc_path() {
903 let test_env = TestEnvironment::default();
904 std::fs::write(test_env.env_root().join("existing-file"), b"").unwrap();
905 let output = test_env.run_jj_in(".", ["git", "init", "existing-file"]);
906 insta::assert_snapshot!(output.strip_stderr_last_line(), @r"
907 ------- stderr -------
908 Error: Failed to create workspace
909 [EOF]
910 [exit status: 1]
911 ");
912}