just playing with tangled
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 crate::common::create_commit_with_files;
16use crate::common::TestEnvironment;
17
18#[test]
19fn test_status_copies() {
20 let test_env = TestEnvironment::default();
21 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
22 let repo_path = test_env.env_root().join("repo");
23
24 std::fs::write(repo_path.join("copy-source"), "copy1\ncopy2\ncopy3\n").unwrap();
25 std::fs::write(repo_path.join("rename-source"), "rename").unwrap();
26 test_env.run_jj_in(&repo_path, ["new"]).success();
27 std::fs::write(
28 repo_path.join("copy-source"),
29 "copy1\ncopy2\ncopy3\nsource\n",
30 )
31 .unwrap();
32 std::fs::write(
33 repo_path.join("copy-target"),
34 "copy1\ncopy2\ncopy3\ntarget\n",
35 )
36 .unwrap();
37 std::fs::remove_file(repo_path.join("rename-source")).unwrap();
38 std::fs::write(repo_path.join("rename-target"), "rename").unwrap();
39
40 let output = test_env.run_jj_in(&repo_path, ["status"]);
41 insta::assert_snapshot!(output, @r"
42 Working copy changes:
43 M copy-source
44 C {copy-source => copy-target}
45 R {rename-source => rename-target}
46 Working copy (@) : rlvkpnrz a96c3086 (no description set)
47 Parent commit (@-): qpvuntsm e3e2c703 (no description set)
48 [EOF]
49 ");
50}
51
52#[test]
53fn test_status_merge() {
54 let test_env = TestEnvironment::default();
55 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
56 let repo_path = test_env.env_root().join("repo");
57
58 std::fs::write(repo_path.join("file"), "base").unwrap();
59 test_env.run_jj_in(&repo_path, ["new", "-m=left"]).success();
60 test_env
61 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "left"])
62 .success();
63 test_env
64 .run_jj_in(&repo_path, ["new", "@-", "-m=right"])
65 .success();
66 std::fs::write(repo_path.join("file"), "right").unwrap();
67 test_env
68 .run_jj_in(&repo_path, ["new", "left", "@"])
69 .success();
70
71 // The output should mention each parent, and the diff should be empty (compared
72 // to the auto-merged parents)
73 let output = test_env.run_jj_in(&repo_path, ["status"]);
74 insta::assert_snapshot!(output, @r"
75 The working copy has no changes.
76 Working copy (@) : mzvwutvl a538c72d (empty) (no description set)
77 Parent commit (@-): rlvkpnrz d3dd19f1 left | (empty) left
78 Parent commit (@-): zsuskuln 705a356d right
79 [EOF]
80 ");
81}
82
83// See https://github.com/jj-vcs/jj/issues/2051.
84#[test]
85fn test_status_ignored_gitignore() {
86 let test_env = TestEnvironment::default();
87 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
88 let repo_path = test_env.env_root().join("repo");
89
90 std::fs::create_dir(repo_path.join("untracked")).unwrap();
91 std::fs::write(repo_path.join("untracked").join("inside_untracked"), "test").unwrap();
92 std::fs::write(
93 repo_path.join("untracked").join(".gitignore"),
94 "!inside_untracked\n",
95 )
96 .unwrap();
97 std::fs::write(repo_path.join(".gitignore"), "untracked/\n!dummy\n").unwrap();
98
99 let output = test_env.run_jj_in(&repo_path, ["status"]);
100 insta::assert_snapshot!(output, @r"
101 Working copy changes:
102 A .gitignore
103 Working copy (@) : qpvuntsm 3cef2183 (no description set)
104 Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
105 [EOF]
106 ");
107}
108
109#[test]
110fn test_status_filtered() {
111 let test_env = TestEnvironment::default();
112 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
113 let repo_path = test_env.env_root().join("repo");
114
115 std::fs::write(repo_path.join("file_1"), "file_1").unwrap();
116 std::fs::write(repo_path.join("file_2"), "file_2").unwrap();
117
118 // The output filtered to file_1 should not list the addition of file_2.
119 let output = test_env.run_jj_in(&repo_path, ["status", "file_1"]);
120 insta::assert_snapshot!(output, @r"
121 Working copy changes:
122 A file_1
123 Working copy (@) : qpvuntsm c8fb8395 (no description set)
124 Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
125 [EOF]
126 ");
127}
128
129// See <https://github.com/jj-vcs/jj/issues/3108>
130// See <https://github.com/jj-vcs/jj/issues/4147>
131#[test]
132fn test_status_display_relevant_working_commit_conflict_hints() {
133 let test_env = TestEnvironment::default();
134 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
135
136 let repo_path = test_env.env_root().join("repo");
137 let conflicted_path = repo_path.join("conflicted.txt");
138
139 // PARENT: Write the initial file
140 std::fs::write(&conflicted_path, "initial contents").unwrap();
141 test_env
142 .run_jj_in(&repo_path, ["describe", "--message", "Initial contents"])
143 .success();
144
145 // CHILD1: New commit on top of <PARENT>
146 test_env
147 .run_jj_in(
148 &repo_path,
149 ["new", "--message", "First part of conflicting change"],
150 )
151 .success();
152 std::fs::write(&conflicted_path, "Child 1").unwrap();
153
154 // CHILD2: New commit also on top of <PARENT>
155 test_env
156 .run_jj_in(
157 &repo_path,
158 [
159 "new",
160 "--message",
161 "Second part of conflicting change",
162 "@-",
163 ],
164 )
165 .success();
166 std::fs::write(&conflicted_path, "Child 2").unwrap();
167
168 // CONFLICT: New commit that is conflicted by merging <CHILD1> and <CHILD2>
169 test_env
170 .run_jj_in(&repo_path, ["new", "--message", "boom", "all:(@-)+"])
171 .success();
172 // Adding more descendants to ensure we correctly find the root ancestors with
173 // conflicts, not just the parents.
174 test_env
175 .run_jj_in(&repo_path, ["new", "--message", "boom-cont"])
176 .success();
177 test_env
178 .run_jj_in(&repo_path, ["new", "--message", "boom-cont-2"])
179 .success();
180
181 let output = test_env.run_jj_in(&repo_path, ["log", "-r", "::"]);
182
183 insta::assert_snapshot!(output, @r"
184 @ yqosqzyt test.user@example.com 2001-02-03 08:05:13 dcb25635 conflict
185 │ (empty) boom-cont-2
186 × royxmykx test.user@example.com 2001-02-03 08:05:12 664a4c6c conflict
187 │ (empty) boom-cont
188 × mzvwutvl test.user@example.com 2001-02-03 08:05:11 c5a4e9cb conflict
189 ├─╮ (empty) boom
190 │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 1e8c2956
191 │ │ First part of conflicting change
192 ○ │ zsuskuln test.user@example.com 2001-02-03 08:05:11 2c8b19fd
193 ├─╯ Second part of conflicting change
194 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 aade7195
195 │ Initial contents
196 ◆ zzzzzzzz root() 00000000
197 [EOF]
198 ");
199
200 let output = test_env.run_jj_in(&repo_path, ["status"]);
201 insta::assert_snapshot!(output, @r"
202 The working copy has no changes.
203 Working copy (@) : yqosqzyt dcb25635 (conflict) (empty) boom-cont-2
204 Parent commit (@-): royxmykx 664a4c6c (conflict) (empty) boom-cont
205 Warning: There are unresolved conflicts at these paths:
206 conflicted.txt 2-sided conflict
207 Hint: To resolve the conflicts, start by updating to the first one:
208 jj new mzvwutvl
209 Then use `jj resolve`, or edit the conflict markers in the file directly.
210 Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
211 Then run `jj squash` to move the resolution into the conflicted commit.
212 [EOF]
213 ");
214
215 let output = test_env.run_jj_in(&repo_path, ["status", "--color=always"]);
216 insta::assert_snapshot!(output, @r"
217 The working copy has no changes.
218 Working copy (@) : [1m[38;5;13my[38;5;8mqosqzyt[39m [38;5;12md[38;5;8mcb25635[39m [38;5;9m(conflict)[39m [38;5;10m(empty)[39m boom-cont-2[0m
219 Parent commit (@-): [1m[38;5;5mr[0m[38;5;8moyxmykx[39m [1m[38;5;4m6[0m[38;5;8m64a4c6c[39m [38;5;1m(conflict)[39m [38;5;2m(empty)[39m boom-cont
220 [1m[38;5;3mWarning: [39mThere are unresolved conflicts at these paths:[0m
221 conflicted.txt [38;5;3m2-sided conflict[39m
222 [1m[38;5;6mHint: [0m[39mTo resolve the conflicts, start by updating to the first one:[39m
223 [39m jj new [1m[38;5;5mm[0m[38;5;8mzvwutvl[39m[39m
224 [39mThen use `jj resolve`, or edit the conflict markers in the file directly.[39m
225 [39mOnce the conflicts are resolved, you may want to inspect the result with `jj diff`.[39m
226 [39mThen run `jj squash` to move the resolution into the conflicted commit.[39m
227 [EOF]
228 ");
229
230 let output = test_env.run_jj_in(
231 &repo_path,
232 ["status", "--config=hints.resolving-conflicts=false"],
233 );
234 insta::assert_snapshot!(output, @r"
235 The working copy has no changes.
236 Working copy (@) : yqosqzyt dcb25635 (conflict) (empty) boom-cont-2
237 Parent commit (@-): royxmykx 664a4c6c (conflict) (empty) boom-cont
238 Warning: There are unresolved conflicts at these paths:
239 conflicted.txt 2-sided conflict
240 [EOF]
241 ");
242
243 // Resolve conflict
244 test_env
245 .run_jj_in(&repo_path, ["new", "--message", "fixed 1"])
246 .success();
247 std::fs::write(&conflicted_path, "first commit to fix conflict").unwrap();
248
249 // Add one more commit atop the commit that resolves the conflict.
250 test_env
251 .run_jj_in(&repo_path, ["new", "--message", "fixed 2"])
252 .success();
253 std::fs::write(&conflicted_path, "edit not conflict").unwrap();
254
255 // wc is now conflict free, parent is also conflict free
256 let output = test_env.run_jj_in(&repo_path, ["log", "-r", "::"]);
257
258 insta::assert_snapshot!(output, @r"
259 @ wqnwkozp test.user@example.com 2001-02-03 08:05:20 c4a6dbc2
260 │ fixed 2
261 ○ kmkuslsw test.user@example.com 2001-02-03 08:05:19 fcebf6ee
262 │ fixed 1
263 × yqosqzyt test.user@example.com 2001-02-03 08:05:13 dcb25635 conflict
264 │ (empty) boom-cont-2
265 × royxmykx test.user@example.com 2001-02-03 08:05:12 664a4c6c conflict
266 │ (empty) boom-cont
267 × mzvwutvl test.user@example.com 2001-02-03 08:05:11 c5a4e9cb conflict
268 ├─╮ (empty) boom
269 │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 1e8c2956
270 │ │ First part of conflicting change
271 ○ │ zsuskuln test.user@example.com 2001-02-03 08:05:11 2c8b19fd
272 ├─╯ Second part of conflicting change
273 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 aade7195
274 │ Initial contents
275 ◆ zzzzzzzz root() 00000000
276 [EOF]
277 ");
278
279 let output = test_env.run_jj_in(&repo_path, ["status"]);
280
281 insta::assert_snapshot!(output, @r"
282 Working copy changes:
283 M conflicted.txt
284 Working copy (@) : wqnwkozp c4a6dbc2 fixed 2
285 Parent commit (@-): kmkuslsw fcebf6ee fixed 1
286 [EOF]
287 ");
288
289 // Step back one.
290 // wc is still conflict free, parent has a conflict.
291 test_env.run_jj_in(&repo_path, ["edit", "@-"]).success();
292 let output = test_env.run_jj_in(&repo_path, ["log", "-r", "::"]);
293
294 insta::assert_snapshot!(output, @r"
295 ○ wqnwkozp test.user@example.com 2001-02-03 08:05:20 c4a6dbc2
296 │ fixed 2
297 @ kmkuslsw test.user@example.com 2001-02-03 08:05:19 fcebf6ee
298 │ fixed 1
299 × yqosqzyt test.user@example.com 2001-02-03 08:05:13 dcb25635 conflict
300 │ (empty) boom-cont-2
301 × royxmykx test.user@example.com 2001-02-03 08:05:12 664a4c6c conflict
302 │ (empty) boom-cont
303 × mzvwutvl test.user@example.com 2001-02-03 08:05:11 c5a4e9cb conflict
304 ├─╮ (empty) boom
305 │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 1e8c2956
306 │ │ First part of conflicting change
307 ○ │ zsuskuln test.user@example.com 2001-02-03 08:05:11 2c8b19fd
308 ├─╯ Second part of conflicting change
309 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 aade7195
310 │ Initial contents
311 ◆ zzzzzzzz root() 00000000
312 [EOF]
313 ");
314
315 let output = test_env.run_jj_in(&repo_path, ["status"]);
316
317 insta::assert_snapshot!(output, @r"
318 Working copy changes:
319 M conflicted.txt
320 Working copy (@) : kmkuslsw fcebf6ee fixed 1
321 Parent commit (@-): yqosqzyt dcb25635 (conflict) (empty) boom-cont-2
322 Hint: Conflict in parent commit has been resolved in working copy
323 [EOF]
324 ");
325
326 // Step back to all the way to `root()+` so that wc has no conflict, even though
327 // there is a conflict later in the tree. So that we can confirm
328 // our hinting logic doesn't get confused.
329 test_env
330 .run_jj_in(&repo_path, ["edit", "root()+"])
331 .success();
332 let output = test_env.run_jj_in(&repo_path, ["log", "-r", "::"]);
333
334 insta::assert_snapshot!(output, @r"
335 ○ wqnwkozp test.user@example.com 2001-02-03 08:05:20 c4a6dbc2
336 │ fixed 2
337 ○ kmkuslsw test.user@example.com 2001-02-03 08:05:19 fcebf6ee
338 │ fixed 1
339 × yqosqzyt test.user@example.com 2001-02-03 08:05:13 dcb25635 conflict
340 │ (empty) boom-cont-2
341 × royxmykx test.user@example.com 2001-02-03 08:05:12 664a4c6c conflict
342 │ (empty) boom-cont
343 × mzvwutvl test.user@example.com 2001-02-03 08:05:11 c5a4e9cb conflict
344 ├─╮ (empty) boom
345 │ ○ kkmpptxz test.user@example.com 2001-02-03 08:05:10 1e8c2956
346 │ │ First part of conflicting change
347 ○ │ zsuskuln test.user@example.com 2001-02-03 08:05:11 2c8b19fd
348 ├─╯ Second part of conflicting change
349 @ qpvuntsm test.user@example.com 2001-02-03 08:05:08 aade7195
350 │ Initial contents
351 ◆ zzzzzzzz root() 00000000
352 [EOF]
353 ");
354
355 let output = test_env.run_jj_in(&repo_path, ["status"]);
356
357 insta::assert_snapshot!(output, @r"
358 Working copy changes:
359 A conflicted.txt
360 Working copy (@) : qpvuntsm aade7195 Initial contents
361 Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
362 [EOF]
363 ");
364}
365
366#[test]
367fn test_status_simplify_conflict_sides() {
368 let test_env = TestEnvironment::default();
369 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
370 let repo_path = test_env.env_root().join("repo");
371
372 // Creates a 4-sided conflict, with fileA and fileB having different conflicts:
373 // fileA: A - B + C - B + B - B + B
374 // fileB: A - A + A - A + B - C + D
375 create_commit_with_files(
376 &test_env.work_dir(&repo_path),
377 "base",
378 &[],
379 &[("fileA", "base\n"), ("fileB", "base\n")],
380 );
381 create_commit_with_files(
382 &test_env.work_dir(&repo_path),
383 "a1",
384 &["base"],
385 &[("fileA", "1\n")],
386 );
387 create_commit_with_files(
388 &test_env.work_dir(&repo_path),
389 "a2",
390 &["base"],
391 &[("fileA", "2\n")],
392 );
393 create_commit_with_files(
394 &test_env.work_dir(&repo_path),
395 "b1",
396 &["base"],
397 &[("fileB", "1\n")],
398 );
399 create_commit_with_files(
400 &test_env.work_dir(&repo_path),
401 "b2",
402 &["base"],
403 &[("fileB", "2\n")],
404 );
405 create_commit_with_files(
406 &test_env.work_dir(&repo_path),
407 "conflictA",
408 &["a1", "a2"],
409 &[],
410 );
411 create_commit_with_files(
412 &test_env.work_dir(&repo_path),
413 "conflictB",
414 &["b1", "b2"],
415 &[],
416 );
417 create_commit_with_files(
418 &test_env.work_dir(&repo_path),
419 "conflict",
420 &["conflictA", "conflictB"],
421 &[],
422 );
423
424 insta::assert_snapshot!(test_env.run_jj_in(&repo_path, ["status"]),
425 @r"
426 The working copy has no changes.
427 Working copy (@) : nkmrtpmo 83c4b9e7 conflict | (conflict) (empty) conflict
428 Parent commit (@-): kmkuslsw 4601566f conflictA | (conflict) (empty) conflictA
429 Parent commit (@-): lylxulpl 6f8d8381 conflictB | (conflict) (empty) conflictB
430 Warning: There are unresolved conflicts at these paths:
431 fileA 2-sided conflict
432 fileB 2-sided conflict
433 Hint: To resolve the conflicts, start by updating to one of the first ones:
434 jj new lylxulpl
435 jj new kmkuslsw
436 Then use `jj resolve`, or edit the conflict markers in the file directly.
437 Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
438 Then run `jj squash` to move the resolution into the conflicted commit.
439 [EOF]
440 ");
441}
442
443#[test]
444fn test_status_untracked_files() {
445 let test_env = TestEnvironment::default();
446 test_env.add_config(r#"snapshot.auto-track = "none()""#);
447
448 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
449 let repo_path = test_env.env_root().join("repo");
450
451 std::fs::write(repo_path.join("always-untracked-file"), "...").unwrap();
452 std::fs::write(repo_path.join("initially-untracked-file"), "...").unwrap();
453 std::fs::create_dir(repo_path.join("sub")).unwrap();
454 std::fs::write(repo_path.join("sub").join("always-untracked"), "...").unwrap();
455 std::fs::write(repo_path.join("sub").join("initially-untracked"), "...").unwrap();
456
457 let output = test_env.run_jj_in(&repo_path, ["status"]);
458 insta::assert_snapshot!(output.normalize_backslash(), @r"
459 Untracked paths:
460 ? always-untracked-file
461 ? initially-untracked-file
462 ? sub/always-untracked
463 ? sub/initially-untracked
464 Working copy (@) : qpvuntsm 230dd059 (empty) (no description set)
465 Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
466 [EOF]
467 ");
468
469 test_env
470 .run_jj_in(
471 &repo_path,
472 [
473 "file",
474 "track",
475 "initially-untracked-file",
476 "sub/initially-untracked",
477 ],
478 )
479 .success();
480
481 let output = test_env.run_jj_in(&repo_path, ["status"]);
482 insta::assert_snapshot!(output.normalize_backslash(), @r"
483 Working copy changes:
484 A initially-untracked-file
485 A sub/initially-untracked
486 Untracked paths:
487 ? always-untracked-file
488 ? sub/always-untracked
489 Working copy (@) : qpvuntsm 99798fcd (no description set)
490 Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
491 [EOF]
492 ");
493
494 test_env.run_jj_in(&repo_path, ["new"]).success();
495
496 let output = test_env.run_jj_in(&repo_path, ["status"]);
497 insta::assert_snapshot!(output.normalize_backslash(), @r"
498 Untracked paths:
499 ? always-untracked-file
500 ? sub/always-untracked
501 Working copy (@) : mzvwutvl 30e53c74 (empty) (no description set)
502 Parent commit (@-): qpvuntsm 99798fcd (no description set)
503 [EOF]
504 ");
505
506 test_env
507 .run_jj_in(
508 &repo_path,
509 [
510 "file",
511 "untrack",
512 "initially-untracked-file",
513 "sub/initially-untracked",
514 ],
515 )
516 .success();
517 let output = test_env.run_jj_in(&repo_path, ["status"]);
518 insta::assert_snapshot!(output.normalize_backslash(), @r"
519 Working copy changes:
520 D initially-untracked-file
521 D sub/initially-untracked
522 Untracked paths:
523 ? always-untracked-file
524 ? initially-untracked-file
525 ? sub/always-untracked
526 ? sub/initially-untracked
527 Working copy (@) : mzvwutvl bb362aaf (no description set)
528 Parent commit (@-): qpvuntsm 99798fcd (no description set)
529 [EOF]
530 ");
531
532 test_env.run_jj_in(&repo_path, ["new"]).success();
533
534 let output = test_env.run_jj_in(&repo_path, ["status"]);
535 insta::assert_snapshot!(output.normalize_backslash(), @r"
536 Untracked paths:
537 ? always-untracked-file
538 ? initially-untracked-file
539 ? sub/always-untracked
540 ? sub/initially-untracked
541 Working copy (@) : yostqsxw 8e8c02fe (empty) (no description set)
542 Parent commit (@-): mzvwutvl bb362aaf (no description set)
543 [EOF]
544 ");
545}