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