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