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