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 0bd37cef (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 @ 0bd37cef2051
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 0bd37cef (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 @ 5f169ecc57b8
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 @ 62eda98b5eb4
353 ○ 5f169ecc57b8 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 @ 5f169ecc57b8
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 @ 62eda98b5eb4
391 ○ 5f169ecc57b8 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 @ 5f169ecc57b8
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 @ 62eda98b5eb4
429 ○ 5f169ecc57b8 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 @ 5f169ecc57b8
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 @ 62eda98b5eb4
471 ○ 5f169ecc57b8 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 @ 5f169ecc57b8
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 @ 62eda98b5eb4
516 ○ 5f169ecc57b8 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: vvkvtnvv 230dd059 (empty) (no description set)
567 @git: vvkvtnvv 230dd059 (empty) (no description set)
568 @origin: vvkvtnvv 230dd059 (empty) (no description set)
569 remote-only: vvkvtnvv 230dd059 (empty) (no description set)
570 @git: vvkvtnvv 230dd059 (empty) (no description set)
571 @origin: vvkvtnvv 230dd059 (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 `jj bookmark track local-remote@origin` to keep local bookmarks updated on future pulls.
586 Initialized repo in "."
587 [EOF]
588 "#);
589 insta::assert_snapshot!(get_bookmark_output(&local_dir), @r"
590 local-remote: vvkvtnvv 230dd059 (empty) (no description set)
591 @git: vvkvtnvv 230dd059 (empty) (no description set)
592 local-remote@origin: vvkvtnvv 230dd059 (empty) (no description set)
593 remote-only@origin: vvkvtnvv 230dd059 (empty) (no description set)
594 [EOF]
595 ");
596}
597
598#[test]
599fn test_git_init_colocated_dirty_working_copy() {
600 let test_env = TestEnvironment::default();
601 let work_dir = test_env.work_dir("repo");
602 let git_repo = init_git_repo(work_dir.root(), false);
603
604 let mut index_manager = git::IndexManager::new(&git_repo);
605
606 index_manager.add_file("new-staged-file", b"new content");
607 index_manager.add_file("some-file", b"new content");
608 index_manager.sync_index();
609
610 work_dir.write_file("unstaged-file", "new content");
611 insta::assert_debug_snapshot!(git::status(&git_repo), @r#"
612 [
613 GitStatus {
614 path: "new-staged-file",
615 status: Index(
616 Addition,
617 ),
618 },
619 GitStatus {
620 path: "some-file",
621 status: Index(
622 Modification,
623 ),
624 },
625 GitStatus {
626 path: "unstaged-file",
627 status: Worktree(
628 Added,
629 ),
630 },
631 ]
632 "#);
633
634 let output = work_dir.run_jj(["git", "init", "--git-repo", "."]);
635 insta::assert_snapshot!(output, @r#"
636 ------- stderr -------
637 Done importing changes from the underlying Git repo.
638 Initialized repo in "."
639 [EOF]
640 "#);
641
642 // Working-copy changes should have been snapshotted.
643 let output = work_dir.run_jj(["log", "-s", "--ignore-working-copy"]);
644 insta::assert_snapshot!(output, @r"
645 @ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 36dbd9a1
646 │ (no description set)
647 │ C {some-file => new-staged-file}
648 │ M some-file
649 │ C {some-file => unstaged-file}
650 ○ nntyzxmz someone@example.org 1970-01-01 11:00:00 my-bookmark git_head() e80a42cc
651 │ My commit message
652 │ A some-file
653 ◆ zzzzzzzz root() 00000000
654 [EOF]
655 ");
656
657 // Git index should be consistent with the working copy parent. With the
658 // current implementation, the index is unchanged. Since jj created new
659 // working copy commit, it's also okay to update the index reflecting the
660 // working copy commit or the working copy parent.
661 insta::assert_debug_snapshot!(git::status(&git_repo), @r#"
662 [
663 GitStatus {
664 path: ".jj/.gitignore",
665 status: Worktree(
666 Ignored,
667 ),
668 },
669 GitStatus {
670 path: ".jj/repo",
671 status: Worktree(
672 Ignored,
673 ),
674 },
675 GitStatus {
676 path: ".jj/working_copy",
677 status: Worktree(
678 Ignored,
679 ),
680 },
681 GitStatus {
682 path: "new-staged-file",
683 status: Index(
684 Addition,
685 ),
686 },
687 GitStatus {
688 path: "some-file",
689 status: Index(
690 Modification,
691 ),
692 },
693 GitStatus {
694 path: "unstaged-file",
695 status: Worktree(
696 IntentToAdd,
697 ),
698 },
699 ]
700 "#);
701}
702
703#[test]
704fn test_git_init_colocated_ignore_working_copy() {
705 let test_env = TestEnvironment::default();
706 let work_dir = test_env.work_dir("repo");
707 init_git_repo(work_dir.root(), false);
708 work_dir.write_file("file1", "");
709
710 let output = work_dir.run_jj(["git", "init", "--ignore-working-copy", "--colocate"]);
711 insta::assert_snapshot!(output, @r"
712 ------- stderr -------
713 Error: --ignore-working-copy is not respected
714 [EOF]
715 [exit status: 2]
716 ");
717}
718
719#[test]
720fn test_git_init_colocated_at_operation() {
721 let test_env = TestEnvironment::default();
722 let work_dir = test_env.work_dir("repo");
723 init_git_repo(work_dir.root(), false);
724
725 let output = work_dir.run_jj(["git", "init", "--at-op=@-", "--colocate"]);
726 insta::assert_snapshot!(output, @r"
727 ------- stderr -------
728 Error: --at-op is not respected
729 [EOF]
730 [exit status: 2]
731 ");
732}
733
734#[test]
735fn test_git_init_external_but_git_dir_exists() {
736 let test_env = TestEnvironment::default();
737 let git_repo_path = test_env.env_root().join("git-repo");
738 let work_dir = test_env.work_dir("repo");
739 git::init(&git_repo_path);
740 init_git_repo(work_dir.root(), false);
741 let output = work_dir.run_jj(["git", "init", "--git-repo", git_repo_path.to_str().unwrap()]);
742 insta::assert_snapshot!(output, @r#"
743 ------- stderr -------
744 Initialized repo in "."
745 [EOF]
746 "#);
747
748 // The local ".git" repository is unrelated, so no commits should be imported
749 insta::assert_snapshot!(get_log_output(&work_dir), @r"
750 @ 230dd059e1b0
751 ◆ 000000000000
752 [EOF]
753 ");
754
755 // Check that Git HEAD is not set because this isn't a colocated repo
756 work_dir.run_jj(["new"]).success();
757 insta::assert_snapshot!(get_log_output(&work_dir), @r"
758 @ 4db490c88528
759 ○ 230dd059e1b0
760 ◆ 000000000000
761 [EOF]
762 ");
763}
764
765#[test]
766fn test_git_init_colocated_via_flag_git_dir_exists() {
767 let test_env = TestEnvironment::default();
768 let work_dir = test_env.work_dir("repo");
769 init_git_repo(work_dir.root(), false);
770
771 let output = test_env.run_jj_in(".", ["git", "init", "--colocate", "repo"]);
772 insta::assert_snapshot!(output, @r#"
773 ------- stderr -------
774 Done importing changes from the underlying Git repo.
775 Initialized repo in "repo"
776 [EOF]
777 "#);
778
779 // Check that the Git repo's HEAD got checked out
780 insta::assert_snapshot!(get_log_output(&work_dir), @r"
781 @ 5f169ecc57b8
782 ○ e80a42cccd06 my-bookmark git_head() My commit message
783 ◆ 000000000000
784 [EOF]
785 ");
786
787 // Check that the Git repo's HEAD moves
788 work_dir.run_jj(["new"]).success();
789 insta::assert_snapshot!(get_log_output(&work_dir), @r"
790 @ 62eda98b5eb4
791 ○ 5f169ecc57b8 git_head()
792 ○ e80a42cccd06 my-bookmark My commit message
793 ◆ 000000000000
794 [EOF]
795 ");
796}
797
798#[test]
799fn test_git_init_colocated_via_flag_git_dir_not_exists() {
800 let test_env = TestEnvironment::default();
801 let work_dir = test_env.work_dir("repo");
802 let output = test_env.run_jj_in(".", ["git", "init", "--colocate", "repo"]);
803 insta::assert_snapshot!(output, @r#"
804 ------- stderr -------
805 Initialized repo in "repo"
806 [EOF]
807 "#);
808 // No HEAD ref is available yet
809 insta::assert_snapshot!(get_log_output(&work_dir), @r"
810 @ 230dd059e1b0
811 ◆ 000000000000
812 [EOF]
813 ");
814
815 // Create the default bookmark (create both in case we change the default)
816 work_dir
817 .run_jj(["bookmark", "create", "-r@", "main", "master"])
818 .success();
819
820 // If .git/HEAD pointed to the default bookmark, new working-copy commit would
821 // be created on top.
822 insta::assert_snapshot!(get_log_output(&work_dir), @r"
823 @ 230dd059e1b0 main master
824 ◆ 000000000000
825 [EOF]
826 ");
827}
828
829#[test]
830fn test_git_init_conditional_config() {
831 let test_env = TestEnvironment::default();
832 let old_workspace_dir = test_env.work_dir("old");
833 let new_workspace_dir = test_env.work_dir("new");
834
835 let run_jj = |work_dir: &TestWorkDir, args: &[&str]| {
836 work_dir.run_jj_with(|cmd| {
837 cmd.args(args)
838 .env_remove("JJ_EMAIL")
839 .env_remove("JJ_OP_HOSTNAME")
840 .env_remove("JJ_OP_USERNAME")
841 })
842 };
843 let log_template = r#"separate(' ', author.email(), description.first_line()) ++ "\n""#;
844 let op_log_template = r#"separate(' ', user, description.first_line()) ++ "\n""#;
845
846 // Override user.email and operation.username conditionally
847 test_env.add_config(formatdoc! {"
848 user.email = 'base@example.org'
849 operation.hostname = 'base'
850 operation.username = 'base'
851 [[--scope]]
852 --when.repositories = [{new_workspace_root}]
853 user.email = 'new-repo@example.org'
854 operation.username = 'new-repo'
855 ",
856 new_workspace_root = to_toml_value(new_workspace_dir.root().to_str().unwrap()),
857 });
858
859 // Override operation.hostname by repo config, which should be loaded into
860 // the command settings, but shouldn't be copied to the new repo.
861 run_jj(&test_env.work_dir(""), &["git", "init", "old"]).success();
862 run_jj(
863 &old_workspace_dir,
864 &["config", "set", "--repo", "operation.hostname", "old-repo"],
865 )
866 .success();
867 run_jj(&old_workspace_dir, &["new"]).success();
868 let output = run_jj(&old_workspace_dir, &["op", "log", "-T", op_log_template]);
869 insta::assert_snapshot!(output, @r"
870 @ base@old-repo new empty commit
871 ○ base@base add workspace 'default'
872 ○ @
873 [EOF]
874 ");
875
876 // Create new repo at the old workspace directory.
877 let output = run_jj(&old_workspace_dir, &["git", "init", "../new"]);
878 insta::assert_snapshot!(output.normalize_backslash(), @r#"
879 ------- stderr -------
880 Initialized repo in "../new"
881 [EOF]
882 "#);
883 run_jj(&new_workspace_dir, &["new"]).success();
884 let output = run_jj(&new_workspace_dir, &["log", "-T", log_template]);
885 insta::assert_snapshot!(output, @r"
886 @ new-repo@example.org
887 ○ new-repo@example.org
888 ◆
889 [EOF]
890 ");
891 let output = run_jj(&new_workspace_dir, &["op", "log", "-T", op_log_template]);
892 insta::assert_snapshot!(output, @r"
893 @ new-repo@base new empty commit
894 ○ new-repo@base add workspace 'default'
895 ○ @
896 [EOF]
897 ");
898}
899
900#[test]
901fn test_git_init_bad_wc_path() {
902 let test_env = TestEnvironment::default();
903 std::fs::write(test_env.env_root().join("existing-file"), b"").unwrap();
904 let output = test_env.run_jj_in(".", ["git", "init", "existing-file"]);
905 insta::assert_snapshot!(output.strip_stderr_last_line(), @r"
906 ------- stderr -------
907 Error: Failed to create workspace
908 [EOF]
909 [exit status: 1]
910 ");
911}