just playing with tangled
1// Copyright 2020 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, PathBuf};
16
17use test_case::test_case;
18
19use crate::common::{strip_last_line, TestEnvironment};
20
21fn init_git_repo(git_repo_path: &Path, bare: bool) -> git2::Repository {
22 init_git_repo_with_opts(git_repo_path, git2::RepositoryInitOptions::new().bare(bare))
23}
24
25fn init_git_repo_with_opts(
26 git_repo_path: &Path,
27 opts: &git2::RepositoryInitOptions,
28) -> git2::Repository {
29 let git_repo = git2::Repository::init_opts(git_repo_path, opts).unwrap();
30 let git_blob_oid = git_repo.blob(b"some content").unwrap();
31 let mut git_tree_builder = git_repo.treebuilder(None).unwrap();
32 git_tree_builder
33 .insert("some-file", git_blob_oid, 0o100644)
34 .unwrap();
35 let git_tree_id = git_tree_builder.write().unwrap();
36 drop(git_tree_builder);
37 let git_tree = git_repo.find_tree(git_tree_id).unwrap();
38 let git_signature = git2::Signature::new(
39 "Git User",
40 "git.user@example.com",
41 &git2::Time::new(123, 60),
42 )
43 .unwrap();
44 git_repo
45 .commit(
46 Some("refs/heads/my-branch"),
47 &git_signature,
48 &git_signature,
49 "My commit message",
50 &git_tree,
51 &[],
52 )
53 .unwrap();
54 drop(git_tree);
55 git_repo.set_head("refs/heads/my-branch").unwrap();
56 git_repo
57}
58
59fn get_branch_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
60 test_env.jj_cmd_success(repo_path, &["branch", "list", "--all-remotes"])
61}
62
63fn read_git_target(workspace_root: &Path) -> String {
64 let mut path = workspace_root.to_path_buf();
65 path.extend([".jj", "repo", "store", "git_target"]);
66 std::fs::read_to_string(path).unwrap()
67}
68
69#[test]
70fn test_init_git_internal() {
71 let test_env = TestEnvironment::default();
72 let (stdout, stderr) = test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
73 insta::assert_snapshot!(stdout, @"");
74 insta::assert_snapshot!(stderr, @r###"
75 Warning: `--git` and `--git-repo` are deprecated.
76 Use `jj git init` instead
77 Initialized repo in "repo"
78 "###);
79
80 let workspace_root = test_env.env_root().join("repo");
81 let jj_path = workspace_root.join(".jj");
82 let repo_path = jj_path.join("repo");
83 let store_path = repo_path.join("store");
84 assert!(workspace_root.is_dir());
85 assert!(jj_path.is_dir());
86 assert!(jj_path.join("working_copy").is_dir());
87 assert!(repo_path.is_dir());
88 assert!(store_path.is_dir());
89 assert!(store_path.join("git").is_dir());
90 assert_eq!(read_git_target(&workspace_root), "git");
91}
92
93#[test_case(false; "full")]
94#[test_case(true; "bare")]
95fn test_init_git_external(bare: bool) {
96 let test_env = TestEnvironment::default();
97 let git_repo_path = test_env.env_root().join("git-repo");
98 init_git_repo(&git_repo_path, bare);
99
100 let (stdout, stderr) = test_env.jj_cmd_ok(
101 test_env.env_root(),
102 &[
103 "init",
104 "repo",
105 "--git-repo",
106 git_repo_path.to_str().unwrap(),
107 ],
108 );
109 insta::allow_duplicates! {
110 insta::assert_snapshot!(stdout, @"");
111 insta::assert_snapshot!(stderr, @r###"
112 Done importing changes from the underlying Git repo.
113 Working copy now at: sqpuoqvx f6950fc1 (empty) (no description set)
114 Parent commit : mwrttmos 8d698d4a my-branch | My commit message
115 Added 1 files, modified 0 files, removed 0 files
116 Warning: `--git` and `--git-repo` are deprecated.
117 Use `jj git init` instead
118 Initialized repo in "repo"
119 "###);
120 }
121
122 let workspace_root = test_env.env_root().join("repo");
123 let jj_path = workspace_root.join(".jj");
124 let repo_path = jj_path.join("repo");
125 let store_path = repo_path.join("store");
126 assert!(workspace_root.is_dir());
127 assert!(jj_path.is_dir());
128 assert!(jj_path.join("working_copy").is_dir());
129 assert!(repo_path.is_dir());
130 assert!(store_path.is_dir());
131 let unix_git_target_file_contents = read_git_target(&workspace_root).replace('\\', "/");
132 if bare {
133 assert!(unix_git_target_file_contents.ends_with("/git-repo"));
134 } else {
135 assert!(unix_git_target_file_contents.ends_with("/git-repo/.git"));
136 }
137
138 // Check that the Git repo's HEAD got checked out
139 let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-r", "@-"]);
140 insta::allow_duplicates! {
141 insta::assert_snapshot!(stdout, @r###"
142 ◉ mwrttmos git.user@example.com 1970-01-01 11:02:03 my-branch HEAD@git 8d698d4a
143 │ My commit message
144 ~
145 "###);
146 }
147}
148
149#[test]
150fn test_init_git_external_non_existent_directory() {
151 let test_env = TestEnvironment::default();
152 let stderr = test_env.jj_cmd_failure(
153 test_env.env_root(),
154 &["init", "repo", "--git-repo", "non-existent"],
155 );
156 insta::assert_snapshot!(strip_last_line(&stderr), @r###"
157 Error: Failed to access the repository
158 Caused by:
159 1: Cannot access $TEST_ENV/non-existent
160 "###);
161}
162
163#[test]
164fn test_init_git_external_non_existent_git_directory() {
165 let test_env = TestEnvironment::default();
166 let workspace_root = test_env.env_root().join("repo");
167 let stderr =
168 test_env.jj_cmd_failure(test_env.env_root(), &["init", "repo", "--git-repo", "repo"]);
169
170 insta::assert_snapshot!(&stderr, @r###"
171 Error: Failed to access the repository
172 Caused by:
173 1: Failed to open git repository
174 2: "$TEST_ENV/repo" does not appear to be a git repository
175 3: Missing HEAD at '.git/HEAD'
176 "###);
177 let jj_path = workspace_root.join(".jj");
178 assert!(!jj_path.exists());
179}
180
181#[test]
182fn test_init_git_colocated() {
183 let test_env = TestEnvironment::default();
184 let workspace_root = test_env.env_root().join("repo");
185 init_git_repo(&workspace_root, false);
186 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]);
187 insta::assert_snapshot!(stdout, @"");
188 insta::assert_snapshot!(stderr, @r###"
189 Done importing changes from the underlying Git repo.
190 Warning: `--git` and `--git-repo` are deprecated.
191 Use `jj git init` instead
192 Initialized repo in "."
193 "###);
194
195 let jj_path = workspace_root.join(".jj");
196 let repo_path = jj_path.join("repo");
197 let store_path = repo_path.join("store");
198 assert!(workspace_root.is_dir());
199 assert!(jj_path.is_dir());
200 assert!(jj_path.join("working_copy").is_dir());
201 assert!(repo_path.is_dir());
202 assert!(store_path.is_dir());
203 assert!(read_git_target(&workspace_root)
204 .replace('\\', "/")
205 .ends_with("../../../.git"));
206
207 // Check that the Git repo's HEAD got checked out
208 let stdout = test_env.jj_cmd_success(&repo_path, &["log", "-r", "@-"]);
209 insta::assert_snapshot!(stdout, @r###"
210 ◉ mwrttmos git.user@example.com 1970-01-01 11:02:03 my-branch HEAD@git 8d698d4a
211 │ My commit message
212 ~
213 "###);
214
215 // Check that the Git repo's HEAD moves
216 test_env.jj_cmd_ok(&workspace_root, &["new"]);
217 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
218 insta::assert_snapshot!(stdout, @r###"
219 ◉ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 HEAD@git f61b77cd
220 │ (no description set)
221 ~
222 "###);
223}
224
225#[test]
226fn test_init_git_colocated_gitlink() {
227 let test_env = TestEnvironment::default();
228 // <workspace_root>/.git -> <git_repo_path>
229 let git_repo_path = test_env.env_root().join("git-repo");
230 let workspace_root = test_env.env_root().join("repo");
231 init_git_repo_with_opts(
232 &git_repo_path,
233 git2::RepositoryInitOptions::new().workdir_path(&workspace_root),
234 );
235 assert!(workspace_root.join(".git").is_file());
236 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]);
237 insta::assert_snapshot!(stdout, @"");
238 insta::assert_snapshot!(stderr, @r###"
239 Done importing changes from the underlying Git repo.
240 Warning: `--git` and `--git-repo` are deprecated.
241 Use `jj git init` instead
242 Initialized repo in "."
243 "###);
244 insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git");
245
246 // Check that the Git repo's HEAD got checked out
247 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
248 insta::assert_snapshot!(stdout, @r###"
249 ◉ mwrttmos git.user@example.com 1970-01-01 11:02:03 my-branch HEAD@git 8d698d4a
250 │ My commit message
251 ~
252 "###);
253
254 // Check that the Git repo's HEAD moves
255 test_env.jj_cmd_ok(&workspace_root, &["new"]);
256 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
257 insta::assert_snapshot!(stdout, @r###"
258 ◉ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 HEAD@git f61b77cd
259 │ (no description set)
260 ~
261 "###);
262}
263
264#[cfg(unix)]
265#[test]
266fn test_init_git_colocated_symlink_directory() {
267 let test_env = TestEnvironment::default();
268 // <workspace_root>/.git -> <git_repo_path>
269 let git_repo_path = test_env.env_root().join("git-repo");
270 let workspace_root = test_env.env_root().join("repo");
271 init_git_repo(&git_repo_path, false);
272 std::fs::create_dir(&workspace_root).unwrap();
273 std::os::unix::fs::symlink(git_repo_path.join(".git"), workspace_root.join(".git")).unwrap();
274 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]);
275 insta::assert_snapshot!(stdout, @"");
276 insta::assert_snapshot!(stderr, @r###"
277 Done importing changes from the underlying Git repo.
278 Warning: `--git` and `--git-repo` are deprecated.
279 Use `jj git init` instead
280 Initialized repo in "."
281 "###);
282 insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git");
283
284 // Check that the Git repo's HEAD got checked out
285 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
286 insta::assert_snapshot!(stdout, @r###"
287 ◉ mwrttmos git.user@example.com 1970-01-01 11:02:03 my-branch HEAD@git 8d698d4a
288 │ My commit message
289 ~
290 "###);
291
292 // Check that the Git repo's HEAD moves
293 test_env.jj_cmd_ok(&workspace_root, &["new"]);
294 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
295 insta::assert_snapshot!(stdout, @r###"
296 ◉ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 HEAD@git f61b77cd
297 │ (no description set)
298 ~
299 "###);
300}
301
302#[cfg(unix)]
303#[test]
304fn test_init_git_colocated_symlink_directory_without_bare_config() {
305 let test_env = TestEnvironment::default();
306 // <workspace_root>/.git -> <git_repo_path>
307 let git_repo_path = test_env.env_root().join("git-repo.git");
308 let workspace_root = test_env.env_root().join("repo");
309 // Set up git repo without core.bare set (as the "repo" tool would do.)
310 // The core.bare config is deduced from the directory name.
311 let git_repo = init_git_repo(&workspace_root, false);
312 git_repo.config().unwrap().remove("core.bare").unwrap();
313 std::fs::rename(workspace_root.join(".git"), &git_repo_path).unwrap();
314 std::os::unix::fs::symlink(&git_repo_path, workspace_root.join(".git")).unwrap();
315 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]);
316 insta::assert_snapshot!(stdout, @"");
317 insta::assert_snapshot!(stderr, @r###"
318 Done importing changes from the underlying Git repo.
319 Warning: `--git` and `--git-repo` are deprecated.
320 Use `jj git init` instead
321 Initialized repo in "."
322 "###);
323 insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git");
324
325 // Check that the Git repo's HEAD got checked out
326 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
327 insta::assert_snapshot!(stdout, @r###"
328 ◉ mwrttmos git.user@example.com 1970-01-01 11:02:03 my-branch HEAD@git 8d698d4a
329 │ My commit message
330 ~
331 "###);
332
333 // Check that the Git repo's HEAD moves
334 test_env.jj_cmd_ok(&workspace_root, &["new"]);
335 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
336 insta::assert_snapshot!(stdout, @r###"
337 ◉ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 HEAD@git f61b77cd
338 │ (no description set)
339 ~
340 "###);
341}
342
343#[cfg(unix)]
344#[test]
345fn test_init_git_colocated_symlink_gitlink() {
346 let test_env = TestEnvironment::default();
347 // <workspace_root>/.git -> <git_workdir_path>/.git -> <git_repo_path>
348 let git_repo_path = test_env.env_root().join("git-repo");
349 let git_workdir_path = test_env.env_root().join("git-workdir");
350 let workspace_root = test_env.env_root().join("repo");
351 init_git_repo_with_opts(
352 &git_repo_path,
353 git2::RepositoryInitOptions::new().workdir_path(&git_workdir_path),
354 );
355 assert!(git_workdir_path.join(".git").is_file());
356 std::fs::create_dir(&workspace_root).unwrap();
357 std::os::unix::fs::symlink(git_workdir_path.join(".git"), workspace_root.join(".git")).unwrap();
358 let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["init", "--git-repo", "."]);
359 insta::assert_snapshot!(stdout, @"");
360 insta::assert_snapshot!(stderr, @r###"
361 Done importing changes from the underlying Git repo.
362 Warning: `--git` and `--git-repo` are deprecated.
363 Use `jj git init` instead
364 Initialized repo in "."
365 "###);
366 insta::assert_snapshot!(read_git_target(&workspace_root), @"../../../.git");
367
368 // Check that the Git repo's HEAD got checked out
369 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
370 insta::assert_snapshot!(stdout, @r###"
371 ◉ mwrttmos git.user@example.com 1970-01-01 11:02:03 my-branch HEAD@git 8d698d4a
372 │ My commit message
373 ~
374 "###);
375
376 // Check that the Git repo's HEAD moves
377 test_env.jj_cmd_ok(&workspace_root, &["new"]);
378 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
379 insta::assert_snapshot!(stdout, @r###"
380 ◉ sqpuoqvx test.user@example.com 2001-02-03 08:05:07 HEAD@git f61b77cd
381 │ (no description set)
382 ~
383 "###);
384}
385
386#[test]
387fn test_init_git_colocated_imported_refs() {
388 let test_env = TestEnvironment::default();
389 test_env.add_config("git.auto-local-branch = true");
390
391 // Set up remote refs
392 test_env.jj_cmd_ok(test_env.env_root(), &["init", "remote", "--git"]);
393 let remote_path = test_env.env_root().join("remote");
394 test_env.jj_cmd_ok(
395 &remote_path,
396 &["branch", "create", "local-remote", "remote-only"],
397 );
398 test_env.jj_cmd_ok(&remote_path, &["new"]);
399 test_env.jj_cmd_ok(&remote_path, &["git", "export"]);
400
401 let remote_git_path = remote_path.join(PathBuf::from_iter([".jj", "repo", "store", "git"]));
402 let set_up_local_repo = |local_path: &Path| {
403 let git_repo =
404 git2::Repository::clone(remote_git_path.to_str().unwrap(), local_path).unwrap();
405 let git_ref = git_repo
406 .find_reference("refs/remotes/origin/local-remote")
407 .unwrap();
408 git_repo
409 .reference(
410 "refs/heads/local-remote",
411 git_ref.target().unwrap(),
412 false,
413 "",
414 )
415 .unwrap();
416 };
417
418 // With git.auto-local-branch = true
419 let local_path = test_env.env_root().join("local1");
420 set_up_local_repo(&local_path);
421 let (_stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["init", "--git-repo=."]);
422 insta::assert_snapshot!(stderr, @r###"
423 Done importing changes from the underlying Git repo.
424 Warning: `--git` and `--git-repo` are deprecated.
425 Use `jj git init` instead
426 Initialized repo in "."
427 "###);
428 insta::assert_snapshot!(get_branch_output(&test_env, &local_path), @r###"
429 local-remote: vvkvtnvv 230dd059 (empty) (no description set)
430 @git: vvkvtnvv 230dd059 (empty) (no description set)
431 @origin: vvkvtnvv 230dd059 (empty) (no description set)
432 remote-only: vvkvtnvv 230dd059 (empty) (no description set)
433 @git: vvkvtnvv 230dd059 (empty) (no description set)
434 @origin: vvkvtnvv 230dd059 (empty) (no description set)
435 "###);
436
437 // With git.auto-local-branch = false
438 test_env.add_config("git.auto-local-branch = false");
439 let local_path = test_env.env_root().join("local2");
440 set_up_local_repo(&local_path);
441 let (_stdout, stderr) = test_env.jj_cmd_ok(&local_path, &["init", "--git-repo=."]);
442 insta::assert_snapshot!(stderr, @r###"
443 Done importing changes from the underlying Git repo.
444 Hint: The following remote branches aren't associated with the existing local branches:
445 local-remote@origin
446 Hint: Run `jj branch track local-remote@origin` to keep local branches updated on future pulls.
447 Warning: `--git` and `--git-repo` are deprecated.
448 Use `jj git init` instead
449 Initialized repo in "."
450 "###);
451 insta::assert_snapshot!(get_branch_output(&test_env, &local_path), @r###"
452 local-remote: vvkvtnvv 230dd059 (empty) (no description set)
453 @git: vvkvtnvv 230dd059 (empty) (no description set)
454 local-remote@origin: vvkvtnvv 230dd059 (empty) (no description set)
455 remote-only@origin: vvkvtnvv 230dd059 (empty) (no description set)
456 "###);
457}
458
459#[test]
460fn test_init_git_external_but_git_dir_exists() {
461 let test_env = TestEnvironment::default();
462 let git_repo_path = test_env.env_root().join("git-repo");
463 let workspace_root = test_env.env_root().join("repo");
464 git2::Repository::init(&git_repo_path).unwrap();
465 init_git_repo(&workspace_root, false);
466 let (stdout, stderr) = test_env.jj_cmd_ok(
467 &workspace_root,
468 &["init", "--git-repo", git_repo_path.to_str().unwrap()],
469 );
470 insta::assert_snapshot!(stdout, @"");
471 insta::assert_snapshot!(stderr, @r###"
472 Warning: `--git` and `--git-repo` are deprecated.
473 Use `jj git init` instead
474 Initialized repo in "."
475 "###);
476
477 // The local ".git" repository is unrelated, so no commits should be imported
478 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
479 insta::assert_snapshot!(stdout, @r###"
480 ◉ zzzzzzzz root() 00000000
481 "###);
482
483 // Check that Git HEAD is not set because this isn't a colocated repo
484 test_env.jj_cmd_ok(&workspace_root, &["new"]);
485 let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-r", "@-"]);
486 insta::assert_snapshot!(stdout, @r###"
487 ◉ qpvuntsm test.user@example.com 2001-02-03 08:05:07 230dd059
488 │ (empty) (no description set)
489 ~
490 "###);
491}
492
493#[test]
494fn test_init_git_internal_must_be_colocated() {
495 let test_env = TestEnvironment::default();
496 let workspace_root = test_env.env_root().join("repo");
497 init_git_repo(&workspace_root, false);
498
499 let stderr = test_env.jj_cmd_failure(&workspace_root, &["init", "--git"]);
500 insta::assert_snapshot!(stderr, @r###"
501 Error: Did not create a jj repo because there is an existing Git repo in this directory.
502 Hint: To create a repo backed by the existing Git repo, run `jj git init --colocate` instead.
503 "###);
504}
505
506#[test]
507fn test_init_git_bad_wc_path() {
508 let test_env = TestEnvironment::default();
509 std::fs::write(test_env.env_root().join("existing-file"), b"").unwrap();
510 let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["init", "--git", "existing-file"]);
511 assert!(stderr.contains("Failed to create workspace"));
512}
513
514#[test]
515fn test_init_local_disallowed() {
516 let test_env = TestEnvironment::default();
517 let stdout = test_env.jj_cmd_failure(test_env.env_root(), &["init", "repo"]);
518 insta::assert_snapshot!(stdout, @r###"
519 Error: The native backend is disallowed by default.
520 Hint: Did you mean to call `jj git init`?
521 Set `ui.allow-init-native` to allow initializing a repo with the native backend.
522 "###);
523}
524
525#[test]
526fn test_init_local() {
527 let test_env = TestEnvironment::default();
528 test_env.add_config(r#"ui.allow-init-native = true"#);
529 let (stdout, stderr) = test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo"]);
530 insta::assert_snapshot!(stdout, @"");
531 insta::assert_snapshot!(stderr, @r###"
532 Initialized repo in "repo"
533 "###);
534
535 let workspace_root = test_env.env_root().join("repo");
536 let jj_path = workspace_root.join(".jj");
537 let repo_path = jj_path.join("repo");
538 let store_path = repo_path.join("store");
539 assert!(workspace_root.is_dir());
540 assert!(jj_path.is_dir());
541 assert!(jj_path.join("working_copy").is_dir());
542 assert!(repo_path.is_dir());
543 assert!(store_path.is_dir());
544 assert!(store_path.join("commits").is_dir());
545 assert!(store_path.join("trees").is_dir());
546 assert!(store_path.join("files").is_dir());
547 assert!(store_path.join("symlinks").is_dir());
548 assert!(store_path.join("conflicts").is_dir());
549}