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::TestEnvironment;
16
17#[test]
18fn test_syntax_error() {
19 let test_env = TestEnvironment::default();
20 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
21 let work_dir = test_env.work_dir("repo");
22
23 let output = work_dir.run_jj(["log", "-r", ":x"]);
24 insta::assert_snapshot!(output, @r"
25 ------- stderr -------
26 Error: Failed to parse revset: `:` is not a prefix operator
27 Caused by: --> 1:1
28 |
29 1 | :x
30 | ^
31 |
32 = `:` is not a prefix operator
33 Hint: Did you mean `::` for ancestors?
34 [EOF]
35 [exit status: 1]
36 ");
37
38 let output = work_dir.run_jj(["log", "-r", "x &"]);
39 insta::assert_snapshot!(output, @r"
40 ------- stderr -------
41 Error: Failed to parse revset: Syntax error
42 Caused by: --> 1:4
43 |
44 1 | x &
45 | ^---
46 |
47 = expected `::`, `..`, `~`, or <primary>
48 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for revsets syntax and how to quote symbols.
49 [EOF]
50 [exit status: 1]
51 ");
52
53 let output = work_dir.run_jj(["log", "-r", "x - y"]);
54 insta::assert_snapshot!(output, @r"
55 ------- stderr -------
56 Error: Failed to parse revset: `-` is not an infix operator
57 Caused by: --> 1:3
58 |
59 1 | x - y
60 | ^
61 |
62 = `-` is not an infix operator
63 Hint: Did you mean `~` for difference?
64 [EOF]
65 [exit status: 1]
66 ");
67
68 let output = work_dir.run_jj(["log", "-r", "HEAD^"]);
69 insta::assert_snapshot!(output, @r"
70 ------- stderr -------
71 Error: Failed to parse revset: `^` is not a postfix operator
72 Caused by: --> 1:5
73 |
74 1 | HEAD^
75 | ^
76 |
77 = `^` is not a postfix operator
78 Hint: Did you mean `-` for parents?
79 [EOF]
80 [exit status: 1]
81 ");
82}
83
84#[test]
85fn test_bad_function_call() {
86 let test_env = TestEnvironment::default();
87 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
88 let work_dir = test_env.work_dir("repo");
89
90 let output = work_dir.run_jj(["log", "-r", "all(or::nothing)"]);
91 insta::assert_snapshot!(output, @r"
92 ------- stderr -------
93 Error: Failed to parse revset: Function `all`: Expected 0 arguments
94 Caused by: --> 1:5
95 |
96 1 | all(or::nothing)
97 | ^---------^
98 |
99 = Function `all`: Expected 0 arguments
100 [EOF]
101 [exit status: 1]
102 ");
103
104 let output = work_dir.run_jj(["log", "-r", "parents()"]);
105 insta::assert_snapshot!(output, @r"
106 ------- stderr -------
107 Error: Failed to parse revset: Function `parents`: Expected 1 arguments
108 Caused by: --> 1:9
109 |
110 1 | parents()
111 | ^
112 |
113 = Function `parents`: Expected 1 arguments
114 [EOF]
115 [exit status: 1]
116 ");
117
118 let output = work_dir.run_jj(["log", "-r", "parents(foo, bar)"]);
119 insta::assert_snapshot!(output, @r"
120 ------- stderr -------
121 Error: Failed to parse revset: Function `parents`: Expected 1 arguments
122 Caused by: --> 1:9
123 |
124 1 | parents(foo, bar)
125 | ^------^
126 |
127 = Function `parents`: Expected 1 arguments
128 [EOF]
129 [exit status: 1]
130 ");
131
132 let output = work_dir.run_jj(["log", "-r", "heads(foo, bar)"]);
133 insta::assert_snapshot!(output, @r"
134 ------- stderr -------
135 Error: Failed to parse revset: Function `heads`: Expected 1 arguments
136 Caused by: --> 1:7
137 |
138 1 | heads(foo, bar)
139 | ^------^
140 |
141 = Function `heads`: Expected 1 arguments
142 [EOF]
143 [exit status: 1]
144 ");
145
146 let output = work_dir.run_jj(["log", "-r", "latest(a, not_an_integer)"]);
147 insta::assert_snapshot!(output, @r"
148 ------- stderr -------
149 Error: Failed to parse revset: Expected expression of type integer
150 Caused by: --> 1:11
151 |
152 1 | latest(a, not_an_integer)
153 | ^------------^
154 |
155 = Expected expression of type integer
156 [EOF]
157 [exit status: 1]
158 ");
159
160 // "N to M arguments"
161 let output = work_dir.run_jj(["log", "-r", "ancestors()"]);
162 insta::assert_snapshot!(output, @r"
163 ------- stderr -------
164 Error: Failed to parse revset: Function `ancestors`: Expected 1 to 2 arguments
165 Caused by: --> 1:11
166 |
167 1 | ancestors()
168 | ^
169 |
170 = Function `ancestors`: Expected 1 to 2 arguments
171 [EOF]
172 [exit status: 1]
173 ");
174
175 let output = work_dir.run_jj(["log", "-r", "files(not::a-fileset)"]);
176 insta::assert_snapshot!(output, @r"
177 ------- stderr -------
178 Error: Failed to parse revset: In fileset expression
179 Caused by:
180 1: --> 1:7
181 |
182 1 | files(not::a-fileset)
183 | ^------------^
184 |
185 = In fileset expression
186 2: --> 1:5
187 |
188 1 | not::a-fileset
189 | ^---
190 |
191 = expected <identifier>, <string_literal>, or <raw_string_literal>
192 Hint: See https://jj-vcs.github.io/jj/latest/filesets/ or use `jj help -k filesets` for filesets syntax and how to match file paths.
193 [EOF]
194 [exit status: 1]
195 ");
196
197 let output = work_dir.run_jj(["log", "-r", r#"files(foo:"bar")"#]);
198 insta::assert_snapshot!(output, @r#"
199 ------- stderr -------
200 Error: Failed to parse revset: In fileset expression
201 Caused by:
202 1: --> 1:7
203 |
204 1 | files(foo:"bar")
205 | ^-------^
206 |
207 = In fileset expression
208 2: --> 1:1
209 |
210 1 | foo:"bar"
211 | ^-------^
212 |
213 = Invalid file pattern
214 3: Invalid file pattern kind `foo:`
215 Hint: See https://jj-vcs.github.io/jj/latest/filesets/#file-patterns or `jj help -k filesets` for valid prefixes.
216 [EOF]
217 [exit status: 1]
218 "#);
219
220 let output = work_dir.run_jj(["log", "-r", r#"files("../out")"#]);
221 insta::assert_snapshot!(output.normalize_backslash(), @r#"
222 ------- stderr -------
223 Error: Failed to parse revset: In fileset expression
224 Caused by:
225 1: --> 1:7
226 |
227 1 | files("../out")
228 | ^------^
229 |
230 = In fileset expression
231 2: --> 1:1
232 |
233 1 | "../out"
234 | ^------^
235 |
236 = Invalid file pattern
237 3: Path "../out" is not in the repo "."
238 4: Invalid component ".." in repo-relative path "../out"
239 [EOF]
240 [exit status: 1]
241 "#);
242
243 let output = work_dir.run_jj(["log", "-r", "bookmarks(bad:pattern)"]);
244 insta::assert_snapshot!(output, @r"
245 ------- stderr -------
246 Error: Failed to parse revset: Invalid string pattern
247 Caused by:
248 1: --> 1:11
249 |
250 1 | bookmarks(bad:pattern)
251 | ^---------^
252 |
253 = Invalid string pattern
254 2: Invalid string pattern kind `bad:`
255 Hint: Try prefixing with one of `exact:`, `glob:`, `regex:`, `substring:`, or one of these with `-i` suffix added (e.g. `glob-i:`) for case-insensitive matching
256 [EOF]
257 [exit status: 1]
258 ");
259
260 let output = work_dir.run_jj(["log", "-r", "bookmarks(regex:'(')"]);
261 insta::assert_snapshot!(output, @r"
262 ------- stderr -------
263 Error: Failed to parse revset: Invalid string pattern
264 Caused by:
265 1: --> 1:11
266 |
267 1 | bookmarks(regex:'(')
268 | ^-------^
269 |
270 = Invalid string pattern
271 2: regex parse error:
272 (
273 ^
274 error: unclosed group
275 [EOF]
276 [exit status: 1]
277 ");
278
279 let output = work_dir.run_jj(["log", "-r", "root()::whatever()"]);
280 insta::assert_snapshot!(output, @r"
281 ------- stderr -------
282 Error: Failed to parse revset: Function `whatever` doesn't exist
283 Caused by: --> 1:9
284 |
285 1 | root()::whatever()
286 | ^------^
287 |
288 = Function `whatever` doesn't exist
289 [EOF]
290 [exit status: 1]
291 ");
292
293 let output = work_dir.run_jj(["log", "-r", "remote_bookmarks(a, b, remote=c)"]);
294 insta::assert_snapshot!(output, @r#"
295 ------- stderr -------
296 Error: Failed to parse revset: Function `remote_bookmarks`: Got multiple values for keyword "remote"
297 Caused by: --> 1:24
298 |
299 1 | remote_bookmarks(a, b, remote=c)
300 | ^------^
301 |
302 = Function `remote_bookmarks`: Got multiple values for keyword "remote"
303 [EOF]
304 [exit status: 1]
305 "#);
306
307 let output = work_dir.run_jj(["log", "-r", "remote_bookmarks(remote=a, b)"]);
308 insta::assert_snapshot!(output, @r"
309 ------- stderr -------
310 Error: Failed to parse revset: Function `remote_bookmarks`: Positional argument follows keyword argument
311 Caused by: --> 1:28
312 |
313 1 | remote_bookmarks(remote=a, b)
314 | ^
315 |
316 = Function `remote_bookmarks`: Positional argument follows keyword argument
317 [EOF]
318 [exit status: 1]
319 ");
320
321 let output = work_dir.run_jj(["log", "-r", "remote_bookmarks(=foo)"]);
322 insta::assert_snapshot!(output, @r"
323 ------- stderr -------
324 Error: Failed to parse revset: Syntax error
325 Caused by: --> 1:18
326 |
327 1 | remote_bookmarks(=foo)
328 | ^---
329 |
330 = expected <strict_identifier> or <expression>
331 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for revsets syntax and how to quote symbols.
332 [EOF]
333 [exit status: 1]
334 ");
335
336 let output = work_dir.run_jj(["log", "-r", "remote_bookmarks(remote=)"]);
337 insta::assert_snapshot!(output, @r"
338 ------- stderr -------
339 Error: Failed to parse revset: Syntax error
340 Caused by: --> 1:25
341 |
342 1 | remote_bookmarks(remote=)
343 | ^---
344 |
345 = expected <expression>
346 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for revsets syntax and how to quote symbols.
347 [EOF]
348 [exit status: 1]
349 ");
350}
351
352#[test]
353fn test_function_name_hint() {
354 let test_env = TestEnvironment::default();
355 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
356 let work_dir = test_env.work_dir("repo");
357 let evaluate = |expr| work_dir.run_jj(["log", "-r", expr]);
358
359 test_env.add_config(
360 r###"
361 [revset-aliases]
362 'bookmarks(x)' = 'x' # override builtin function
363 'my_author(x)' = 'author(x)' # similar name to builtin function
364 'author_sym' = 'x' # not a function alias
365 'my_bookmarks' = 'bookmark()' # typo in alias
366 "###,
367 );
368
369 // The suggestion "bookmarks" shouldn't be duplicated
370 insta::assert_snapshot!(evaluate("bookmark()"), @r"
371 ------- stderr -------
372 Error: Failed to parse revset: Function `bookmark` doesn't exist
373 Caused by: --> 1:1
374 |
375 1 | bookmark()
376 | ^------^
377 |
378 = Function `bookmark` doesn't exist
379 Hint: Did you mean `bookmarks`, `remote_bookmarks`?
380 [EOF]
381 [exit status: 1]
382 ");
383
384 // Both builtin function and function alias should be suggested
385 insta::assert_snapshot!(evaluate("author_()"), @r"
386 ------- stderr -------
387 Error: Failed to parse revset: Function `author_` doesn't exist
388 Caused by: --> 1:1
389 |
390 1 | author_()
391 | ^-----^
392 |
393 = Function `author_` doesn't exist
394 Hint: Did you mean `author`, `author_date`, `author_email`, `author_name`, `my_author`?
395 [EOF]
396 [exit status: 1]
397 ");
398
399 insta::assert_snapshot!(evaluate("my_bookmarks"), @r"
400 ------- stderr -------
401 Error: Failed to parse revset: In alias `my_bookmarks`
402 Caused by:
403 1: --> 1:1
404 |
405 1 | my_bookmarks
406 | ^----------^
407 |
408 = In alias `my_bookmarks`
409 2: --> 1:1
410 |
411 1 | bookmark()
412 | ^------^
413 |
414 = Function `bookmark` doesn't exist
415 Hint: Did you mean `bookmarks`, `remote_bookmarks`?
416 [EOF]
417 [exit status: 1]
418 ");
419}
420
421#[test]
422fn test_alias() {
423 let test_env = TestEnvironment::default();
424 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
425 let work_dir = test_env.work_dir("repo");
426
427 test_env.add_config(
428 r###"
429 [revset-aliases]
430 'my-root' = 'root()'
431 'syntax-error' = 'whatever &'
432 'recurse' = 'recurse1'
433 'recurse1' = 'recurse2()'
434 'recurse2()' = 'recurse'
435 'identity(x)' = 'x'
436 'my_author(x)' = 'author(x)'
437 "###,
438 );
439
440 let output = work_dir.run_jj(["log", "-r", "my-root"]);
441 insta::assert_snapshot!(output, @r"
442 ◆ zzzzzzzz root() 00000000
443 [EOF]
444 ");
445
446 let output = work_dir.run_jj(["log", "-r", "identity(my-root)"]);
447 insta::assert_snapshot!(output, @r"
448 ◆ zzzzzzzz root() 00000000
449 [EOF]
450 ");
451
452 let output = work_dir.run_jj(["log", "-r", "root() & syntax-error"]);
453 insta::assert_snapshot!(output, @r"
454 ------- stderr -------
455 Error: Failed to parse revset: In alias `syntax-error`
456 Caused by:
457 1: --> 1:10
458 |
459 1 | root() & syntax-error
460 | ^----------^
461 |
462 = In alias `syntax-error`
463 2: --> 1:11
464 |
465 1 | whatever &
466 | ^---
467 |
468 = expected `::`, `..`, `~`, or <primary>
469 Hint: See https://jj-vcs.github.io/jj/latest/revsets/ or use `jj help -k revsets` for revsets syntax and how to quote symbols.
470 [EOF]
471 [exit status: 1]
472 ");
473
474 let output = work_dir.run_jj(["log", "-r", "identity()"]);
475 insta::assert_snapshot!(output, @r"
476 ------- stderr -------
477 Error: Failed to parse revset: Function `identity`: Expected 1 arguments
478 Caused by: --> 1:10
479 |
480 1 | identity()
481 | ^
482 |
483 = Function `identity`: Expected 1 arguments
484 [EOF]
485 [exit status: 1]
486 ");
487
488 let output = work_dir.run_jj(["log", "-r", "my_author(none())"]);
489 insta::assert_snapshot!(output, @r"
490 ------- stderr -------
491 Error: Failed to parse revset: In alias `my_author(x)`
492 Caused by:
493 1: --> 1:1
494 |
495 1 | my_author(none())
496 | ^---------------^
497 |
498 = In alias `my_author(x)`
499 2: --> 1:8
500 |
501 1 | author(x)
502 | ^
503 |
504 = In function parameter `x`
505 3: --> 1:11
506 |
507 1 | my_author(none())
508 | ^----^
509 |
510 = Expected expression of string pattern
511 [EOF]
512 [exit status: 1]
513 ");
514
515 let output = work_dir.run_jj(["log", "-r", "root() & recurse"]);
516 insta::assert_snapshot!(output, @r"
517 ------- stderr -------
518 Error: Failed to parse revset: In alias `recurse`
519 Caused by:
520 1: --> 1:10
521 |
522 1 | root() & recurse
523 | ^-----^
524 |
525 = In alias `recurse`
526 2: --> 1:1
527 |
528 1 | recurse1
529 | ^------^
530 |
531 = In alias `recurse1`
532 3: --> 1:1
533 |
534 1 | recurse2()
535 | ^--------^
536 |
537 = In alias `recurse2()`
538 4: --> 1:1
539 |
540 1 | recurse
541 | ^-----^
542 |
543 = Alias `recurse` expanded recursively
544 [EOF]
545 [exit status: 1]
546 ");
547}
548
549#[test]
550fn test_alias_override() {
551 let test_env = TestEnvironment::default();
552 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
553 let work_dir = test_env.work_dir("repo");
554
555 test_env.add_config(
556 r###"
557 [revset-aliases]
558 'f(x)' = 'user'
559 "###,
560 );
561
562 // 'f(x)' should be overridden by --config 'f(a)'. If aliases were sorted
563 // purely by name, 'f(a)' would come first.
564 let output = work_dir.run_jj(["log", "-r", "f(_)", "--config=revset-aliases.'f(a)'=arg"]);
565 insta::assert_snapshot!(output, @r"
566 ------- stderr -------
567 Error: Revision `arg` doesn't exist
568 [EOF]
569 [exit status: 1]
570 ");
571}
572
573#[test]
574fn test_bad_alias_decl() {
575 let test_env = TestEnvironment::default();
576 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
577 let work_dir = test_env.work_dir("repo");
578
579 test_env.add_config(
580 r#"
581 [revset-aliases]
582 'my-root' = 'root()'
583 '"bad"' = 'root()'
584 'badfn(a, a)' = 'root()'
585 "#,
586 );
587
588 // Invalid declaration should be warned and ignored.
589 let output = work_dir.run_jj(["log", "-r", "my-root"]);
590 insta::assert_snapshot!(output, @r#"
591 ◆ zzzzzzzz root() 00000000
592 [EOF]
593 ------- stderr -------
594 Warning: Failed to load `revset-aliases."bad"`: --> 1:1
595 |
596 1 | "bad"
597 | ^---
598 |
599 = expected <strict_identifier> or <function_name>
600 Warning: Failed to load `revset-aliases.badfn(a, a)`: --> 1:7
601 |
602 1 | badfn(a, a)
603 | ^--^
604 |
605 = Redefinition of function parameter
606 [EOF]
607 "#);
608}
609
610#[test]
611fn test_all_modifier() {
612 let test_env = TestEnvironment::default();
613 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
614 let work_dir = test_env.work_dir("repo");
615
616 // Command that accepts single revision by default
617 let output = work_dir.run_jj(["new", "all()"]);
618 insta::assert_snapshot!(output, @r"
619 ------- stderr -------
620 Error: Revset `all()` resolved to more than one revision
621 Hint: The revset `all()` resolved to these revisions:
622 qpvuntsm 230dd059 (empty) (no description set)
623 zzzzzzzz 00000000 (empty) (no description set)
624 Hint: Prefix the expression with `all:` to allow any number of revisions (i.e. `all:all()`).
625 [EOF]
626 [exit status: 1]
627 ");
628 let output = work_dir.run_jj(["new", "all:all()"]);
629 insta::assert_snapshot!(output, @r"
630 ------- stderr -------
631 Error: The Git backend does not support creating merge commits with the root commit as one of the parents.
632 [EOF]
633 [exit status: 1]
634 ");
635
636 // Command that accepts multiple revisions by default
637 let output = work_dir.run_jj(["log", "-rall:all()"]);
638 insta::assert_snapshot!(output, @r"
639 @ qpvuntsm test.user@example.com 2001-02-03 08:05:07 230dd059
640 │ (empty) (no description set)
641 ◆ zzzzzzzz root() 00000000
642 [EOF]
643 ");
644
645 // Command that accepts only single revision
646 let output = work_dir.run_jj(["bookmark", "create", "-rall:@", "x"]);
647 insta::assert_snapshot!(output, @r"
648 ------- stderr -------
649 Created 1 bookmarks pointing to qpvuntsm 230dd059 x | (empty) (no description set)
650 [EOF]
651 ");
652 let output = work_dir.run_jj(["bookmark", "set", "-rall:all()", "x"]);
653 insta::assert_snapshot!(output, @r"
654 ------- stderr -------
655 Error: Revset `all:all()` resolved to more than one revision
656 Hint: The revset `all:all()` resolved to these revisions:
657 qpvuntsm 230dd059 x | (empty) (no description set)
658 zzzzzzzz 00000000 (empty) (no description set)
659 [EOF]
660 [exit status: 1]
661 ");
662
663 // Template expression that accepts multiple revisions by default
664 let output = work_dir.run_jj(["log", "-Tself.contained_in('all:all()')"]);
665 insta::assert_snapshot!(output, @r"
666 @ true
667 ◆ true
668 [EOF]
669 ");
670
671 // Typo
672 let output = work_dir.run_jj(["new", "ale:x"]);
673 insta::assert_snapshot!(output, @r"
674 ------- stderr -------
675 Error: Failed to parse revset: Modifier `ale` doesn't exist
676 Caused by: --> 1:1
677 |
678 1 | ale:x
679 | ^-^
680 |
681 = Modifier `ale` doesn't exist
682 [EOF]
683 [exit status: 1]
684 ");
685
686 // Modifier shouldn't be allowed in sub expression
687 let output = work_dir.run_jj(["new", "x..", "--config=revset-aliases.x='all:@'"]);
688 insta::assert_snapshot!(output, @r"
689 ------- stderr -------
690 Error: Failed to parse revset: In alias `x`
691 Caused by:
692 1: --> 1:1
693 |
694 1 | x..
695 | ^
696 |
697 = In alias `x`
698 2: --> 1:1
699 |
700 1 | all:@
701 | ^-^
702 |
703 = Modifier `all:` is not allowed in sub expression
704 [EOF]
705 [exit status: 1]
706 ");
707
708 // immutable_heads() alias may be parsed as a top-level expression, but
709 // still, modifier shouldn't be allowed there.
710 let output = work_dir.run_jj([
711 "new",
712 "--config=revset-aliases.'immutable_heads()'='all:@'",
713 "--config=revsets.short-prefixes='none()'",
714 ]);
715 insta::assert_snapshot!(output, @r"
716 ------- stderr -------
717 Config error: Invalid `revset-aliases.immutable_heads()`
718 Caused by: --> 1:1
719 |
720 1 | all:@
721 | ^-^
722 |
723 = Modifier `all:` is not allowed in sub expression
724 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`.
725 [EOF]
726 [exit status: 1]
727 ");
728}
729
730/// Verifies that the committer_date revset honors the local time zone.
731/// This test cannot run on Windows because The TZ env var does not control
732/// chrono::Local on that platform.
733#[test]
734#[cfg(not(target_os = "windows"))]
735fn test_revset_committer_date_with_time_zone() {
736 // Use these for the test instead of tzdb identifiers like America/New_York
737 // because the tz database may not be installed on some build servers
738 const NEW_YORK: &str = "EST+5EDT+4,M3.1.0,M11.1.0";
739 const CHICAGO: &str = "CST+6CDT+5,M3.1.0,M11.1.0";
740 const AUSTRALIA: &str = "AEST-10";
741 let mut test_env = TestEnvironment::default();
742 test_env.add_env_var("TZ", NEW_YORK);
743 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
744 let work_dir = test_env.work_dir("repo");
745
746 work_dir
747 .run_jj([
748 "--config=debug.commit-timestamp=2023-01-25T11:30:00-05:00",
749 "describe",
750 "-m",
751 "first",
752 ])
753 .success();
754 work_dir
755 .run_jj([
756 "--config=debug.commit-timestamp=2023-01-25T12:30:00-05:00",
757 "new",
758 "-m",
759 "second",
760 ])
761 .success();
762 work_dir
763 .run_jj([
764 "--config=debug.commit-timestamp=2023-01-25T13:30:00-05:00",
765 "new",
766 "-m",
767 "third",
768 ])
769 .success();
770
771 let mut log_commits_before_and_after = |committer_date: &str, now: &str, tz: &str| {
772 test_env.add_env_var("TZ", tz);
773 let config = format!("debug.commit-timestamp={now}");
774 let work_dir = test_env.work_dir("repo");
775 let before_log = work_dir.run_jj([
776 "--config",
777 config.as_str(),
778 "log",
779 "--no-graph",
780 "-T",
781 "description.first_line() ++ ' ' ++ committer.timestamp() ++ '\n'",
782 "-r",
783 format!("committer_date(before:'{committer_date}') ~ root()").as_str(),
784 ]);
785 let after_log = work_dir.run_jj([
786 "--config",
787 config.as_str(),
788 "log",
789 "--no-graph",
790 "-T",
791 "description.first_line() ++ ' ' ++ committer.timestamp() ++ '\n'",
792 "-r",
793 format!("committer_date(after:'{committer_date}')").as_str(),
794 ]);
795 (before_log, after_log)
796 };
797
798 let (before_log, after_log) =
799 log_commits_before_and_after("2023-01-25 12:00", "2023-02-01T00:00:00-05:00", NEW_YORK);
800 insta::assert_snapshot!(before_log, @r"
801 first 2023-01-25 11:30:00.000 -05:00
802 [EOF]
803 ");
804 insta::assert_snapshot!(after_log, @r"
805 third 2023-01-25 13:30:00.000 -05:00
806 second 2023-01-25 12:30:00.000 -05:00
807 [EOF]
808 ");
809
810 // Switch to DST and ensure we get the same results, because it should
811 // evaluate 12:00 on commit date, not the current date
812 let (before_log, after_log) =
813 log_commits_before_and_after("2023-01-25 12:00", "2023-06-01T00:00:00-04:00", NEW_YORK);
814 insta::assert_snapshot!(before_log, @r"
815 first 2023-01-25 11:30:00.000 -05:00
816 [EOF]
817 ");
818 insta::assert_snapshot!(after_log, @r"
819 third 2023-01-25 13:30:00.000 -05:00
820 second 2023-01-25 12:30:00.000 -05:00
821 [EOF]
822 ");
823
824 // Change the local time zone and ensure the result changes
825 let (before_log, after_log) =
826 log_commits_before_and_after("2023-01-25 12:00", "2023-06-01T00:00:00-06:00", CHICAGO);
827 insta::assert_snapshot!(before_log, @r"
828 second 2023-01-25 12:30:00.000 -05:00
829 first 2023-01-25 11:30:00.000 -05:00
830 [EOF]
831 ");
832 insta::assert_snapshot!(after_log, @r"
833 third 2023-01-25 13:30:00.000 -05:00
834 [EOF]
835 ");
836
837 // Time zone far outside USA with no DST
838 let (before_log, after_log) =
839 log_commits_before_and_after("2023-01-26 03:00", "2023-06-01T00:00:00+10:00", AUSTRALIA);
840 insta::assert_snapshot!(before_log, @r"
841 first 2023-01-25 11:30:00.000 -05:00
842 [EOF]
843 ");
844 insta::assert_snapshot!(after_log, @r"
845 third 2023-01-25 13:30:00.000 -05:00
846 second 2023-01-25 12:30:00.000 -05:00
847 [EOF]
848 ");
849}