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::to_toml_value;
16use crate::common::TestEnvironment;
17
18#[test]
19fn test_log_with_empty_revision() {
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 let output = work_dir.run_jj(["log", "-r="]);
25 insta::assert_snapshot!(output, @r"
26 ------- stderr -------
27 error: a value is required for '--revisions <REVSETS>' but none was supplied
28
29 For more information, try '--help'.
30 [EOF]
31 [exit status: 2]
32 ");
33}
34
35#[test]
36fn test_log_with_no_template() {
37 let test_env = TestEnvironment::default();
38 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
39 let work_dir = test_env.work_dir("repo");
40
41 let output = work_dir.run_jj(["log", "-T"]);
42 insta::assert_snapshot!(output, @r"
43 ------- stderr -------
44 error: a value is required for '--template <TEMPLATE>' but none was supplied
45
46 For more information, try '--help'.
47 Hint: The following template aliases are defined:
48 - builtin_config_list
49 - builtin_config_list_detailed
50 - builtin_draft_commit_description
51 - builtin_log_comfortable
52 - builtin_log_compact
53 - builtin_log_compact_full_description
54 - builtin_log_detailed
55 - builtin_log_node
56 - builtin_log_node_ascii
57 - builtin_log_oneline
58 - builtin_op_log_comfortable
59 - builtin_op_log_compact
60 - builtin_op_log_node
61 - builtin_op_log_node_ascii
62 - builtin_op_log_oneline
63 - commit_summary_separator
64 - default_commit_description
65 - description_placeholder
66 - email_placeholder
67 - git_format_patch_email_headers
68 - name_placeholder
69 [EOF]
70 [exit status: 2]
71 ");
72}
73
74#[test]
75fn test_log_with_or_without_diff() {
76 let test_env = TestEnvironment::default();
77 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
78 let work_dir = test_env.work_dir("repo");
79
80 work_dir.write_file("file1", "foo\n");
81 work_dir.run_jj(["describe", "-m", "add a file"]).success();
82 work_dir.run_jj(["new", "-m", "a new commit"]).success();
83 work_dir.write_file("file1", "foo\nbar\n");
84
85 let output = work_dir.run_jj(["log", "-T", "description"]);
86 insta::assert_snapshot!(output, @r"
87 @ a new commit
88 ○ add a file
89 ◆
90 [EOF]
91 ");
92
93 let output = work_dir.run_jj(["log", "-T", "description", "-p"]);
94 insta::assert_snapshot!(output, @r"
95 @ a new commit
96 │ Modified regular file file1:
97 │ 1 1: foo
98 │ 2: bar
99 ○ add a file
100 │ Added regular file file1:
101 │ 1: foo
102 ◆
103 [EOF]
104 ");
105
106 let output = work_dir.run_jj(["log", "-T", "description", "--no-graph"]);
107 insta::assert_snapshot!(output, @r"
108 a new commit
109 add a file
110 [EOF]
111 ");
112
113 // `-p` for default diff output, `-s` for summary
114 let output = work_dir.run_jj(["log", "-T", "description", "-p", "-s"]);
115 insta::assert_snapshot!(output, @r"
116 @ a new commit
117 │ M file1
118 │ Modified regular file file1:
119 │ 1 1: foo
120 │ 2: bar
121 ○ add a file
122 │ A file1
123 │ Added regular file file1:
124 │ 1: foo
125 ◆
126 [EOF]
127 ");
128
129 // `-s` for summary, `--git` for git diff (which implies `-p`)
130 let output = work_dir.run_jj(["log", "-T", "description", "-s", "--git"]);
131 insta::assert_snapshot!(output, @r"
132 @ a new commit
133 │ M file1
134 │ diff --git a/file1 b/file1
135 │ index 257cc5642c..3bd1f0e297 100644
136 │ --- a/file1
137 │ +++ b/file1
138 │ @@ -1,1 +1,2 @@
139 │ foo
140 │ +bar
141 ○ add a file
142 │ A file1
143 │ diff --git a/file1 b/file1
144 │ new file mode 100644
145 │ index 0000000000..257cc5642c
146 │ --- /dev/null
147 │ +++ b/file1
148 │ @@ -0,0 +1,1 @@
149 │ +foo
150 ◆
151 [EOF]
152 ");
153
154 // `-p` for default diff output, `--stat` for diff-stat
155 let output = work_dir.run_jj(["log", "-T", "description", "-p", "--stat"]);
156 insta::assert_snapshot!(output, @r"
157 @ a new commit
158 │ file1 | 1 +
159 │ 1 file changed, 1 insertion(+), 0 deletions(-)
160 │ Modified regular file file1:
161 │ 1 1: foo
162 │ 2: bar
163 ○ add a file
164 │ file1 | 1 +
165 │ 1 file changed, 1 insertion(+), 0 deletions(-)
166 │ Added regular file file1:
167 │ 1: foo
168 ◆
169 0 files changed, 0 insertions(+), 0 deletions(-)
170 [EOF]
171 ");
172
173 // `--stat` is short format, which should be printed first
174 let output = work_dir.run_jj(["log", "-T", "description", "--git", "--stat"]);
175 insta::assert_snapshot!(output, @r"
176 @ a new commit
177 │ file1 | 1 +
178 │ 1 file changed, 1 insertion(+), 0 deletions(-)
179 │ diff --git a/file1 b/file1
180 │ index 257cc5642c..3bd1f0e297 100644
181 │ --- a/file1
182 │ +++ b/file1
183 │ @@ -1,1 +1,2 @@
184 │ foo
185 │ +bar
186 ○ add a file
187 │ file1 | 1 +
188 │ 1 file changed, 1 insertion(+), 0 deletions(-)
189 │ diff --git a/file1 b/file1
190 │ new file mode 100644
191 │ index 0000000000..257cc5642c
192 │ --- /dev/null
193 │ +++ b/file1
194 │ @@ -0,0 +1,1 @@
195 │ +foo
196 ◆
197 0 files changed, 0 insertions(+), 0 deletions(-)
198 [EOF]
199 ");
200
201 // `-p` enables default "summary" output, so `-s` is noop
202 let output = work_dir.run_jj([
203 "log",
204 "-T",
205 "description",
206 "-p",
207 "-s",
208 "--config=ui.diff-formatter=:summary",
209 ]);
210 insta::assert_snapshot!(output, @r"
211 @ a new commit
212 │ M file1
213 ○ add a file
214 │ A file1
215 ◆
216 [EOF]
217 ");
218
219 // `-p` enables default "color-words" diff output, so `--color-words` is noop
220 let output = work_dir.run_jj(["log", "-T", "description", "-p", "--color-words"]);
221 insta::assert_snapshot!(output, @r"
222 @ a new commit
223 │ Modified regular file file1:
224 │ 1 1: foo
225 │ 2: bar
226 ○ add a file
227 │ Added regular file file1:
228 │ 1: foo
229 ◆
230 [EOF]
231 ");
232
233 // `--git` enables git diff, so `-p` is noop
234 let output = work_dir.run_jj(["log", "-T", "description", "--no-graph", "-p", "--git"]);
235 insta::assert_snapshot!(output, @r"
236 a new commit
237 diff --git a/file1 b/file1
238 index 257cc5642c..3bd1f0e297 100644
239 --- a/file1
240 +++ b/file1
241 @@ -1,1 +1,2 @@
242 foo
243 +bar
244 add a file
245 diff --git a/file1 b/file1
246 new file mode 100644
247 index 0000000000..257cc5642c
248 --- /dev/null
249 +++ b/file1
250 @@ -0,0 +1,1 @@
251 +foo
252 [EOF]
253 ");
254
255 // Cannot use both `--git` and `--color-words`
256 let output = work_dir.run_jj([
257 "log",
258 "-T",
259 "description",
260 "--no-graph",
261 "-p",
262 "--git",
263 "--color-words",
264 ]);
265 insta::assert_snapshot!(output, @r"
266 ------- stderr -------
267 error: the argument '--git' cannot be used with '--color-words'
268
269 Usage: jj log --template <TEMPLATE> --no-graph --patch --git [FILESETS]...
270
271 For more information, try '--help'.
272 [EOF]
273 [exit status: 2]
274 ");
275
276 // `-s` with or without graph
277 let output = work_dir.run_jj(["log", "-T", "description", "-s"]);
278 insta::assert_snapshot!(output, @r"
279 @ a new commit
280 │ M file1
281 ○ add a file
282 │ A file1
283 ◆
284 [EOF]
285 ");
286 let output = work_dir.run_jj(["log", "-T", "description", "--no-graph", "-s"]);
287 insta::assert_snapshot!(output, @r"
288 a new commit
289 M file1
290 add a file
291 A file1
292 [EOF]
293 ");
294
295 // `--git` implies `-p`, with or without graph
296 let output = work_dir.run_jj(["log", "-T", "description", "-r", "@", "--git"]);
297 insta::assert_snapshot!(output, @r"
298 @ a new commit
299 │ diff --git a/file1 b/file1
300 ~ index 257cc5642c..3bd1f0e297 100644
301 --- a/file1
302 +++ b/file1
303 @@ -1,1 +1,2 @@
304 foo
305 +bar
306 [EOF]
307 ");
308 let output = work_dir.run_jj(["log", "-T", "description", "-r", "@", "--no-graph", "--git"]);
309 insta::assert_snapshot!(output, @r"
310 a new commit
311 diff --git a/file1 b/file1
312 index 257cc5642c..3bd1f0e297 100644
313 --- a/file1
314 +++ b/file1
315 @@ -1,1 +1,2 @@
316 foo
317 +bar
318 [EOF]
319 ");
320
321 // `--color-words` implies `-p`, with or without graph
322 let output = work_dir.run_jj(["log", "-T", "description", "-r", "@", "--color-words"]);
323 insta::assert_snapshot!(output, @r"
324 @ a new commit
325 │ Modified regular file file1:
326 ~ 1 1: foo
327 2: bar
328 [EOF]
329 ");
330 let output = work_dir.run_jj([
331 "log",
332 "-T",
333 "description",
334 "-r",
335 "@",
336 "--no-graph",
337 "--color-words",
338 ]);
339 insta::assert_snapshot!(output, @r"
340 a new commit
341 Modified regular file file1:
342 1 1: foo
343 2: bar
344 [EOF]
345 ");
346}
347
348#[test]
349fn test_log_null_terminate_multiline_descriptions() {
350 let test_env = TestEnvironment::default();
351 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
352 let work_dir = test_env.work_dir("repo");
353
354 work_dir
355 .run_jj(["commit", "-m", "commit 1 line 1", "-m", "commit 1 line 2"])
356 .success();
357 work_dir
358 .run_jj(["commit", "-m", "commit 2 line 1", "-m", "commit 2 line 2"])
359 .success();
360 work_dir
361 .run_jj(["describe", "-m", "commit 3 line 1", "-m", "commit 3 line 2"])
362 .success();
363
364 let output = work_dir
365 .run_jj([
366 "log",
367 "-r",
368 "~root()",
369 "-T",
370 r#"description ++ "\0""#,
371 "--no-graph",
372 ])
373 .success();
374 insta::assert_debug_snapshot!(
375 output.stdout.normalized(),
376 @r#""commit 3 line 1\n\ncommit 3 line 2\n\0commit 2 line 1\n\ncommit 2 line 2\n\0commit 1 line 1\n\ncommit 1 line 2\n\0""#
377 );
378}
379
380#[test]
381fn test_log_shortest_accessors() {
382 let test_env = TestEnvironment::default();
383 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
384 let work_dir = test_env.work_dir("repo");
385 let render = |rev, template| work_dir.run_jj(["log", "--no-graph", "-r", rev, "-T", template]);
386 test_env.add_config(
387 r#"
388 [template-aliases]
389 'format_id(id)' = 'id.shortest(12).prefix() ++ "[" ++ id.shortest(12).rest() ++ "]"'
390 "#,
391 );
392
393 work_dir.write_file("file", "original file\n");
394 work_dir.run_jj(["describe", "-m", "initial"]).success();
395 work_dir
396 .run_jj(["bookmark", "c", "-r@", "original"])
397 .success();
398 insta::assert_snapshot!(
399 render("original", r#"format_id(change_id) ++ " " ++ format_id(commit_id)"#),
400 @"q[pvuntsmwlqt] 8[216f646c36d][EOF]");
401
402 // Create a chain of 10 commits
403 for i in 1..10 {
404 work_dir
405 .run_jj(["new", "-m", &format!("commit{i}")])
406 .success();
407 work_dir.write_file("file", format!("file {i}\n"));
408 }
409 // Create 2^3 duplicates of the chain
410 for _ in 0..3 {
411 work_dir
412 .run_jj(["duplicate", "description(commit)"])
413 .success();
414 }
415
416 insta::assert_snapshot!(
417 render("original", r#"format_id(change_id) ++ " " ++ format_id(commit_id)"#),
418 @"qpv[untsmwlqt] 82[16f646c36d][EOF]");
419
420 insta::assert_snapshot!(
421 render("::@", r#"change_id.shortest() ++ " " ++ commit_id.shortest() ++ "\n""#), @r"
422 wq c2
423 km 74
424 kp 97
425 zn 78
426 yo 40
427 vr bc9
428 yq 28
429 ro af
430 mz 04
431 qpv 82
432 zzz 00
433 [EOF]
434 ");
435
436 insta::assert_snapshot!(
437 render("::@", r#"format_id(change_id) ++ " " ++ format_id(commit_id) ++ "\n""#), @r"
438 wq[nwkozpkust] c2[b4c0bb3362]
439 km[kuslswpqwq] 74[fcd50c0643]
440 kp[qxywonksrl] 97[dcaada9b8d]
441 zn[kkpsqqskkl] 78[c03ab2235b]
442 yo[stqsxwqrlt] 40[1119280761]
443 vr[uxwmqvtpmx] bc9[e8942b459]
444 yq[osqzytrlsw] 28[edbc9658ef]
445 ro[yxmykxtrkr] af[3e6a27a1d0]
446 mz[vwutvlkqwt] 04[6c6a1df762]
447 qpv[untsmwlqt] 82[16f646c36d]
448 zzz[zzzzzzzzz] 00[0000000000]
449 [EOF]
450 ");
451
452 // Can get shorter prefixes in configured revset
453 test_env.add_config(r#"revsets.short-prefixes = "(@----)::""#);
454 insta::assert_snapshot!(
455 render("::@", r#"format_id(change_id) ++ " " ++ format_id(commit_id) ++ "\n""#), @r"
456 w[qnwkozpkust] c[2b4c0bb3362]
457 km[kuslswpqwq] 74[fcd50c0643]
458 kp[qxywonksrl] 9[7dcaada9b8d]
459 z[nkkpsqqskkl] 78[c03ab2235b]
460 y[ostqsxwqrlt] 4[01119280761]
461 vr[uxwmqvtpmx] bc9[e8942b459]
462 yq[osqzytrlsw] 28[edbc9658ef]
463 ro[yxmykxtrkr] af[3e6a27a1d0]
464 mz[vwutvlkqwt] 04[6c6a1df762]
465 qpv[untsmwlqt] 82[16f646c36d]
466 zzz[zzzzzzzzz] 00[0000000000]
467 [EOF]
468 ");
469
470 // Can disable short prefixes by setting to empty string
471 test_env.add_config(r#"revsets.short-prefixes = """#);
472 insta::assert_snapshot!(
473 render("::@", r#"format_id(change_id) ++ " " ++ format_id(commit_id) ++ "\n""#), @r"
474 wq[nwkozpkust] c2[b4c0bb3362]
475 km[kuslswpqwq] 74[fcd50c0643]
476 kp[qxywonksrl] 97[dcaada9b8d]
477 zn[kkpsqqskkl] 78[c03ab2235b]
478 yo[stqsxwqrlt] 401[119280761]
479 vr[uxwmqvtpmx] bc9[e8942b459]
480 yq[osqzytrlsw] 28[edbc9658ef]
481 ro[yxmykxtrkr] af[3e6a27a1d0]
482 mz[vwutvlkqwt] 04[6c6a1df762]
483 qpv[untsmwlqt] 82[16f646c36d]
484 zzz[zzzzzzzzz] 00[0000000000]
485 [EOF]
486 ");
487}
488
489#[test]
490fn test_log_bad_short_prefixes() {
491 let test_env = TestEnvironment::default();
492 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
493 let work_dir = test_env.work_dir("repo");
494
495 // Suppress warning in the commit summary template
496 test_env.add_config("template-aliases.'format_short_id(id)' = 'id.short(8)'");
497
498 // Error on bad config of short prefixes
499 test_env.add_config(r#"revsets.short-prefixes = "!nval!d""#);
500 let output = work_dir.run_jj(["status"]);
501 insta::assert_snapshot!(output, @r"
502 ------- stderr -------
503 Config error: Invalid `revsets.short-prefixes`
504 Caused by: --> 1:1
505 |
506 1 | !nval!d
507 | ^---
508 |
509 = expected <strict_identifier> or <expression>
510 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
511 [EOF]
512 [exit status: 1]
513 ");
514
515 // Warn on resolution of short prefixes
516 test_env.add_config("revsets.short-prefixes = 'missing'");
517 let output = work_dir.run_jj(["log", "-Tcommit_id.shortest()"]);
518 insta::assert_snapshot!(output, @r"
519 @ e
520 ◆ 0
521 [EOF]
522 ------- stderr -------
523 Warning: In template expression
524 --> 1:11
525 |
526 1 | commit_id.shortest()
527 | ^------^
528 |
529 = Failed to load short-prefixes index
530 Failed to resolve short-prefixes disambiguation revset
531 Revision `missing` doesn't exist
532 [EOF]
533 ");
534
535 // Error on resolution of short prefixes
536 test_env.add_config("revsets.short-prefixes = 'missing'");
537 let output = work_dir.run_jj(["log", "-r0"]);
538 insta::assert_snapshot!(output, @r"
539 ------- stderr -------
540 Error: Failed to resolve short-prefixes disambiguation revset
541 Caused by: Revision `missing` doesn't exist
542 [EOF]
543 [exit status: 1]
544 ");
545}
546
547#[test]
548fn test_log_prefix_highlight_styled() {
549 let test_env = TestEnvironment::default();
550 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
551 let work_dir = test_env.work_dir("repo");
552
553 fn prefix_format(len: Option<usize>) -> String {
554 format!(
555 r###"
556 separate(" ",
557 "Change",
558 change_id.shortest({0}),
559 description.first_line(),
560 commit_id.shortest({0}),
561 bookmarks,
562 )
563 "###,
564 len.map(|l| l.to_string()).unwrap_or_default()
565 )
566 }
567
568 work_dir.write_file("file", "original file\n");
569 work_dir.run_jj(["describe", "-m", "initial"]).success();
570 work_dir
571 .run_jj(["bookmark", "c", "-r@", "original"])
572 .success();
573 insta::assert_snapshot!(
574 work_dir.run_jj(["log", "-r", "original", "-T", &prefix_format(Some(12))]), @r"
575 @ Change qpvuntsmwlqt initial 8216f646c36d original
576 │
577 ~
578 [EOF]
579 ");
580
581 // Create a chain of 10 commits
582 for i in 1..10 {
583 work_dir
584 .run_jj(["new", "-m", &format!("commit{i}")])
585 .success();
586 work_dir.write_file("file", format!("file {i}\n"));
587 }
588 // Create 2^3 duplicates of the chain
589 for _ in 0..3 {
590 work_dir
591 .run_jj(["duplicate", "description(commit)"])
592 .success();
593 }
594
595 insta::assert_snapshot!(
596 work_dir.run_jj(["log", "-r", "original", "-T", &prefix_format(Some(12))]), @r"
597 ○ Change qpvuntsmwlqt initial 8216f646c36d original
598 │
599 ~
600 [EOF]
601 ");
602 let output = work_dir.run_jj([
603 "--color=always",
604 "log",
605 "-r",
606 "@-----------..@",
607 "-T",
608 &prefix_format(Some(12)),
609 ]);
610 insta::assert_snapshot!(output, @r"
611 [1m[38;5;2m@[0m Change [1m[38;5;5mwq[0m[38;5;8mnwkozpkust[39m commit9 [1m[38;5;4mc2[0m[38;5;8mb4c0bb3362[39m
612 ○ Change [1m[38;5;5mkm[0m[38;5;8mkuslswpqwq[39m commit8 [1m[38;5;4m74[0m[38;5;8mfcd50c0643[39m
613 ○ Change [1m[38;5;5mkp[0m[38;5;8mqxywonksrl[39m commit7 [1m[38;5;4m97[0m[38;5;8mdcaada9b8d[39m
614 ○ Change [1m[38;5;5mzn[0m[38;5;8mkkpsqqskkl[39m commit6 [1m[38;5;4m78[0m[38;5;8mc03ab2235b[39m
615 ○ Change [1m[38;5;5myo[0m[38;5;8mstqsxwqrlt[39m commit5 [1m[38;5;4m40[0m[38;5;8m1119280761[39m
616 ○ Change [1m[38;5;5mvr[0m[38;5;8muxwmqvtpmx[39m commit4 [1m[38;5;4mbc9[0m[38;5;8me8942b459[39m
617 ○ Change [1m[38;5;5myq[0m[38;5;8mosqzytrlsw[39m commit3 [1m[38;5;4m28[0m[38;5;8medbc9658ef[39m
618 ○ Change [1m[38;5;5mro[0m[38;5;8myxmykxtrkr[39m commit2 [1m[38;5;4maf[0m[38;5;8m3e6a27a1d0[39m
619 ○ Change [1m[38;5;5mmz[0m[38;5;8mvwutvlkqwt[39m commit1 [1m[38;5;4m04[0m[38;5;8m6c6a1df762[39m
620 ○ Change [1m[38;5;5mqpv[0m[38;5;8muntsmwlqt[39m initial [1m[38;5;4m82[0m[38;5;8m16f646c36d[39m [38;5;5moriginal[39m
621 [1m[38;5;14m◆[0m Change [1m[38;5;5mzzz[0m[38;5;8mzzzzzzzzz[39m [1m[38;5;4m00[0m[38;5;8m0000000000[39m
622 [EOF]
623 ");
624 let output = work_dir.run_jj([
625 "--color=always",
626 "log",
627 "-r",
628 "@-----------..@",
629 "-T",
630 &prefix_format(Some(3)),
631 ]);
632 insta::assert_snapshot!(output, @r"
633 [1m[38;5;2m@[0m Change [1m[38;5;5mwq[0m[38;5;8mn[39m commit9 [1m[38;5;4mc2[0m[38;5;8mb[39m
634 ○ Change [1m[38;5;5mkm[0m[38;5;8mk[39m commit8 [1m[38;5;4m74[0m[38;5;8mf[39m
635 ○ Change [1m[38;5;5mkp[0m[38;5;8mq[39m commit7 [1m[38;5;4m97[0m[38;5;8md[39m
636 ○ Change [1m[38;5;5mzn[0m[38;5;8mk[39m commit6 [1m[38;5;4m78[0m[38;5;8mc[39m
637 ○ Change [1m[38;5;5myo[0m[38;5;8ms[39m commit5 [1m[38;5;4m40[0m[38;5;8m1[39m
638 ○ Change [1m[38;5;5mvr[0m[38;5;8mu[39m commit4 [1m[38;5;4mbc9[0m
639 ○ Change [1m[38;5;5myq[0m[38;5;8mo[39m commit3 [1m[38;5;4m28[0m[38;5;8me[39m
640 ○ Change [1m[38;5;5mro[0m[38;5;8my[39m commit2 [1m[38;5;4maf[0m[38;5;8m3[39m
641 ○ Change [1m[38;5;5mmz[0m[38;5;8mv[39m commit1 [1m[38;5;4m04[0m[38;5;8m6[39m
642 ○ Change [1m[38;5;5mqpv[0m initial [1m[38;5;4m82[0m[38;5;8m1[39m [38;5;5moriginal[39m
643 [1m[38;5;14m◆[0m Change [1m[38;5;5mzzz[0m [1m[38;5;4m00[0m[38;5;8m0[39m
644 [EOF]
645 ");
646 let output = work_dir.run_jj([
647 "--color=always",
648 "log",
649 "-r",
650 "@-----------..@",
651 "-T",
652 &prefix_format(None),
653 ]);
654 insta::assert_snapshot!(output, @r"
655 [1m[38;5;2m@[0m Change [1m[38;5;5mwq[0m commit9 [1m[38;5;4mc2[0m
656 ○ Change [1m[38;5;5mkm[0m commit8 [1m[38;5;4m74[0m
657 ○ Change [1m[38;5;5mkp[0m commit7 [1m[38;5;4m97[0m
658 ○ Change [1m[38;5;5mzn[0m commit6 [1m[38;5;4m78[0m
659 ○ Change [1m[38;5;5myo[0m commit5 [1m[38;5;4m40[0m
660 ○ Change [1m[38;5;5mvr[0m commit4 [1m[38;5;4mbc9[0m
661 ○ Change [1m[38;5;5myq[0m commit3 [1m[38;5;4m28[0m
662 ○ Change [1m[38;5;5mro[0m commit2 [1m[38;5;4maf[0m
663 ○ Change [1m[38;5;5mmz[0m commit1 [1m[38;5;4m04[0m
664 ○ Change [1m[38;5;5mqpv[0m initial [1m[38;5;4m82[0m [38;5;5moriginal[39m
665 [1m[38;5;14m◆[0m Change [1m[38;5;5mzzz[0m [1m[38;5;4m00[0m
666 [EOF]
667 ");
668}
669
670#[test]
671fn test_log_prefix_highlight_counts_hidden_commits() {
672 let test_env = TestEnvironment::default();
673 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
674 let work_dir = test_env.work_dir("repo");
675 test_env.add_config(
676 r#"
677 [revsets]
678 short-prefixes = "" # Disable short prefixes
679 [template-aliases]
680 'format_id(id)' = 'id.shortest(12).prefix() ++ "[" ++ id.shortest(12).rest() ++ "]"'
681 "#,
682 );
683
684 let prefix_format = r#"
685 separate(" ",
686 "Change",
687 format_id(change_id),
688 description.first_line(),
689 format_id(commit_id),
690 bookmarks,
691 )
692 "#;
693
694 work_dir.write_file("file", "original file\n");
695 work_dir.run_jj(["describe", "-m", "initial"]).success();
696 work_dir
697 .run_jj(["bookmark", "c", "-r@", "original"])
698 .success();
699 insta::assert_snapshot!(work_dir.run_jj(["log", "-r", "all()", "-T", prefix_format]), @r"
700 @ Change q[pvuntsmwlqt] initial 8[216f646c36d] original
701 ◆ Change z[zzzzzzzzzzz] 00[0000000000]
702 [EOF]
703 ");
704
705 // Create 2^7 hidden commits
706 work_dir.run_jj(["new", "root()", "-m", "extra"]).success();
707 for _ in 0..7 {
708 work_dir
709 .run_jj(["duplicate", "description(extra)"])
710 .success();
711 }
712 work_dir.run_jj(["abandon", "description(extra)"]).success();
713
714 // The unique prefixes became longer.
715 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", prefix_format]), @r"
716 @ Change wq[nwkozpkust] 88[e8407a4f0a]
717 │ ○ Change qpv[untsmwlqt] initial 82[16f646c36d] original
718 ├─╯
719 ◆ Change zzz[zzzzzzzzz] 00[0000000000]
720 [EOF]
721 ");
722 insta::assert_snapshot!(work_dir.run_jj(["log", "-r", "8", "-T", prefix_format]), @r"
723 ------- stderr -------
724 Error: Commit ID prefix `8` is ambiguous
725 [EOF]
726 [exit status: 1]
727 ");
728 insta::assert_snapshot!(work_dir.run_jj(["log", "-r", "88", "-T", prefix_format]), @r"
729 @ Change wq[nwkozpkust] 88[e8407a4f0a]
730 │
731 ~
732 [EOF]
733 ");
734}
735
736#[test]
737fn test_log_author_format() {
738 let test_env = TestEnvironment::default();
739 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
740 let work_dir = test_env.work_dir("repo");
741
742 insta::assert_snapshot!(work_dir.run_jj(["log", "--revisions=@"]), @r"
743 @ qpvuntsm test.user@example.com 2001-02-03 08:05:07 e8849ae1
744 │ (empty) (no description set)
745 ~
746 [EOF]
747 ");
748
749 let decl = "template-aliases.'format_short_signature(signature)'";
750 insta::assert_snapshot!(work_dir.run_jj([
751 "--config",
752 &format!("{decl}='signature.email().local()'"),
753 "log",
754 "--revisions=@",
755 ]), @r"
756 @ qpvuntsm test.user 2001-02-03 08:05:07 e8849ae1
757 │ (empty) (no description set)
758 ~
759 [EOF]
760 ");
761}
762
763#[test]
764fn test_log_divergence() {
765 let test_env = TestEnvironment::default();
766 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
767 let work_dir = test_env.work_dir("repo");
768 let template = r#"description.first_line() ++ if(divergent, " !divergence!")"#;
769
770 work_dir.write_file("file", "foo\n");
771 work_dir
772 .run_jj(["describe", "-m", "description 1"])
773 .success();
774 // No divergence
775 let output = work_dir.run_jj(["log", "-T", template]);
776 insta::assert_snapshot!(output, @r"
777 @ description 1
778 ◆
779 [EOF]
780 ");
781
782 // Create divergence
783 work_dir
784 .run_jj(["describe", "-m", "description 2", "--at-operation", "@-"])
785 .success();
786 let output = work_dir.run_jj(["log", "-T", template]);
787 insta::assert_snapshot!(output, @r"
788 @ description 1 !divergence!
789 │ ○ description 2 !divergence!
790 ├─╯
791 ◆
792 [EOF]
793 ------- stderr -------
794 Concurrent modification detected, resolving automatically.
795 [EOF]
796 ");
797}
798
799#[test]
800fn test_log_reversed() {
801 let test_env = TestEnvironment::default();
802 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
803 let work_dir = test_env.work_dir("repo");
804
805 work_dir.run_jj(["describe", "-m", "first"]).success();
806 work_dir.run_jj(["new", "-m", "second"]).success();
807
808 let output = work_dir.run_jj(["log", "-T", "description", "--reversed"]);
809 insta::assert_snapshot!(output, @r"
810 ◆
811 ○ first
812 @ second
813 [EOF]
814 ");
815
816 let output = work_dir.run_jj(["log", "-T", "description", "--reversed", "--no-graph"]);
817 insta::assert_snapshot!(output, @r"
818 first
819 second
820 [EOF]
821 ");
822}
823
824#[test]
825fn test_log_filtered_by_path() {
826 let test_env = TestEnvironment::default();
827 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
828 let work_dir = test_env.work_dir("repo");
829
830 work_dir.write_file("file1", "foo\n");
831 work_dir.run_jj(["describe", "-m", "first"]).success();
832 work_dir.run_jj(["new", "-m", "second"]).success();
833 work_dir.write_file("file1", "foo\nbar\n");
834 work_dir.write_file("file2", "baz\n");
835
836 let output = work_dir.run_jj(["log", "-T", "description", "file1"]);
837 insta::assert_snapshot!(output, @r"
838 @ second
839 ○ first
840 │
841 ~
842 [EOF]
843 ");
844
845 let output = work_dir.run_jj(["log", "-T", "description", "file2"]);
846 insta::assert_snapshot!(output, @r"
847 @ second
848 │
849 ~
850 [EOF]
851 ");
852
853 let output = work_dir.run_jj(["log", "-T", "description", "-s", "file1"]);
854 insta::assert_snapshot!(output, @r"
855 @ second
856 │ M file1
857 ○ first
858 │ A file1
859 ~
860 [EOF]
861 ");
862
863 let output = work_dir.run_jj(["log", "-T", "description", "-s", "file2", "--no-graph"]);
864 insta::assert_snapshot!(output, @r"
865 second
866 A file2
867 [EOF]
868 ");
869
870 // empty revisions are filtered out by "all()" fileset.
871 let output = work_dir.run_jj(["log", "-Tdescription", "-s", "all()"]);
872 insta::assert_snapshot!(output, @r"
873 @ second
874 │ M file1
875 │ A file2
876 ○ first
877 │ A file1
878 ~
879 [EOF]
880 ");
881
882 // "root:<path>" is resolved relative to the workspace root.
883 let output = test_env.run_jj_in(
884 ".",
885 [
886 "log",
887 "-R",
888 work_dir.root().to_str().unwrap(),
889 "-Tdescription",
890 "-s",
891 "root:file1",
892 ],
893 );
894 insta::assert_snapshot!(output.normalize_backslash(), @r"
895 @ second
896 │ M repo/file1
897 ○ first
898 │ A repo/file1
899 ~
900 [EOF]
901 ");
902
903 // files() revset doesn't filter the diff.
904 let output = work_dir.run_jj([
905 "log",
906 "-T",
907 "description",
908 "-s",
909 "-rfiles(file2)",
910 "--no-graph",
911 ]);
912 insta::assert_snapshot!(output, @r"
913 second
914 M file1
915 A file2
916 [EOF]
917 ");
918}
919
920#[test]
921fn test_log_limit() {
922 let test_env = TestEnvironment::default();
923 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
924 let work_dir = test_env.work_dir("repo");
925
926 work_dir.run_jj(["describe", "-m", "a"]).success();
927 work_dir.write_file("a", "");
928 work_dir.run_jj(["new", "-m", "b"]).success();
929 work_dir.write_file("b", "");
930 work_dir
931 .run_jj(["new", "-m", "c", "description(a)"])
932 .success();
933 work_dir.write_file("c", "");
934 work_dir
935 .run_jj(["new", "-m", "d", "description(c)", "description(b)"])
936 .success();
937
938 let output = work_dir.run_jj(["log", "-T", "description", "--limit=3"]);
939 insta::assert_snapshot!(output, @r"
940 @ d
941 ├─╮
942 │ ○ b
943 ○ │ c
944 ├─╯
945 [EOF]
946 ");
947
948 // Applied on sorted DAG
949 let output = work_dir.run_jj(["log", "-T", "description", "--limit=2"]);
950 insta::assert_snapshot!(output, @r"
951 @ d
952 ├─╮
953 │ ○ b
954 [EOF]
955 ");
956
957 let output = work_dir.run_jj(["log", "-T", "description", "--limit=2", "--no-graph"]);
958 insta::assert_snapshot!(output, @r"
959 d
960 c
961 [EOF]
962 ");
963
964 // Applied on reversed DAG: Because the node "a" is omitted, "b" and "c" are
965 // rendered as roots.
966 let output = work_dir.run_jj(["log", "-T", "description", "--limit=3", "--reversed"]);
967 insta::assert_snapshot!(output, @r"
968 ○ c
969 │ ○ b
970 ├─╯
971 @ d
972 [EOF]
973 ");
974 let output = work_dir.run_jj([
975 "log",
976 "-T",
977 "description",
978 "--limit=3",
979 "--reversed",
980 "--no-graph",
981 ]);
982 insta::assert_snapshot!(output, @r"
983 b
984 c
985 d
986 [EOF]
987 ");
988
989 // Applied on filtered commits
990 let output = work_dir.run_jj(["log", "-T", "description", "--limit=1", "b", "c"]);
991 insta::assert_snapshot!(output, @r"
992 ○ c
993 │
994 ~
995 [EOF]
996 ");
997}
998
999#[test]
1000fn test_log_warn_path_might_be_revset() {
1001 let test_env = TestEnvironment::default();
1002 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1003 let work_dir = test_env.work_dir("repo");
1004
1005 work_dir.write_file("file1", "foo\n");
1006
1007 // Don't warn if the file actually exists.
1008 let output = work_dir.run_jj(["log", "file1", "-T", "description"]);
1009 insta::assert_snapshot!(output, @r"
1010 @
1011 │
1012 ~
1013 [EOF]
1014 ");
1015
1016 // Warn for `jj log .` specifically, for former Mercurial users.
1017 let output = work_dir.run_jj(["log", ".", "-T", "description"]);
1018 insta::assert_snapshot!(output, @r#"
1019 @
1020 │
1021 ~
1022 [EOF]
1023 ------- stderr -------
1024 Warning: The argument "." is being interpreted as a fileset expression, but this is often not useful because all non-empty commits touch '.'. If you meant to show the working copy commit, pass -r '@' instead.
1025 [EOF]
1026 "#);
1027
1028 // ...but checking `jj log .` makes sense in a subdirectory.
1029 let sub_dir = work_dir.create_dir_all("dir");
1030 let output = sub_dir.run_jj(["log", "."]);
1031 insta::assert_snapshot!(output, @"");
1032
1033 // Warn for `jj log @` instead of `jj log -r @`.
1034 let output = work_dir.run_jj(["log", "@", "-T", "description"]);
1035 insta::assert_snapshot!(output, @r#"
1036 ------- stderr -------
1037 Warning: The argument "@" is being interpreted as a fileset expression. To specify a revset, pass -r "@" instead.
1038 [EOF]
1039 "#);
1040
1041 // Warn when there's no path with the provided name.
1042 let output = work_dir.run_jj(["log", "file2", "-T", "description"]);
1043 insta::assert_snapshot!(output, @r#"
1044 ------- stderr -------
1045 Warning: The argument "file2" is being interpreted as a fileset expression. To specify a revset, pass -r "file2" instead.
1046 [EOF]
1047 "#);
1048
1049 // If an explicit revision is provided, then suppress the warning.
1050 let output = work_dir.run_jj(["log", "@", "-r", "@", "-T", "description"]);
1051 insta::assert_snapshot!(output, @"");
1052}
1053
1054#[test]
1055fn test_default_revset() {
1056 let test_env = TestEnvironment::default();
1057 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1058 let work_dir = test_env.work_dir("repo");
1059
1060 work_dir.write_file("file1", "foo\n");
1061 work_dir.run_jj(["describe", "-m", "add a file"]).success();
1062
1063 // Set configuration to only show the root commit.
1064 test_env.add_config(r#"revsets.log = "root()""#);
1065
1066 // Log should only contain one line (for the root commit), and not show the
1067 // commit created above.
1068 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "commit_id"]), @r"
1069 ◆ 0000000000000000000000000000000000000000
1070 [EOF]
1071 ");
1072
1073 // The default revset is not used if a path is specified
1074 insta::assert_snapshot!(work_dir.run_jj(["log", "file1", "-T", "description"]), @r"
1075 @ add a file
1076 │
1077 ~
1078 [EOF]
1079 ");
1080}
1081
1082#[test]
1083fn test_default_revset_per_repo() {
1084 let test_env = TestEnvironment::default();
1085 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1086 let work_dir = test_env.work_dir("repo");
1087
1088 work_dir.write_file("file1", "foo\n");
1089 work_dir.run_jj(["describe", "-m", "add a file"]).success();
1090
1091 // Set configuration to only show the root commit.
1092 work_dir.write_file(".jj/repo/config.toml", r#"revsets.log = "root()""#);
1093
1094 // Log should only contain one line (for the root commit), and not show the
1095 // commit created above.
1096 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "commit_id"]), @r"
1097 ◆ 0000000000000000000000000000000000000000
1098 [EOF]
1099 ");
1100}
1101
1102#[test]
1103fn test_multiple_revsets() {
1104 let test_env = TestEnvironment::default();
1105 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1106 let work_dir = test_env.work_dir("repo");
1107 for name in ["foo", "bar", "baz"] {
1108 work_dir.run_jj(["new", "-m", name]).success();
1109 work_dir
1110 .run_jj(["bookmark", "create", "-r@", name])
1111 .success();
1112 }
1113
1114 // Default revset should be overridden if one or more -r options are specified.
1115 test_env.add_config(r#"revsets.log = "root()""#);
1116
1117 insta::assert_snapshot!(
1118 work_dir.run_jj(["log", "-T", "bookmarks", "-rfoo"]), @r"
1119 ○ foo
1120 │
1121 ~
1122 [EOF]
1123 ");
1124 insta::assert_snapshot!(
1125 work_dir.run_jj(["log", "-T", "bookmarks", "-rfoo", "-rbar", "-rbaz"]), @r"
1126 @ baz
1127 ○ bar
1128 ○ foo
1129 │
1130 ~
1131 [EOF]
1132 ");
1133 insta::assert_snapshot!(
1134 work_dir.run_jj(["log", "-T", "bookmarks", "-rfoo", "-rfoo"]), @r"
1135 ○ foo
1136 │
1137 ~
1138 [EOF]
1139 ");
1140}
1141
1142#[test]
1143fn test_graph_template_color() {
1144 // Test that color codes from a multi-line template don't span the graph lines.
1145 let test_env = TestEnvironment::default();
1146 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1147 let work_dir = test_env.work_dir("repo");
1148
1149 work_dir
1150 .run_jj(["describe", "-m", "first line\nsecond line\nthird line"])
1151 .success();
1152 work_dir.run_jj(["new", "-m", "single line"]).success();
1153
1154 test_env.add_config(
1155 r#"[colors]
1156 description = "red"
1157 "working_copy description" = "green"
1158 "#,
1159 );
1160
1161 // First test without color for comparison
1162 let template = r#"label(if(current_working_copy, "working_copy"), description)"#;
1163 let output = work_dir.run_jj(["log", "-T", template]);
1164 insta::assert_snapshot!(output, @r"
1165 @ single line
1166 ○ first line
1167 │ second line
1168 │ third line
1169 ◆
1170 [EOF]
1171 ");
1172 let output = work_dir.run_jj(["--color=always", "log", "-T", template]);
1173 insta::assert_snapshot!(output, @r"
1174 [1m[38;5;2m@[0m [1m[38;5;2msingle line[0m
1175 ○ [38;5;1mfirst line[39m
1176 │ [38;5;1msecond line[39m
1177 │ [38;5;1mthird line[39m
1178 [1m[38;5;14m◆[0m
1179 [EOF]
1180 ");
1181 let output = work_dir.run_jj(["--color=debug", "log", "-T", template]);
1182 insta::assert_snapshot!(output, @r"
1183 [1m[38;5;2m<<log commit node working_copy::@>>[0m [1m[38;5;2m<<log commit working_copy description::single line>>[0m
1184 <<log commit node::○>> [38;5;1m<<log commit description::first line>>[39m
1185 │ [38;5;1m<<log commit description::second line>>[39m
1186 │ [38;5;1m<<log commit description::third line>>[39m
1187 [1m[38;5;14m<<log commit node immutable::◆>>[0m
1188 [EOF]
1189 ");
1190}
1191
1192#[test]
1193fn test_graph_styles() {
1194 // Test that different graph styles are available.
1195 let test_env = TestEnvironment::default();
1196 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1197 let work_dir = test_env.work_dir("repo");
1198
1199 work_dir.run_jj(["commit", "-m", "initial"]).success();
1200 work_dir
1201 .run_jj(["commit", "-m", "main bookmark 1"])
1202 .success();
1203 work_dir
1204 .run_jj(["describe", "-m", "main bookmark 2"])
1205 .success();
1206 work_dir
1207 .run_jj(["new", "-m", "side bookmark\nwith\nlong\ndescription"])
1208 .success();
1209 work_dir
1210 .run_jj([
1211 "new",
1212 "-m",
1213 "merge",
1214 r#"description("main bookmark 1")"#,
1215 "@",
1216 ])
1217 .success();
1218
1219 // Default (curved) style
1220 let output = work_dir.run_jj(["log", "-T=description"]);
1221 insta::assert_snapshot!(output, @r"
1222 @ merge
1223 ├─╮
1224 │ ○ side bookmark
1225 │ │ with
1226 │ │ long
1227 │ │ description
1228 │ ○ main bookmark 2
1229 ├─╯
1230 ○ main bookmark 1
1231 ○ initial
1232 ◆
1233 [EOF]
1234 ");
1235
1236 // ASCII style
1237 test_env.add_config(r#"ui.graph.style = "ascii""#);
1238 let output = work_dir.run_jj(["log", "-T=description"]);
1239 insta::assert_snapshot!(output, @r"
1240 @ merge
1241 |\
1242 | o side bookmark
1243 | | with
1244 | | long
1245 | | description
1246 | o main bookmark 2
1247 |/
1248 o main bookmark 1
1249 o initial
1250 +
1251 [EOF]
1252 ");
1253
1254 // Large ASCII style
1255 test_env.add_config(r#"ui.graph.style = "ascii-large""#);
1256 let output = work_dir.run_jj(["log", "-T=description"]);
1257 insta::assert_snapshot!(output, @r"
1258 @ merge
1259 |\
1260 | \
1261 | o side bookmark
1262 | | with
1263 | | long
1264 | | description
1265 | o main bookmark 2
1266 | /
1267 |/
1268 o main bookmark 1
1269 o initial
1270 +
1271 [EOF]
1272 ");
1273
1274 // Curved style
1275 test_env.add_config(r#"ui.graph.style = "curved""#);
1276 let output = work_dir.run_jj(["log", "-T=description"]);
1277 insta::assert_snapshot!(output, @r"
1278 @ merge
1279 ├─╮
1280 │ ○ side bookmark
1281 │ │ with
1282 │ │ long
1283 │ │ description
1284 │ ○ main bookmark 2
1285 ├─╯
1286 ○ main bookmark 1
1287 ○ initial
1288 ◆
1289 [EOF]
1290 ");
1291
1292 // Square style
1293 test_env.add_config(r#"ui.graph.style = "square""#);
1294 let output = work_dir.run_jj(["log", "-T=description"]);
1295 insta::assert_snapshot!(output, @r"
1296 @ merge
1297 ├─┐
1298 │ ○ side bookmark
1299 │ │ with
1300 │ │ long
1301 │ │ description
1302 │ ○ main bookmark 2
1303 ├─┘
1304 ○ main bookmark 1
1305 ○ initial
1306 ◆
1307 [EOF]
1308 ");
1309
1310 // Invalid style name
1311 let output = work_dir.run_jj(["log", "--config=ui.graph.style=unknown"]);
1312 insta::assert_snapshot!(output, @r"
1313 ------- stderr -------
1314 Config error: Invalid type or value for ui.graph.style
1315 Caused by: unknown variant `unknown`, expected one of `ascii`, `ascii-large`, `curved`, `square`
1316
1317 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
1318 [EOF]
1319 [exit status: 1]
1320 ");
1321}
1322
1323#[test]
1324fn test_log_word_wrap() {
1325 let test_env = TestEnvironment::default();
1326 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1327 let work_dir = test_env.work_dir("repo");
1328 let render = |args: &[&str], columns: u32, word_wrap: bool| {
1329 let word_wrap = to_toml_value(word_wrap);
1330 work_dir.run_jj_with(|cmd| {
1331 cmd.args(args)
1332 .arg(format!("--config=ui.log-word-wrap={word_wrap}"))
1333 .env("COLUMNS", columns.to_string())
1334 })
1335 };
1336
1337 work_dir
1338 .run_jj(["commit", "-m", "main bookmark 1"])
1339 .success();
1340 work_dir
1341 .run_jj(["describe", "-m", "main bookmark 2"])
1342 .success();
1343 work_dir.run_jj(["new", "-m", "side"]).success();
1344 work_dir
1345 .run_jj(["new", "-m", "merge", "@--", "@"])
1346 .success();
1347
1348 // ui.log-word-wrap option applies to both graph/no-graph outputs
1349 insta::assert_snapshot!(render(&["log", "-r@"], 40, false), @r"
1350 @ mzvwutvl test.user@example.com 2001-02-03 08:05:11 bafb1ee5
1351 │ (empty) merge
1352 ~
1353 [EOF]
1354 ");
1355 insta::assert_snapshot!(render(&["log", "-r@"], 40, true), @r"
1356 @ mzvwutvl test.user@example.com
1357 │ 2001-02-03 08:05:11 bafb1ee5
1358 ~ (empty) merge
1359 [EOF]
1360 ");
1361 insta::assert_snapshot!(render(&["log", "--no-graph", "-r@"], 40, false), @r"
1362 mzvwutvl test.user@example.com 2001-02-03 08:05:11 bafb1ee5
1363 (empty) merge
1364 [EOF]
1365 ");
1366 insta::assert_snapshot!(render(&["log", "--no-graph", "-r@"], 40, true), @r"
1367 mzvwutvl test.user@example.com
1368 2001-02-03 08:05:11 bafb1ee5
1369 (empty) merge
1370 [EOF]
1371 ");
1372
1373 // Color labels should be preserved
1374 insta::assert_snapshot!(render(&["log", "-r@", "--color=always"], 40, true), @r"
1375 [1m[38;5;2m@[0m [1m[38;5;13mm[38;5;8mzvwutvl[39m [38;5;3mtest.user@example.com[39m[0m
1376 │ [1m[38;5;14m2001-02-03 08:05:11[39m [38;5;12mb[38;5;8mafb1ee5[39m[0m
1377 ~ [1m[38;5;10m(empty)[39m merge[0m
1378 [EOF]
1379 ");
1380
1381 // Graph width should be subtracted from the term width
1382 let template = r#""0 1 2 3 4 5 6 7 8 9""#;
1383 insta::assert_snapshot!(render(&["log", "-T", template], 10, true), @r"
1384 @ 0 1 2
1385 ├─╮ 3 4 5
1386 │ │ 6 7 8
1387 │ │ 9
1388 │ ○ 0 1 2
1389 │ │ 3 4 5
1390 │ │ 6 7 8
1391 │ │ 9
1392 │ ○ 0 1 2
1393 ├─╯ 3 4 5
1394 │ 6 7 8
1395 │ 9
1396 ○ 0 1 2 3
1397 │ 4 5 6 7
1398 │ 8 9
1399 ◆ 0 1 2 3
1400 4 5 6 7
1401 8 9
1402 [EOF]
1403 ");
1404
1405 // Shouldn't panic with $COLUMNS < graph_width
1406 insta::assert_snapshot!(render(&["log", "-r@"], 0, true), @r"
1407 @ mzvwutvl
1408 │ test.user@example.com
1409 ~ 2001-02-03
1410 08:05:11
1411 bafb1ee5
1412 (empty)
1413 merge
1414 [EOF]
1415 ");
1416 insta::assert_snapshot!(render(&["log", "-r@"], 1, true), @r"
1417 @ mzvwutvl
1418 │ test.user@example.com
1419 ~ 2001-02-03
1420 08:05:11
1421 bafb1ee5
1422 (empty)
1423 merge
1424 [EOF]
1425 ");
1426}
1427
1428#[test]
1429fn test_log_diff_stat_width() {
1430 let test_env = TestEnvironment::default();
1431 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1432 let work_dir = test_env.work_dir("repo");
1433 let render = |args: &[&str], columns: u32| {
1434 work_dir.run_jj_with(|cmd| cmd.args(args).env("COLUMNS", columns.to_string()))
1435 };
1436
1437 work_dir.write_file("file1", "foo\n".repeat(100));
1438 work_dir.run_jj(["new", "root()"]).success();
1439 work_dir.write_file("file2", "foo\n".repeat(100));
1440
1441 insta::assert_snapshot!(render(&["log", "--stat", "--no-graph"], 30), @r"
1442 rlvkpnrz test.user@example.com 2001-02-03 08:05:09 9490cfd3
1443 (no description set)
1444 file2 | 100 +++++++++++++++
1445 1 file changed, 100 insertions(+), 0 deletions(-)
1446 qpvuntsm test.user@example.com 2001-02-03 08:05:08 79f0968d
1447 (no description set)
1448 file1 | 100 +++++++++++++++
1449 1 file changed, 100 insertions(+), 0 deletions(-)
1450 zzzzzzzz root() 00000000
1451 0 files changed, 0 insertions(+), 0 deletions(-)
1452 [EOF]
1453 ");
1454
1455 // Graph width should be subtracted
1456 insta::assert_snapshot!(render(&["log", "--stat"], 30), @r"
1457 @ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 9490cfd3
1458 │ (no description set)
1459 │ file2 | 100 ++++++++++++
1460 │ 1 file changed, 100 insertions(+), 0 deletions(-)
1461 │ ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 79f0968d
1462 ├─╯ (no description set)
1463 │ file1 | 100 ++++++++++
1464 │ 1 file changed, 100 insertions(+), 0 deletions(-)
1465 ◆ zzzzzzzz root() 00000000
1466 0 files changed, 0 insertions(+), 0 deletions(-)
1467 [EOF]
1468 ");
1469}
1470
1471#[test]
1472fn test_elided() {
1473 // Test that elided commits are shown as synthetic nodes.
1474 let test_env = TestEnvironment::default();
1475 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1476 let work_dir = test_env.work_dir("repo");
1477
1478 work_dir.run_jj(["describe", "-m", "initial"]).success();
1479 work_dir.run_jj(["new", "-m", "main bookmark 1"]).success();
1480 work_dir.run_jj(["new", "-m", "main bookmark 2"]).success();
1481 work_dir
1482 .run_jj(["new", "@--", "-m", "side bookmark 1"])
1483 .success();
1484 work_dir.run_jj(["new", "-m", "side bookmark 2"]).success();
1485 work_dir
1486 .run_jj([
1487 "new",
1488 "-m",
1489 "merge",
1490 r#"description("main bookmark 2")"#,
1491 "@",
1492 ])
1493 .success();
1494
1495 let get_log = |revs: &str| work_dir.run_jj(["log", "-T", r#"description ++ "\n""#, "-r", revs]);
1496
1497 // Test the setup
1498 insta::assert_snapshot!(get_log("::"), @r"
1499 @ merge
1500 ├─╮
1501 │ ○ side bookmark 2
1502 │ │
1503 │ ○ side bookmark 1
1504 │ │
1505 ○ │ main bookmark 2
1506 │ │
1507 ○ │ main bookmark 1
1508 ├─╯
1509 ○ initial
1510 │
1511 ◆
1512 [EOF]
1513 ");
1514
1515 // Elide some commits from each side of the merge. It's unclear that a revision
1516 // was skipped on the left side.
1517 test_env.add_config("ui.log-synthetic-elided-nodes = false");
1518 insta::assert_snapshot!(get_log("@ | @- | description(initial)"), @r"
1519 @ merge
1520 ├─╮
1521 │ ○ side bookmark 2
1522 │ ╷
1523 ○ ╷ main bookmark 2
1524 ├─╯
1525 ○ initial
1526 │
1527 ~
1528 [EOF]
1529 ");
1530
1531 // Elide shared commits. It's unclear that a revision was skipped on the right
1532 // side (#1252).
1533 insta::assert_snapshot!(get_log("@-- | root()"), @r"
1534 ○ side bookmark 1
1535 ╷
1536 ╷ ○ main bookmark 1
1537 ╭─╯
1538 ◆
1539 [EOF]
1540 ");
1541
1542 // Now test the same thing with synthetic nodes for elided commits
1543
1544 // Elide some commits from each side of the merge
1545 test_env.add_config("ui.log-synthetic-elided-nodes = true");
1546 insta::assert_snapshot!(get_log("@ | @- | description(initial)"), @r"
1547 @ merge
1548 ├─╮
1549 │ ○ side bookmark 2
1550 │ │
1551 │ ~ (elided revisions)
1552 ○ │ main bookmark 2
1553 │ │
1554 ~ │ (elided revisions)
1555 ├─╯
1556 ○ initial
1557 │
1558 ~
1559 [EOF]
1560 ");
1561
1562 // Elide shared commits. To keep the implementation simple, it still gets
1563 // rendered as two synthetic nodes.
1564 insta::assert_snapshot!(get_log("@-- | root()"), @r"
1565 ○ side bookmark 1
1566 │
1567 ~ (elided revisions)
1568 │ ○ main bookmark 1
1569 │ │
1570 │ ~ (elided revisions)
1571 ├─╯
1572 ◆
1573 [EOF]
1574 ");
1575}
1576
1577#[test]
1578fn test_log_with_custom_symbols() {
1579 // Test that elided commits are shown as synthetic nodes.
1580 let test_env = TestEnvironment::default();
1581 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1582 let work_dir = test_env.work_dir("repo");
1583
1584 work_dir.run_jj(["describe", "-m", "initial"]).success();
1585 work_dir.run_jj(["new", "-m", "main bookmark 1"]).success();
1586 work_dir.run_jj(["new", "-m", "main bookmark 2"]).success();
1587 work_dir
1588 .run_jj(["new", "@--", "-m", "side bookmark 1"])
1589 .success();
1590 work_dir.run_jj(["new", "-m", "side bookmark 2"]).success();
1591 work_dir
1592 .run_jj([
1593 "new",
1594 "-m",
1595 "merge",
1596 r#"description("main bookmark 2")"#,
1597 "@",
1598 ])
1599 .success();
1600
1601 let get_log = |revs: &str| work_dir.run_jj(["log", "-T", r#"description ++ "\n""#, "-r", revs]);
1602
1603 // Simple test with showing default and elided nodes.
1604 test_env.add_config(
1605 r###"
1606 ui.log-synthetic-elided-nodes = true
1607 templates.log_node = 'if(self, if(current_working_copy, "$", if(root, "┴", "┝")), "🮀")'
1608 "###,
1609 );
1610 insta::assert_snapshot!(get_log("@ | @- | description(initial) | root()"), @r"
1611 $ merge
1612 ├─╮
1613 │ ┝ side bookmark 2
1614 │ │
1615 │ 🮀 (elided revisions)
1616 ┝ │ main bookmark 2
1617 │ │
1618 🮀 │ (elided revisions)
1619 ├─╯
1620 ┝ initial
1621 │
1622 ┴
1623 [EOF]
1624 ");
1625
1626 // Simple test with showing default and elided nodes, ascii style.
1627 test_env.add_config(
1628 r###"
1629 ui.log-synthetic-elided-nodes = true
1630 ui.graph.style = 'ascii'
1631 templates.log_node = 'if(self, if(current_working_copy, "$", if(root, "^", "*")), ":")'
1632 "###,
1633 );
1634 insta::assert_snapshot!(get_log("@ | @- | description(initial) | root()"), @r"
1635 $ merge
1636 |\
1637 | * side bookmark 2
1638 | |
1639 | : (elided revisions)
1640 * | main bookmark 2
1641 | |
1642 : | (elided revisions)
1643 |/
1644 * initial
1645 |
1646 ^
1647 [EOF]
1648 ");
1649}
1650
1651#[test]
1652fn test_log_full_description_template() {
1653 let test_env = TestEnvironment::default();
1654 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1655 let work_dir = test_env.work_dir("repo");
1656
1657 work_dir
1658 .run_jj([
1659 "describe",
1660 "-m",
1661 "this is commit with a multiline description\n\n<full description>",
1662 ])
1663 .success();
1664
1665 let output = work_dir.run_jj(["log", "-T", "builtin_log_compact_full_description"]);
1666 insta::assert_snapshot!(output, @r"
1667 @ qpvuntsm test.user@example.com 2001-02-03 08:05:08 37b69cda
1668 │ (empty) this is commit with a multiline description
1669 │
1670 │ <full description>
1671 │
1672 ◆ zzzzzzzz root() 00000000
1673 [EOF]
1674 ");
1675}