just playing with tangled
at gvimdiff 64 kB view raw
1// Copyright 2023 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 indoc::indoc; 16use regex::Regex; 17use testutils::git; 18 19use crate::common::TestEnvironment; 20 21#[test] 22fn test_log_parents() { 23 let test_env = TestEnvironment::default(); 24 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 25 let work_dir = test_env.work_dir("repo"); 26 27 work_dir.run_jj(["new"]).success(); 28 work_dir.run_jj(["new", "@-"]).success(); 29 work_dir.run_jj(["new", "@", "@-"]).success(); 30 31 let template = 32 r#"commit_id ++ "\nP: " ++ parents.len() ++ " " ++ parents.map(|c| c.commit_id()) ++ "\n""#; 33 let output = work_dir.run_jj(["log", "-T", template]); 34 insta::assert_snapshot!(output, @r" 35 @ c067170d4ca1bc6162b64f7550617ec809647f84 36 ├─╮ P: 2 4db490c88528133d579540b6900b8098f0c17701 230dd059e1b059aefc0da06a2e5a7dbf22362f22 37 ○ │ 4db490c88528133d579540b6900b8098f0c17701 38 ├─╯ P: 1 230dd059e1b059aefc0da06a2e5a7dbf22362f22 39 ○ 230dd059e1b059aefc0da06a2e5a7dbf22362f22 40 │ P: 1 0000000000000000000000000000000000000000 41 ◆ 0000000000000000000000000000000000000000 42 P: 0 43 [EOF] 44 "); 45 46 // List<Commit> can be filtered 47 let template = 48 r#""P: " ++ parents.filter(|c| !c.root()).map(|c| c.commit_id().short()) ++ "\n""#; 49 let output = work_dir.run_jj(["log", "-T", template]); 50 insta::assert_snapshot!(output, @r" 51 @ P: 4db490c88528 230dd059e1b0 52 ├─╮ 53 ○ │ P: 230dd059e1b0 54 ├─╯ 55 ○ P: 56 ◆ P: 57 [EOF] 58 "); 59 60 let template = r#"parents.map(|c| c.commit_id().shortest(4))"#; 61 let output = work_dir.run_jj(["log", "-T", template, "-r@", "--color=always"]); 62 insta::assert_snapshot!(output, @r" 63 @ 4db4 230d 64 65 ~ 66 [EOF] 67 "); 68 69 // Commit object isn't printable 70 let output = work_dir.run_jj(["log", "-T", "parents"]); 71 insta::assert_snapshot!(output, @r" 72 ------- stderr ------- 73 Error: Failed to parse template: Expected expression of type `Template`, but actual type is `List<Commit>` 74 Caused by: --> 1:1 75 | 76 1 | parents 77 | ^-----^ 78 | 79 = Expected expression of type `Template`, but actual type is `List<Commit>` 80 [EOF] 81 [exit status: 1] 82 "); 83 84 // Redundant argument passed to keyword method 85 let template = r#"parents.map(|c| c.commit_id(""))"#; 86 let output = work_dir.run_jj(["log", "-T", template]); 87 insta::assert_snapshot!(output, @r#" 88 ------- stderr ------- 89 Error: Failed to parse template: Function `commit_id`: Expected 0 arguments 90 Caused by: --> 1:29 91 | 92 1 | parents.map(|c| c.commit_id("")) 93 | ^^ 94 | 95 = Function `commit_id`: Expected 0 arguments 96 [EOF] 97 [exit status: 1] 98 "#); 99} 100 101#[test] 102fn test_log_author_timestamp() { 103 let test_env = TestEnvironment::default(); 104 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 105 let work_dir = test_env.work_dir("repo"); 106 107 work_dir.run_jj(["describe", "-m", "first"]).success(); 108 work_dir.run_jj(["new", "-m", "second"]).success(); 109 110 let output = work_dir.run_jj(["log", "-T", "author.timestamp()"]); 111 insta::assert_snapshot!(output, @r" 112 @ 2001-02-03 04:05:09.000 +07:00 113 ○ 2001-02-03 04:05:08.000 +07:00 114 ◆ 1970-01-01 00:00:00.000 +00:00 115 [EOF] 116 "); 117} 118 119#[test] 120fn test_log_author_timestamp_ago() { 121 let test_env = TestEnvironment::default(); 122 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 123 let work_dir = test_env.work_dir("repo"); 124 125 work_dir.run_jj(["describe", "-m", "first"]).success(); 126 work_dir.run_jj(["new", "-m", "second"]).success(); 127 128 let template = r#"author.timestamp().ago() ++ "\n""#; 129 let output = work_dir 130 .run_jj(&["log", "--no-graph", "-T", template]) 131 .success(); 132 let line_re = Regex::new(r"[0-9]+ years ago").unwrap(); 133 assert!( 134 output.stdout.raw().lines().all(|x| line_re.is_match(x)), 135 "expected every line to match regex" 136 ); 137} 138 139#[test] 140fn test_log_author_timestamp_utc() { 141 let test_env = TestEnvironment::default(); 142 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 143 let work_dir = test_env.work_dir("repo"); 144 145 let output = work_dir.run_jj(["log", "-T", "author.timestamp().utc()"]); 146 insta::assert_snapshot!(output, @r" 147 @ 2001-02-02 21:05:07.000 +00:00 148 ◆ 1970-01-01 00:00:00.000 +00:00 149 [EOF] 150 "); 151} 152 153#[cfg(unix)] 154#[test] 155fn test_log_author_timestamp_local() { 156 let mut test_env = TestEnvironment::default(); 157 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 158 159 test_env.add_env_var("TZ", "UTC-05:30"); 160 let work_dir = test_env.work_dir("repo"); 161 let output = work_dir.run_jj(["log", "-T", "author.timestamp().local()"]); 162 insta::assert_snapshot!(output, @r" 163 @ 2001-02-03 08:05:07.000 +11:00 164 ◆ 1970-01-01 11:00:00.000 +11:00 165 [EOF] 166 "); 167 test_env.add_env_var("TZ", "UTC+10:00"); 168 let work_dir = test_env.work_dir("repo"); 169 let output = work_dir.run_jj(["log", "-T", "author.timestamp().local()"]); 170 insta::assert_snapshot!(output, @r" 171 @ 2001-02-03 08:05:07.000 +11:00 172 ◆ 1970-01-01 11:00:00.000 +11:00 173 [EOF] 174 "); 175} 176 177#[test] 178fn test_log_author_timestamp_after_before() { 179 let test_env = TestEnvironment::default(); 180 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 181 let work_dir = test_env.work_dir("repo"); 182 183 work_dir.run_jj(["describe", "-m", "first"]).success(); 184 185 let template = r#" 186 separate(" ", 187 author.timestamp(), 188 ":", 189 if(author.timestamp().after("1969"), "(after 1969)", "(before 1969)"), 190 if(author.timestamp().before("1975"), "(before 1975)", "(after 1975)"), 191 if(author.timestamp().before("now"), "(before now)", "(after now)") 192 ) ++ "\n""#; 193 let output = work_dir.run_jj(["log", "--no-graph", "-T", template]); 194 insta::assert_snapshot!(output, @r" 195 2001-02-03 04:05:08.000 +07:00 : (after 1969) (after 1975) (before now) 196 1970-01-01 00:00:00.000 +00:00 : (after 1969) (before 1975) (before now) 197 [EOF] 198 "); 199 200 // Should display error with invalid date. 201 let template = r#"author.timestamp().after("invalid date")"#; 202 let output = work_dir.run_jj(["log", "-r@", "--no-graph", "-T", template]); 203 insta::assert_snapshot!(output, @r#" 204 ------- stderr ------- 205 Error: Failed to parse template: Invalid date pattern 206 Caused by: 207 1: --> 1:26 208 | 209 1 | author.timestamp().after("invalid date") 210 | ^------------^ 211 | 212 = Invalid date pattern 213 2: expected unsupported identifier as position 0..7 214 [EOF] 215 [exit status: 1] 216 "#); 217} 218 219#[test] 220fn test_mine_is_true_when_author_is_user() { 221 let test_env = TestEnvironment::default(); 222 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 223 let work_dir = test_env.work_dir("repo"); 224 work_dir 225 .run_jj([ 226 "--config=user.email=johndoe@example.com", 227 "--config=user.name=John Doe", 228 "new", 229 ]) 230 .success(); 231 232 let output = work_dir.run_jj([ 233 "log", 234 "-T", 235 r#"coalesce(if(mine, "mine"), author.email(), email_placeholder)"#, 236 ]); 237 insta::assert_snapshot!(output, @r" 238 @ johndoe@example.com 239 ○ mine 240 ◆ (no email set) 241 [EOF] 242 "); 243} 244 245#[test] 246fn test_log_default() { 247 let test_env = TestEnvironment::default(); 248 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 249 let work_dir = test_env.work_dir("repo"); 250 251 work_dir.write_file("file1", "foo\n"); 252 work_dir.run_jj(["describe", "-m", "add a file"]).success(); 253 work_dir.run_jj(["new", "-m", "description 1"]).success(); 254 work_dir 255 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 256 .success(); 257 258 // Test default log output format 259 let output = work_dir.run_jj(["log"]); 260 insta::assert_snapshot!(output, @r" 261 @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 my-bookmark bac9ff9e 262 │ (empty) description 1 263 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 aa2015d7 264 │ add a file 265 ◆ zzzzzzzz root() 00000000 266 [EOF] 267 "); 268 269 // Color 270 let output = work_dir.run_jj(["log", "--color=always"]); 271 insta::assert_snapshot!(output, @r" 272 @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 my-bookmark bac9ff9e 273 │ (empty) description 1 274 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 aa2015d7 275 │ add a file 276 ◆ zzzzzzzz root() 00000000 277 [EOF] 278 "); 279 280 // Color without graph 281 let output = work_dir.run_jj(["log", "--color=always", "--no-graph"]); 282 insta::assert_snapshot!(output, @r" 283 kkmpptxz test.user@example.com 2001-02-03 08:05:09 my-bookmark bac9ff9e 284 (empty) description 1 285 qpvuntsm test.user@example.com 2001-02-03 08:05:08 aa2015d7 286 add a file 287 zzzzzzzz root() 00000000 288 [EOF] 289 "); 290} 291 292#[test] 293fn test_log_default_without_working_copy() { 294 let test_env = TestEnvironment::default(); 295 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 296 let work_dir = test_env.work_dir("repo"); 297 298 work_dir.run_jj(["workspace", "forget"]).success(); 299 let output = work_dir.run_jj(["log"]); 300 insta::assert_snapshot!(output, @r" 301 ◆ zzzzzzzz root() 00000000 302 [EOF] 303 "); 304} 305 306#[test] 307fn test_log_builtin_templates() { 308 let test_env = TestEnvironment::default(); 309 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 310 let work_dir = test_env.work_dir("repo"); 311 // Render without graph to test line ending 312 let render = |template| work_dir.run_jj(["log", "-T", template, "--no-graph"]); 313 314 work_dir 315 .run_jj(["--config=user.email=''", "--config=user.name=''", "new"]) 316 .success(); 317 work_dir 318 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 319 .success(); 320 321 insta::assert_snapshot!(render(r#"builtin_log_oneline"#), @r" 322 rlvkpnrz (no email set) 2001-02-03 08:05:08 my-bookmark dc315397 (empty) (no description set) 323 qpvuntsm test.user 2001-02-03 08:05:07 230dd059 (empty) (no description set) 324 zzzzzzzz root() 00000000 325 [EOF] 326 "); 327 328 insta::assert_snapshot!(render(r#"builtin_log_compact"#), @r" 329 rlvkpnrz (no email set) 2001-02-03 08:05:08 my-bookmark dc315397 330 (empty) (no description set) 331 qpvuntsm test.user@example.com 2001-02-03 08:05:07 230dd059 332 (empty) (no description set) 333 zzzzzzzz root() 00000000 334 [EOF] 335 "); 336 337 insta::assert_snapshot!(render(r#"builtin_log_comfortable"#), @r" 338 rlvkpnrz (no email set) 2001-02-03 08:05:08 my-bookmark dc315397 339 (empty) (no description set) 340 341 qpvuntsm test.user@example.com 2001-02-03 08:05:07 230dd059 342 (empty) (no description set) 343 344 zzzzzzzz root() 00000000 345 346 [EOF] 347 "); 348 349 insta::assert_snapshot!(render(r#"builtin_log_detailed"#), @r" 350 Commit ID: dc31539712c7294d1d712cec63cef4504b94ca74 351 Change ID: rlvkpnrzqnoowoytxnquwvuryrwnrmlp 352 Bookmarks: my-bookmark 353 Author : (no name set) <(no email set)> (2001-02-03 08:05:08) 354 Committer: (no name set) <(no email set)> (2001-02-03 08:05:08) 355 356 (no description set) 357 358 Commit ID: 230dd059e1b059aefc0da06a2e5a7dbf22362f22 359 Change ID: qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu 360 Author : Test User <test.user@example.com> (2001-02-03 08:05:07) 361 Committer: Test User <test.user@example.com> (2001-02-03 08:05:07) 362 363 (no description set) 364 365 Commit ID: 0000000000000000000000000000000000000000 366 Change ID: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 367 Author : (no name set) <(no email set)> (1970-01-01 11:00:00) 368 Committer: (no name set) <(no email set)> (1970-01-01 11:00:00) 369 370 (no description set) 371 372 [EOF] 373 "); 374} 375 376#[test] 377fn test_log_builtin_templates_colored() { 378 let test_env = TestEnvironment::default(); 379 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 380 let work_dir = test_env.work_dir("repo"); 381 let render = |template| work_dir.run_jj(["--color=always", "log", "-T", template]); 382 383 work_dir 384 .run_jj(["--config=user.email=''", "--config=user.name=''", "new"]) 385 .success(); 386 work_dir 387 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 388 .success(); 389 390 insta::assert_snapshot!(render(r#"builtin_log_oneline"#), @r" 391 @ rlvkpnrz (no email set) 2001-02-03 08:05:08 my-bookmark dc315397 (empty) (no description set) 392 ○ qpvuntsm test.user 2001-02-03 08:05:07 230dd059 (empty) (no description set) 393 ◆ zzzzzzzz root() 00000000 394 [EOF] 395 "); 396 397 insta::assert_snapshot!(render(r#"builtin_log_compact"#), @r" 398 @ rlvkpnrz (no email set) 2001-02-03 08:05:08 my-bookmark dc315397 399 │ (empty) (no description set) 400 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:07 230dd059 401 │ (empty) (no description set) 402 ◆ zzzzzzzz root() 00000000 403 [EOF] 404 "); 405 406 insta::assert_snapshot!(render(r#"builtin_log_comfortable"#), @r" 407 @ rlvkpnrz (no email set) 2001-02-03 08:05:08 my-bookmark dc315397 408 │ (empty) (no description set) 409 410 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:07 230dd059 411 │ (empty) (no description set) 412 413 ◆ zzzzzzzz root() 00000000 414 415 [EOF] 416 "); 417 418 insta::assert_snapshot!(render(r#"builtin_log_detailed"#), @r" 419 @ Commit ID: dc31539712c7294d1d712cec63cef4504b94ca74 420 │ Change ID: rlvkpnrzqnoowoytxnquwvuryrwnrmlp 421 │ Bookmarks: my-bookmark 422 │ Author : (no name set) <(no email set)> (2001-02-03 08:05:08) 423 │ Committer: (no name set) <(no email set)> (2001-02-03 08:05:08) 424 425 │  (no description set) 426 427 ○ Commit ID: 230dd059e1b059aefc0da06a2e5a7dbf22362f22 428 │ Change ID: qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu 429 │ Author : Test User <test.user@example.com> (2001-02-03 08:05:07) 430 │ Committer: Test User <test.user@example.com> (2001-02-03 08:05:07) 431 432 │  (no description set) 433 434 ◆ Commit ID: 0000000000000000000000000000000000000000 435 Change ID: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 436 Author : (no name set) <(no email set)> (1970-01-01 11:00:00) 437 Committer: (no name set) <(no email set)> (1970-01-01 11:00:00) 438 439  (no description set) 440 441 [EOF] 442 "); 443} 444 445#[test] 446fn test_log_builtin_templates_colored_debug() { 447 let test_env = TestEnvironment::default(); 448 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 449 let work_dir = test_env.work_dir("repo"); 450 let render = |template| work_dir.run_jj(["--color=debug", "log", "-T", template]); 451 452 work_dir 453 .run_jj(["--config=user.email=''", "--config=user.name=''", "new"]) 454 .success(); 455 work_dir 456 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 457 .success(); 458 459 insta::assert_snapshot!(render(r#"builtin_log_oneline"#), @r" 460 <<node working_copy::@>> <<log working_copy change_id shortest prefix::r>><<log working_copy change_id shortest rest::lvkpnrz>><<log working_copy:: >><<log working_copy email placeholder::(no email set)>><<log working_copy:: >><<log working_copy committer timestamp local format::2001-02-03 08:05:08>><<log working_copy:: >><<log working_copy bookmarks name::my-bookmark>><<log working_copy:: >><<log working_copy commit_id shortest prefix::d>><<log working_copy commit_id shortest rest::c315397>><<log working_copy:: >><<log working_copy empty::(empty)>><<log working_copy:: >><<log working_copy empty description placeholder::(no description set)>><<log working_copy::>> 461 <<node::○>> <<log change_id shortest prefix::q>><<log change_id shortest rest::pvuntsm>><<log:: >><<log author email local::test.user>><<log:: >><<log committer timestamp local format::2001-02-03 08:05:07>><<log:: >><<log commit_id shortest prefix::2>><<log commit_id shortest rest::30dd059>><<log:: >><<log empty::(empty)>><<log:: >><<log empty description placeholder::(no description set)>><<log::>> 462 <<node immutable::◆>> <<log change_id shortest prefix::z>><<log change_id shortest rest::zzzzzzz>><<log:: >><<log root::root()>><<log:: >><<log commit_id shortest prefix::0>><<log commit_id shortest rest::0000000>><<log::>> 463 [EOF] 464 "); 465 466 insta::assert_snapshot!(render(r#"builtin_log_compact"#), @r" 467 <<node working_copy::@>> <<log working_copy change_id shortest prefix::r>><<log working_copy change_id shortest rest::lvkpnrz>><<log working_copy:: >><<log working_copy email placeholder::(no email set)>><<log working_copy:: >><<log working_copy committer timestamp local format::2001-02-03 08:05:08>><<log working_copy:: >><<log working_copy bookmarks name::my-bookmark>><<log working_copy:: >><<log working_copy commit_id shortest prefix::d>><<log working_copy commit_id shortest rest::c315397>><<log working_copy::>> 468 │ <<log working_copy empty::(empty)>><<log working_copy:: >><<log working_copy empty description placeholder::(no description set)>><<log working_copy::>> 469 <<node::○>> <<log change_id shortest prefix::q>><<log change_id shortest rest::pvuntsm>><<log:: >><<log author email local::test.user>><<log author email::@>><<log author email domain::example.com>><<log:: >><<log committer timestamp local format::2001-02-03 08:05:07>><<log:: >><<log commit_id shortest prefix::2>><<log commit_id shortest rest::30dd059>><<log::>> 470 │ <<log empty::(empty)>><<log:: >><<log empty description placeholder::(no description set)>><<log::>> 471 <<node immutable::◆>> <<log change_id shortest prefix::z>><<log change_id shortest rest::zzzzzzz>><<log:: >><<log root::root()>><<log:: >><<log commit_id shortest prefix::0>><<log commit_id shortest rest::0000000>><<log::>> 472 [EOF] 473 "); 474 475 insta::assert_snapshot!(render(r#"builtin_log_comfortable"#), @r" 476 <<node working_copy::@>> <<log working_copy change_id shortest prefix::r>><<log working_copy change_id shortest rest::lvkpnrz>><<log working_copy:: >><<log working_copy email placeholder::(no email set)>><<log working_copy:: >><<log working_copy committer timestamp local format::2001-02-03 08:05:08>><<log working_copy:: >><<log working_copy bookmarks name::my-bookmark>><<log working_copy:: >><<log working_copy commit_id shortest prefix::d>><<log working_copy commit_id shortest rest::c315397>><<log working_copy::>> 477 │ <<log working_copy empty::(empty)>><<log working_copy:: >><<log working_copy empty description placeholder::(no description set)>><<log working_copy::>> 478 │ <<log::>> 479 <<node::○>> <<log change_id shortest prefix::q>><<log change_id shortest rest::pvuntsm>><<log:: >><<log author email local::test.user>><<log author email::@>><<log author email domain::example.com>><<log:: >><<log committer timestamp local format::2001-02-03 08:05:07>><<log:: >><<log commit_id shortest prefix::2>><<log commit_id shortest rest::30dd059>><<log::>> 480 │ <<log empty::(empty)>><<log:: >><<log empty description placeholder::(no description set)>><<log::>> 481 │ <<log::>> 482 <<node immutable::◆>> <<log change_id shortest prefix::z>><<log change_id shortest rest::zzzzzzz>><<log:: >><<log root::root()>><<log:: >><<log commit_id shortest prefix::0>><<log commit_id shortest rest::0000000>><<log::>> 483 <<log::>> 484 [EOF] 485 "); 486 487 insta::assert_snapshot!(render(r#"builtin_log_detailed"#), @r" 488 <<node working_copy::@>> <<log::Commit ID: >><<log commit_id::dc31539712c7294d1d712cec63cef4504b94ca74>><<log::>> 489 │ <<log::Change ID: >><<log change_id::rlvkpnrzqnoowoytxnquwvuryrwnrmlp>><<log::>> 490 │ <<log::Bookmarks: >><<log local_bookmarks name::my-bookmark>><<log::>> 491 │ <<log::Author : >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::> (>><<log author timestamp local format::2001-02-03 08:05:08>><<log::)>> 492 │ <<log::Committer: >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::> (>><<log committer timestamp local format::2001-02-03 08:05:08>><<log::)>> 493 │ <<log::>> 494 │ <<log empty description placeholder:: (no description set)>><<log::>> 495 │ <<log::>> 496 <<node::○>> <<log::Commit ID: >><<log commit_id::230dd059e1b059aefc0da06a2e5a7dbf22362f22>><<log::>> 497 │ <<log::Change ID: >><<log change_id::qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu>><<log::>> 498 │ <<log::Author : >><<log author name::Test User>><<log:: <>><<log author email local::test.user>><<log author email::@>><<log author email domain::example.com>><<log::> (>><<log author timestamp local format::2001-02-03 08:05:07>><<log::)>> 499 │ <<log::Committer: >><<log committer name::Test User>><<log:: <>><<log committer email local::test.user>><<log committer email::@>><<log committer email domain::example.com>><<log::> (>><<log committer timestamp local format::2001-02-03 08:05:07>><<log::)>> 500 │ <<log::>> 501 │ <<log empty description placeholder:: (no description set)>><<log::>> 502 │ <<log::>> 503 <<node immutable::◆>> <<log::Commit ID: >><<log commit_id::0000000000000000000000000000000000000000>><<log::>> 504 <<log::Change ID: >><<log change_id::zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz>><<log::>> 505 <<log::Author : >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::> (>><<log author timestamp local format::1970-01-01 11:00:00>><<log::)>> 506 <<log::Committer: >><<log name placeholder::(no name set)>><<log:: <>><<log email placeholder::(no email set)>><<log::> (>><<log committer timestamp local format::1970-01-01 11:00:00>><<log::)>> 507 <<log::>> 508 <<log empty description placeholder:: (no description set)>><<log::>> 509 <<log::>> 510 [EOF] 511 "); 512} 513 514#[test] 515fn test_log_evolog_divergence() { 516 let test_env = TestEnvironment::default(); 517 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 518 let work_dir = test_env.work_dir("repo"); 519 520 work_dir.write_file("file", "foo\n"); 521 work_dir 522 .run_jj(["describe", "-m", "description 1"]) 523 .success(); 524 // No divergence 525 let output = work_dir.run_jj(["log"]); 526 insta::assert_snapshot!(output, @r" 527 @ qpvuntsm test.user@example.com 2001-02-03 08:05:08 ff309c29 528 │ description 1 529 ◆ zzzzzzzz root() 00000000 530 [EOF] 531 "); 532 533 // Create divergence 534 work_dir 535 .run_jj(["describe", "-m", "description 2", "--at-operation", "@-"]) 536 .success(); 537 let output = work_dir.run_jj(["log"]); 538 insta::assert_snapshot!(output, @r" 539 @ qpvuntsm?? test.user@example.com 2001-02-03 08:05:08 ff309c29 540 │ description 1 541 │ ○ qpvuntsm?? test.user@example.com 2001-02-03 08:05:10 6ba70e00 542 ├─╯ description 2 543 ◆ zzzzzzzz root() 00000000 544 [EOF] 545 ------- stderr ------- 546 Concurrent modification detected, resolving automatically. 547 [EOF] 548 "); 549 550 // Color 551 let output = work_dir.run_jj(["log", "--color=always"]); 552 insta::assert_snapshot!(output, @r" 553 @ qpvuntsm?? test.user@example.com 2001-02-03 08:05:08 ff309c29 554 │ description 1 555 │ ○ qpvuntsm?? test.user@example.com 2001-02-03 08:05:10 6ba70e00 556 ├─╯ description 2 557 ◆ zzzzzzzz root() 00000000 558 [EOF] 559 "); 560 561 // Evolog and hidden divergent 562 let output = work_dir.run_jj(["evolog"]); 563 insta::assert_snapshot!(output, @r" 564 @ qpvuntsm?? test.user@example.com 2001-02-03 08:05:08 ff309c29 565 │ description 1 566 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 485d52a9 567 │ (no description set) 568 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:07 230dd059 569 (empty) (no description set) 570 [EOF] 571 "); 572 573 // Colored evolog 574 let output = work_dir.run_jj(["evolog", "--color=always"]); 575 insta::assert_snapshot!(output, @r" 576 @ qpvuntsm?? test.user@example.com 2001-02-03 08:05:08 ff309c29 577 │ description 1 578 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 485d52a9 579 │ (no description set) 580 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:07 230dd059 581 (empty) (no description set) 582 [EOF] 583 "); 584} 585 586#[test] 587fn test_log_bookmarks() { 588 let test_env = TestEnvironment::default(); 589 test_env.add_config("git.auto-local-bookmark = true"); 590 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 591 592 test_env.run_jj_in(".", ["git", "init", "origin"]).success(); 593 let origin_dir = test_env.work_dir("origin"); 594 let origin_git_repo_path = origin_dir 595 .root() 596 .join(".jj") 597 .join("repo") 598 .join("store") 599 .join("git"); 600 601 // Created some bookmarks on the remote 602 origin_dir 603 .run_jj(["describe", "-m=description 1"]) 604 .success(); 605 origin_dir 606 .run_jj(["bookmark", "create", "-r@", "bookmark1"]) 607 .success(); 608 origin_dir 609 .run_jj(["new", "root()", "-m=description 2"]) 610 .success(); 611 origin_dir 612 .run_jj(["bookmark", "create", "-r@", "bookmark2", "unchanged"]) 613 .success(); 614 origin_dir 615 .run_jj(["new", "root()", "-m=description 3"]) 616 .success(); 617 origin_dir 618 .run_jj(["bookmark", "create", "-r@", "bookmark3"]) 619 .success(); 620 origin_dir.run_jj(["git", "export"]).success(); 621 test_env 622 .run_jj_in( 623 ".", 624 [ 625 "git", 626 "clone", 627 origin_git_repo_path.to_str().unwrap(), 628 "local", 629 ], 630 ) 631 .success(); 632 let work_dir = test_env.work_dir("local"); 633 634 // Rewrite bookmark1, move bookmark2 forward, create conflict in bookmark3, add 635 // new-bookmark 636 work_dir 637 .run_jj(["describe", "bookmark1", "-m", "modified bookmark1 commit"]) 638 .success(); 639 work_dir.run_jj(["new", "bookmark2"]).success(); 640 work_dir 641 .run_jj(["bookmark", "set", "bookmark2", "--to=@"]) 642 .success(); 643 work_dir 644 .run_jj(["bookmark", "create", "-r@", "new-bookmark"]) 645 .success(); 646 work_dir 647 .run_jj(["describe", "bookmark3", "-m=local"]) 648 .success(); 649 origin_dir 650 .run_jj(["describe", "bookmark3", "-m=origin"]) 651 .success(); 652 origin_dir.run_jj(["git", "export"]).success(); 653 work_dir.run_jj(["git", "fetch"]).success(); 654 655 let template = r#"commit_id.short() ++ " " ++ if(bookmarks, bookmarks, "(no bookmarks)")"#; 656 let output = work_dir.run_jj(["log", "-T", template]); 657 insta::assert_snapshot!(output, @r" 658 @ a5b4d15489cc bookmark2* new-bookmark 659 ○ 8476341eb395 bookmark2@origin unchanged 660 │ ○ fed794e2ba44 bookmark3?? bookmark3@origin 661 ├─╯ 662 │ ○ b1bb3766d584 bookmark3?? 663 ├─╯ 664 │ ○ 4a7e4246fc4d bookmark1* 665 ├─╯ 666 ◆ 000000000000 (no bookmarks) 667 [EOF] 668 "); 669 670 let template = r#"bookmarks.map(|b| separate("/", b.remote(), b.name())).join(", ")"#; 671 let output = work_dir.run_jj(["log", "-T", template]); 672 insta::assert_snapshot!(output, @r" 673 @ bookmark2, new-bookmark 674 ○ origin/bookmark2, unchanged 675 │ ○ bookmark3, origin/bookmark3 676 ├─╯ 677 │ ○ bookmark3 678 ├─╯ 679 │ ○ bookmark1 680 ├─╯ 681 682 [EOF] 683 "); 684 685 let template = r#"separate(" ", "L:", local_bookmarks, "R:", remote_bookmarks)"#; 686 let output = work_dir.run_jj(["log", "-T", template]); 687 insta::assert_snapshot!(output, @r" 688 @ L: bookmark2* new-bookmark R: 689 ○ L: unchanged R: bookmark2@origin unchanged@origin 690 │ ○ L: bookmark3?? R: bookmark3@origin 691 ├─╯ 692 │ ○ L: bookmark3?? R: 693 ├─╯ 694 │ ○ L: bookmark1* R: 695 ├─╯ 696 ◆ L: R: 697 [EOF] 698 "); 699 700 let template = r#" 701 remote_bookmarks.map(|ref| concat( 702 ref, 703 if(ref.tracked(), 704 "(+" ++ ref.tracking_ahead_count().lower() 705 ++ "/-" ++ ref.tracking_behind_count().lower() ++ ")"), 706 )) 707 "#; 708 let output = work_dir.run_jj(["log", "-r::remote_bookmarks()", "-T", template]); 709 insta::assert_snapshot!(output, @r" 710 ○ bookmark3@origin(+0/-1) 711 │ ○ bookmark2@origin(+0/-1) unchanged@origin(+0/-0) 712 ├─╯ 713 │ ○ bookmark1@origin(+1/-1) 714 ├─╯ 715 716 [EOF] 717 "); 718} 719 720#[test] 721fn test_log_git_head() { 722 let test_env = TestEnvironment::default(); 723 let work_dir = test_env.work_dir("repo"); 724 git::init(work_dir.root()); 725 work_dir.run_jj(["git", "init", "--git-repo=."]).success(); 726 727 work_dir.run_jj(["new", "-m=initial"]).success(); 728 work_dir.write_file("file", "foo\n"); 729 730 let output = work_dir.run_jj(["log", "-T", "git_head"]); 731 insta::assert_snapshot!(output, @r" 732 @ false 733 ○ true 734 ◆ false 735 [EOF] 736 "); 737 738 let output = work_dir.run_jj(["log", "--color=always"]); 739 insta::assert_snapshot!(output, @r" 740 @ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 50aaf475 741 │ initial 742 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:07 git_head() 230dd059 743 │ (empty) (no description set) 744 ◆ zzzzzzzz root() 00000000 745 [EOF] 746 "); 747} 748 749#[test] 750fn test_log_commit_id_normal_hex() { 751 let test_env = TestEnvironment::default(); 752 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 753 let work_dir = test_env.work_dir("repo"); 754 755 work_dir.run_jj(["new", "-m", "first"]).success(); 756 work_dir.run_jj(["new", "-m", "second"]).success(); 757 758 let output = work_dir.run_jj([ 759 "log", 760 "-T", 761 r#"commit_id ++ ": " ++ commit_id.normal_hex()"#, 762 ]); 763 insta::assert_snapshot!(output, @r" 764 @ 6572f22267c6f0f2bf7b8a37969ee5a7d54b8aae: 6572f22267c6f0f2bf7b8a37969ee5a7d54b8aae 765 ○ 222fa9f0b41347630a1371203b8aad3897d34e5f: 222fa9f0b41347630a1371203b8aad3897d34e5f 766 ○ 230dd059e1b059aefc0da06a2e5a7dbf22362f22: 230dd059e1b059aefc0da06a2e5a7dbf22362f22 767 ◆ 0000000000000000000000000000000000000000: 0000000000000000000000000000000000000000 768 [EOF] 769 "); 770} 771 772#[test] 773fn test_log_change_id_normal_hex() { 774 let test_env = TestEnvironment::default(); 775 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 776 let work_dir = test_env.work_dir("repo"); 777 778 work_dir.run_jj(["new", "-m", "first"]).success(); 779 work_dir.run_jj(["new", "-m", "second"]).success(); 780 781 let output = work_dir.run_jj([ 782 "log", 783 "-T", 784 r#"change_id ++ ": " ++ change_id.normal_hex()"#, 785 ]); 786 insta::assert_snapshot!(output, @r" 787 @ kkmpptxzrspxrzommnulwmwkkqwworpl: ffdaa62087a280bddc5e3d3ff933b8ae 788 ○ rlvkpnrzqnoowoytxnquwvuryrwnrmlp: 8e4fac809cbb3b162c953458183c8dea 789 ○ qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu: 9a45c67d3e96a7e5007c110ede34dec5 790 ◆ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz: 00000000000000000000000000000000 791 [EOF] 792 "); 793} 794 795#[test] 796fn test_log_customize_short_id() { 797 let test_env = TestEnvironment::default(); 798 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 799 let work_dir = test_env.work_dir("repo"); 800 801 work_dir.run_jj(["describe", "-m", "first"]).success(); 802 803 // Customize both the commit and the change id 804 let decl = "template-aliases.'format_short_id(id)'"; 805 let output = work_dir.run_jj([ 806 "log", 807 "--config", 808 &format!(r#"{decl}='id.shortest(5).prefix().upper() ++ "_" ++ id.shortest(5).rest()'"#), 809 ]); 810 insta::assert_snapshot!(output, @r" 811 @ Q_pvun test.user@example.com 2001-02-03 08:05:08 F_a156 812 │ (empty) first 813 ◆ Z_zzzz root() 0_0000 814 [EOF] 815 "); 816 817 // Customize only the change id 818 let output = work_dir.run_jj([ 819 "log", 820 "--config=template-aliases.'format_short_change_id(id)'='format_short_id(id).upper()'", 821 ]); 822 insta::assert_snapshot!(output, @r" 823 @ QPVUNTSM test.user@example.com 2001-02-03 08:05:08 fa15625b 824 │ (empty) first 825 ◆ ZZZZZZZZ root() 00000000 826 [EOF] 827 "); 828} 829 830#[test] 831fn test_log_immutable() { 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 work_dir.run_jj(["new", "-mA", "root()"]).success(); 836 work_dir.run_jj(["new", "-mB"]).success(); 837 work_dir 838 .run_jj(["bookmark", "create", "-r@", "main"]) 839 .success(); 840 work_dir.run_jj(["new", "-mC"]).success(); 841 work_dir.run_jj(["new", "-mD", "root()"]).success(); 842 843 let template = r#" 844 separate(" ", 845 description.first_line(), 846 bookmarks, 847 if(immutable, "[immutable]"), 848 ) ++ "\n" 849 "#; 850 851 test_env.add_config("revset-aliases.'immutable_heads()' = 'main'"); 852 let output = work_dir.run_jj(["log", "-r::", "-T", template]); 853 insta::assert_snapshot!(output, @r" 854 @ D 855 │ ○ C 856 │ ◆ B main [immutable] 857 │ ◆ A [immutable] 858 ├─╯ 859 ◆ [immutable] 860 [EOF] 861 "); 862 863 // Suppress error that could be detected earlier 864 test_env.add_config("revsets.short-prefixes = ''"); 865 866 test_env.add_config("revset-aliases.'immutable_heads()' = 'unknown_fn()'"); 867 let output = work_dir.run_jj(["log", "-r::", "-T", template]); 868 insta::assert_snapshot!(output, @r" 869 ------- stderr ------- 870 Config error: Invalid `revset-aliases.immutable_heads()` 871 Caused by: --> 1:1 872 | 873 1 | unknown_fn() 874 | ^--------^ 875 | 876 = Function `unknown_fn` doesn't exist 877 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 878 [EOF] 879 [exit status: 1] 880 "); 881 882 test_env.add_config("revset-aliases.'immutable_heads()' = 'unknown_symbol'"); 883 let output = work_dir.run_jj(["log", "-r::", "-T", template]); 884 insta::assert_snapshot!(output, @r#" 885 ------- stderr ------- 886 Error: Failed to parse template: Failed to evaluate revset 887 Caused by: 888 1: --> 5:10 889 | 890 5 | if(immutable, "[immutable]"), 891 | ^-------^ 892 | 893 = Failed to evaluate revset 894 2: Revision `unknown_symbol` doesn't exist 895 [EOF] 896 [exit status: 1] 897 "#); 898} 899 900#[test] 901fn test_log_contained_in() { 902 let test_env = TestEnvironment::default(); 903 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 904 let work_dir = test_env.work_dir("repo"); 905 work_dir.run_jj(["new", "-mA", "root()"]).success(); 906 work_dir.run_jj(["new", "-mB"]).success(); 907 work_dir 908 .run_jj(["bookmark", "create", "-r@", "main"]) 909 .success(); 910 work_dir.run_jj(["new", "-mC"]).success(); 911 work_dir.run_jj(["new", "-mD", "root()"]).success(); 912 913 let template_for_revset = |revset: &str| { 914 format!( 915 r#" 916 separate(" ", 917 description.first_line(), 918 bookmarks, 919 if(self.contained_in("{revset}"), "[contained_in]"), 920 ) ++ "\n" 921 "# 922 ) 923 }; 924 925 let output = work_dir.run_jj([ 926 "log", 927 "-r::", 928 "-T", 929 &template_for_revset(r#"description(A)::"#), 930 ]); 931 insta::assert_snapshot!(output, @r" 932 @ D 933 │ ○ C [contained_in] 934 │ ○ B main [contained_in] 935 │ ○ A [contained_in] 936 ├─╯ 937 938 [EOF] 939 "); 940 941 let output = work_dir.run_jj([ 942 "log", 943 "-r::", 944 "-T", 945 &template_for_revset(r#"visible_heads()"#), 946 ]); 947 insta::assert_snapshot!(output, @r" 948 @ D [contained_in] 949 │ ○ C [contained_in] 950 │ ○ B main 951 │ ○ A 952 ├─╯ 953 954 [EOF] 955 "); 956 957 // Suppress error that could be detected earlier 958 let output = work_dir.run_jj(["log", "-r::", "-T", &template_for_revset("unknown_fn()")]); 959 insta::assert_snapshot!(output, @r#" 960 ------- stderr ------- 961 Error: Failed to parse template: In revset expression 962 Caused by: 963 1: --> 5:28 964 | 965 5 | if(self.contained_in("unknown_fn()"), "[contained_in]"), 966 | ^------------^ 967 | 968 = In revset expression 969 2: --> 1:1 970 | 971 1 | unknown_fn() 972 | ^--------^ 973 | 974 = Function `unknown_fn` doesn't exist 975 [EOF] 976 [exit status: 1] 977 "#); 978 979 let output = work_dir.run_jj(["log", "-r::", "-T", &template_for_revset("author(x:'y')")]); 980 insta::assert_snapshot!(output, @r#" 981 ------- stderr ------- 982 Error: Failed to parse template: In revset expression 983 Caused by: 984 1: --> 5:28 985 | 986 5 | if(self.contained_in("author(x:'y')"), "[contained_in]"), 987 | ^-------------^ 988 | 989 = In revset expression 990 2: --> 1:8 991 | 992 1 | author(x:'y') 993 | ^---^ 994 | 995 = Invalid string pattern 996 3: Invalid string pattern kind `x:` 997 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 998 [EOF] 999 [exit status: 1] 1000 "#); 1001 1002 let output = work_dir.run_jj(["log", "-r::", "-T", &template_for_revset("maine")]); 1003 insta::assert_snapshot!(output, @r#" 1004 ------- stderr ------- 1005 Error: Failed to parse template: Failed to evaluate revset 1006 Caused by: 1007 1: --> 5:28 1008 | 1009 5 | if(self.contained_in("maine"), "[contained_in]"), 1010 | ^-----^ 1011 | 1012 = Failed to evaluate revset 1013 2: Revision `maine` doesn't exist 1014 Hint: Did you mean `main`? 1015 [EOF] 1016 [exit status: 1] 1017 "#); 1018} 1019 1020#[test] 1021fn test_short_prefix_in_transaction() { 1022 let test_env = TestEnvironment::default(); 1023 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1024 let work_dir = test_env.work_dir("repo"); 1025 1026 test_env.add_config(r#" 1027 [revsets] 1028 log = '::description(test)' 1029 1030 [templates] 1031 log = 'summary ++ "\n"' 1032 commit_summary = 'summary' 1033 1034 [template-aliases] 1035 'format_id(id)' = 'id.shortest(12).prefix() ++ "[" ++ id.shortest(12).rest() ++ "]"' 1036 'summary' = 'separate(" ", format_id(change_id), format_id(commit_id), description.first_line())' 1037 "#); 1038 1039 work_dir.write_file("file", "original file\n"); 1040 work_dir.run_jj(["describe", "-m", "initial"]).success(); 1041 1042 // Create a chain of 5 commits 1043 for i in 0..5 { 1044 work_dir 1045 .run_jj(["new", "-m", &format!("commit{i}")]) 1046 .success(); 1047 work_dir.write_file("file", format!("file {i}\n")); 1048 } 1049 // Create 2^4 duplicates of the chain 1050 for _ in 0..4 { 1051 work_dir 1052 .run_jj(["duplicate", "description(commit)"]) 1053 .success(); 1054 } 1055 1056 // Short prefix should be used for commit summary inside the transaction 1057 let parent_id = "58731d"; // Force id lookup to build index before mutation. 1058 // If the cached index wasn't invalidated, the 1059 // newly created commit wouldn't be found in it. 1060 let output = work_dir.run_jj(["new", parent_id, "--no-edit", "-m", "test"]); 1061 insta::assert_snapshot!(output, @r" 1062 ------- stderr ------- 1063 Created new commit km[kuslswpqwq] 7[4ac55dd119b] test 1064 [EOF] 1065 "); 1066 1067 // Should match log's short prefixes 1068 let output = work_dir.run_jj(["log", "--no-graph"]); 1069 insta::assert_snapshot!(output, @r" 1070 km[kuslswpqwq] 7[4ac55dd119b] test 1071 y[qosqzytrlsw] 5[8731db5875e] commit4 1072 r[oyxmykxtrkr] 9[95cc897bca7] commit3 1073 m[zvwutvlkqwt] 3[74534c54448] commit2 1074 zs[uskulnrvyr] d[e304c281bed] commit1 1075 kk[mpptxzrspx] 05[2755155952] commit0 1076 q[pvuntsmwlqt] e[0e22b9fae75] initial 1077 zz[zzzzzzzzzz] 00[0000000000] 1078 [EOF] 1079 "); 1080 1081 test_env.add_config(r#"revsets.short-prefixes = """#); 1082 1083 let output = work_dir.run_jj(["log", "--no-graph"]); 1084 insta::assert_snapshot!(output, @r" 1085 kmk[uslswpqwq] 74ac[55dd119b] test 1086 yq[osqzytrlsw] 587[31db5875e] commit4 1087 ro[yxmykxtrkr] 99[5cc897bca7] commit3 1088 mz[vwutvlkqwt] 374[534c54448] commit2 1089 zs[uskulnrvyr] de[304c281bed] commit1 1090 kk[mpptxzrspx] 052[755155952] commit0 1091 qp[vuntsmwlqt] e0[e22b9fae75] initial 1092 zz[zzzzzzzzzz] 00[0000000000] 1093 [EOF] 1094 "); 1095} 1096 1097#[test] 1098fn test_log_diff_predefined_formats() { 1099 let test_env = TestEnvironment::default(); 1100 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1101 let work_dir = test_env.work_dir("repo"); 1102 1103 work_dir.write_file("file1", "a\nb\n"); 1104 work_dir.write_file("file2", "a\n"); 1105 work_dir.write_file("rename-source", "rename"); 1106 work_dir.run_jj(["new"]).success(); 1107 work_dir.write_file("file1", "a\nb\nc\n"); 1108 work_dir.write_file("file2", "b\nc\n"); 1109 std::fs::rename( 1110 work_dir.root().join("rename-source"), 1111 work_dir.root().join("rename-target"), 1112 ) 1113 .unwrap(); 1114 1115 let template = r#" 1116 concat( 1117 "=== color_words ===\n", 1118 diff.color_words(), 1119 "=== git ===\n", 1120 diff.git(), 1121 "=== stat ===\n", 1122 diff.stat(80), 1123 "=== summary ===\n", 1124 diff.summary(), 1125 ) 1126 "#; 1127 1128 // color, without paths 1129 let output = work_dir.run_jj(["log", "--no-graph", "--color=always", "-r@", "-T", template]); 1130 insta::assert_snapshot!(output, @r" 1131 === color_words === 1132 Modified regular file file1: 1133  1  1: a 1134  2  2: b 1135  3: c 1136 Modified regular file file2: 1137  1  1: ab 1138  2: c 1139 Modified regular file rename-target (rename-source => rename-target): 1140 === git === 1141 diff --git a/file1 b/file1 1142 index 422c2b7ab3..de980441c3 100644 1143 --- a/file1 1144 +++ b/file1 1145 @@ -1,2 +1,3 @@ 1146 a 1147 b 1148 +c 1149 diff --git a/file2 b/file2 1150 index 7898192261..9ddeb5c484 100644 1151 --- a/file2 1152 +++ b/file2 1153 @@ -1,1 +1,2 @@ 1154 -a 1155 +b 1156 +c 1157 diff --git a/rename-source b/rename-target 1158 rename from rename-source 1159 rename to rename-target 1160 === stat === 1161 file1 | 1 + 1162 file2 | 3 ++- 1163 {rename-source => rename-target} | 0 1164 3 files changed, 3 insertions(+), 1 deletion(-) 1165 === summary === 1166 M file1 1167 M file2 1168 R {rename-source => rename-target} 1169 [EOF] 1170 "); 1171 1172 // color labels 1173 let output = work_dir.run_jj(["log", "--no-graph", "--color=debug", "-r@", "-T", template]); 1174 insta::assert_snapshot!(output, @r" 1175 <<log::=== color_words ===>> 1176 <<log diff color_words header::Modified regular file file1:>> 1177 <<log diff color_words removed line_number:: 1>><<log diff color_words:: >><<log diff color_words added line_number:: 1>><<log diff color_words::: a>> 1178 <<log diff color_words removed line_number:: 2>><<log diff color_words:: >><<log diff color_words added line_number:: 2>><<log diff color_words::: b>> 1179 <<log diff color_words:: >><<log diff color_words added line_number:: 3>><<log diff color_words::: >><<log diff color_words added token::c>> 1180 <<log diff color_words header::Modified regular file file2:>> 1181 <<log diff color_words removed line_number:: 1>><<log diff color_words:: >><<log diff color_words added line_number:: 1>><<log diff color_words::: >><<log diff color_words removed token::a>><<log diff color_words added token::b>><<log diff color_words::>> 1182 <<log diff color_words:: >><<log diff color_words added line_number:: 2>><<log diff color_words::: >><<log diff color_words added token::c>> 1183 <<log diff color_words header::Modified regular file rename-target (rename-source => rename-target):>> 1184 <<log::=== git ===>> 1185 <<log diff git file_header::diff --git a/file1 b/file1>> 1186 <<log diff git file_header::index 422c2b7ab3..de980441c3 100644>> 1187 <<log diff git file_header::--- a/file1>> 1188 <<log diff git file_header::+++ b/file1>> 1189 <<log diff git hunk_header::@@ -1,2 +1,3 @@>> 1190 <<log diff git context:: a>> 1191 <<log diff git context:: b>> 1192 <<log diff git added::+>><<log diff git added token::c>> 1193 <<log diff git file_header::diff --git a/file2 b/file2>> 1194 <<log diff git file_header::index 7898192261..9ddeb5c484 100644>> 1195 <<log diff git file_header::--- a/file2>> 1196 <<log diff git file_header::+++ b/file2>> 1197 <<log diff git hunk_header::@@ -1,1 +1,2 @@>> 1198 <<log diff git removed::->><<log diff git removed token::a>><<log diff git removed::>> 1199 <<log diff git added::+>><<log diff git added token::b>><<log diff git added::>> 1200 <<log diff git added::+>><<log diff git added token::c>> 1201 <<log diff git file_header::diff --git a/rename-source b/rename-target>> 1202 <<log diff git file_header::rename from rename-source>> 1203 <<log diff git file_header::rename to rename-target>> 1204 <<log::=== stat ===>> 1205 <<log diff stat::file1 | 1 >><<log diff stat added::+>><<log diff stat removed::>> 1206 <<log diff stat::file2 | 3 >><<log diff stat added::++>><<log diff stat removed::->> 1207 <<log diff stat::{rename-source => rename-target} | 0>><<log diff stat removed::>> 1208 <<log diff stat stat-summary::3 files changed, 3 insertions(+), 1 deletion(-)>> 1209 <<log::=== summary ===>> 1210 <<log diff summary modified::M file1>> 1211 <<log diff summary modified::M file2>> 1212 <<log diff summary renamed::R {rename-source => rename-target}>> 1213 [EOF] 1214 "); 1215 1216 // cwd != workspace root 1217 let output = test_env.run_jj_in(".", ["log", "-Rrepo", "--no-graph", "-r@", "-T", template]); 1218 insta::assert_snapshot!(output.normalize_backslash(), @r" 1219 === color_words === 1220 Modified regular file repo/file1: 1221 1 1: a 1222 2 2: b 1223 3: c 1224 Modified regular file repo/file2: 1225 1 1: ab 1226 2: c 1227 Modified regular file repo/rename-target (repo/rename-source => repo/rename-target): 1228 === git === 1229 diff --git a/file1 b/file1 1230 index 422c2b7ab3..de980441c3 100644 1231 --- a/file1 1232 +++ b/file1 1233 @@ -1,2 +1,3 @@ 1234 a 1235 b 1236 +c 1237 diff --git a/file2 b/file2 1238 index 7898192261..9ddeb5c484 100644 1239 --- a/file2 1240 +++ b/file2 1241 @@ -1,1 +1,2 @@ 1242 -a 1243 +b 1244 +c 1245 diff --git a/rename-source b/rename-target 1246 rename from rename-source 1247 rename to rename-target 1248 === stat === 1249 repo/file1 | 1 + 1250 repo/file2 | 3 ++- 1251 repo/{rename-source => rename-target} | 0 1252 3 files changed, 3 insertions(+), 1 deletion(-) 1253 === summary === 1254 M repo/file1 1255 M repo/file2 1256 R repo/{rename-source => rename-target} 1257 [EOF] 1258 "); 1259 1260 // with non-default config 1261 std::fs::write( 1262 test_env.env_root().join("config-good.toml"), 1263 indoc! {" 1264 diff.color-words.context = 0 1265 diff.color-words.max-inline-alternation = 0 1266 diff.git.context = 1 1267 "}, 1268 ) 1269 .unwrap(); 1270 let output = work_dir.run_jj([ 1271 "log", 1272 "--config-file=../config-good.toml", 1273 "--no-graph", 1274 "-r@", 1275 "-T", 1276 template, 1277 ]); 1278 insta::assert_snapshot!(output, @r" 1279 === color_words === 1280 Modified regular file file1: 1281 ... 1282 3: c 1283 Modified regular file file2: 1284 1 : a 1285 1: b 1286 2: c 1287 Modified regular file rename-target (rename-source => rename-target): 1288 === git === 1289 diff --git a/file1 b/file1 1290 index 422c2b7ab3..de980441c3 100644 1291 --- a/file1 1292 +++ b/file1 1293 @@ -2,1 +2,2 @@ 1294 b 1295 +c 1296 diff --git a/file2 b/file2 1297 index 7898192261..9ddeb5c484 100644 1298 --- a/file2 1299 +++ b/file2 1300 @@ -1,1 +1,2 @@ 1301 -a 1302 +b 1303 +c 1304 diff --git a/rename-source b/rename-target 1305 rename from rename-source 1306 rename to rename-target 1307 === stat === 1308 file1 | 1 + 1309 file2 | 3 ++- 1310 {rename-source => rename-target} | 0 1311 3 files changed, 3 insertions(+), 1 deletion(-) 1312 === summary === 1313 M file1 1314 M file2 1315 R {rename-source => rename-target} 1316 [EOF] 1317 "); 1318 1319 // bad config 1320 std::fs::write( 1321 test_env.env_root().join("config-bad.toml"), 1322 "diff.git.context = 'not an integer'\n", 1323 ) 1324 .unwrap(); 1325 let output = work_dir.run_jj([ 1326 "log", 1327 "--config-file=../config-bad.toml", 1328 "-Tself.diff().git()", 1329 ]); 1330 insta::assert_snapshot!(output, @r#" 1331 ------- stderr ------- 1332 Error: Failed to parse template: Failed to load diff settings 1333 Caused by: 1334 1: --> 1:13 1335 | 1336 1 | self.diff().git() 1337 | ^-^ 1338 | 1339 = Failed to load diff settings 1340 2: Invalid type or value for diff.git.context 1341 3: invalid type: string "not an integer", expected usize 1342 1343 Hint: Check the config file: ../config-bad.toml 1344 [EOF] 1345 [exit status: 1] 1346 "#); 1347 1348 // color_words() with parameters 1349 let template = "self.diff('file1').color_words(0)"; 1350 let output = work_dir.run_jj(["log", "--no-graph", "-r@", "-T", template]); 1351 insta::assert_snapshot!(output, @r" 1352 Modified regular file file1: 1353 ... 1354 3: c 1355 [EOF] 1356 "); 1357 1358 // git() with parameters 1359 let template = "self.diff('file1').git(1)"; 1360 let output = work_dir.run_jj(["log", "--no-graph", "-r@", "-T", template]); 1361 insta::assert_snapshot!(output, @r" 1362 diff --git a/file1 b/file1 1363 index 422c2b7ab3..de980441c3 100644 1364 --- a/file1 1365 +++ b/file1 1366 @@ -2,1 +2,2 @@ 1367 b 1368 +c 1369 [EOF] 1370 "); 1371 1372 // custom template with files() 1373 let template = indoc! {r#" 1374 concat( 1375 "=== " ++ commit_id.short() ++ " ===\n", 1376 diff.files().map(|e| separate(" ", 1377 e.path(), 1378 "[" ++ e.status() ++ "]", 1379 "source=" ++ e.source().path() ++ " [" ++ e.source().file_type() ++ "]", 1380 "target=" ++ e.target().path() ++ " [" ++ e.target().file_type() ++ "]", 1381 ) ++ "\n").join(""), 1382 "* " ++ separate(" ", 1383 if(diff.files(), "non-empty", "empty"), 1384 "len=" ++ diff.files().len(), 1385 ) ++ "\n", 1386 ) 1387 "#}; 1388 let output = work_dir.run_jj(["log", "--no-graph", "-T", template]); 1389 insta::assert_snapshot!(output, @r" 1390 === fbad2dd53d06 === 1391 file1 [modified] source=file1 [file] target=file1 [file] 1392 file2 [modified] source=file2 [file] target=file2 [file] 1393 rename-target [renamed] source=rename-source [file] target=rename-target [file] 1394 * non-empty len=3 1395 === 3c9b3178609b === 1396 file1 [added] source=file1 [] target=file1 [file] 1397 file2 [added] source=file2 [] target=file2 [file] 1398 rename-source [added] source=rename-source [] target=rename-source [file] 1399 * non-empty len=3 1400 === 000000000000 === 1401 * empty len=0 1402 [EOF] 1403 "); 1404 1405 // custom diff stat template 1406 let template = indoc! {r#" 1407 concat( 1408 "=== " ++ commit_id.short() ++ " ===\n", 1409 "* " ++ separate(" ", 1410 "total_added=" ++ diff.stat().total_added(), 1411 "total_removed=" ++ diff.stat().total_removed(), 1412 ) ++ "\n", 1413 ) 1414 "#}; 1415 let output = work_dir.run_jj(["log", "--no-graph", "-T", template]); 1416 insta::assert_snapshot!(output, @r" 1417 === fbad2dd53d06 === 1418 * total_added=3 total_removed=1 1419 === 3c9b3178609b === 1420 * total_added=4 total_removed=0 1421 === 000000000000 === 1422 * total_added=0 total_removed=0 1423 [EOF] 1424 "); 1425} 1426 1427#[test] 1428fn test_file_list_entries() { 1429 let test_env = TestEnvironment::default(); 1430 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1431 let work_dir = test_env.work_dir("repo"); 1432 1433 work_dir.create_dir("dir"); 1434 work_dir.write_file("dir/file", "content1"); 1435 work_dir.write_file("exec-file", "content1"); 1436 work_dir.write_file("conflict-exec-file", "content1"); 1437 work_dir.write_file("conflict-file", "content1"); 1438 work_dir 1439 .run_jj(["file", "chmod", "x", "exec-file", "conflict-exec-file"]) 1440 .success(); 1441 1442 work_dir.run_jj(["new", "root()"]).success(); 1443 work_dir.write_file("conflict-exec-file", "content2"); 1444 work_dir.write_file("conflict-file", "content2"); 1445 work_dir 1446 .run_jj(["file", "chmod", "x", "conflict-exec-file"]) 1447 .success(); 1448 1449 work_dir.run_jj(["new", "all:visible_heads()"]).success(); 1450 1451 let template = indoc! {r#" 1452 separate(" ", 1453 path, 1454 "[" ++ file_type ++ "]", 1455 "conflict=" ++ conflict, 1456 "executable=" ++ executable, 1457 ) ++ "\n" 1458 "#}; 1459 let output = work_dir.run_jj(["file", "list", "-T", template]); 1460 insta::assert_snapshot!(output, @r" 1461 conflict-exec-file [conflict] conflict=true executable=true 1462 conflict-file [conflict] conflict=true executable=false 1463 dir/file [file] conflict=false executable=false 1464 exec-file [file] conflict=false executable=true 1465 [EOF] 1466 "); 1467} 1468 1469#[cfg(unix)] 1470#[test] 1471fn test_file_list_symlink() { 1472 let test_env = TestEnvironment::default(); 1473 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1474 let work_dir = test_env.work_dir("repo"); 1475 1476 std::os::unix::fs::symlink("symlink_target", work_dir.root().join("symlink")).unwrap(); 1477 1478 let template = r#"separate(" ", path, "[" ++ file_type ++ "]") ++ "\n""#; 1479 let output = work_dir.run_jj(["file", "list", "-T", template]); 1480 insta::assert_snapshot!(output, @r" 1481 symlink [symlink] 1482 [EOF] 1483 "); 1484} 1485 1486#[test] 1487fn test_repo_path() { 1488 let test_env = TestEnvironment::default(); 1489 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1490 let work_dir = test_env.work_dir("repo"); 1491 1492 work_dir.create_dir("dir"); 1493 work_dir.write_file("dir/file", "content1"); 1494 work_dir.write_file("file", "content1"); 1495 1496 let template = indoc! {r#" 1497 separate(" ", 1498 path, 1499 "display=" ++ path.display(), 1500 "parent=" ++ if(path.parent(), path.parent(), "<none>"), 1501 "parent^2=" ++ if(path.parent().parent(), path.parent().parent(), "<none>"), 1502 ) ++ "\n" 1503 "#}; 1504 let output = work_dir.run_jj(["file", "list", "-T", template]); 1505 insta::assert_snapshot!(output.normalize_backslash(), @r" 1506 dir/file display=dir/file parent=dir parent^2= 1507 file display=file parent= parent^2=<none> 1508 [EOF] 1509 "); 1510 1511 let template = r#"separate(" ", path, "display=" ++ path.display()) ++ "\n""#; 1512 let output = test_env.run_jj_in( 1513 work_dir.root().join("dir"), 1514 ["file", "list", "-T", template], 1515 ); 1516 insta::assert_snapshot!(output.normalize_backslash(), @r" 1517 dir/file display=file 1518 file display=../file 1519 [EOF] 1520 "); 1521} 1522 1523#[test] 1524fn test_signature_templates() { 1525 let test_env = TestEnvironment::default(); 1526 1527 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1528 let work_dir = test_env.work_dir("repo"); 1529 1530 work_dir.run_jj(["commit", "-m", "unsigned"]).success(); 1531 test_env.add_config("signing.behavior = 'own'"); 1532 test_env.add_config("signing.backend = 'test'"); 1533 work_dir.run_jj(["describe", "-m", "signed"]).success(); 1534 1535 let template = r#" 1536 if(signature, 1537 signature.status() ++ " " ++ signature.display(), 1538 "no", 1539 ) ++ " signature""#; 1540 1541 // show that signatures can render 1542 let output = work_dir.run_jj(["log", "-T", template]); 1543 insta::assert_snapshot!(output, @r" 1544 @ good test-display signature 1545 ○ no signature 1546 ◆ no signature 1547 [EOF] 1548 "); 1549 let output = work_dir.run_jj(["show", "-T", template]); 1550 insta::assert_snapshot!(output, @"good test-display signature[EOF]"); 1551 1552 // builtin templates 1553 test_env.add_config("ui.show-cryptographic-signatures = true"); 1554 1555 let args = ["log", "-r", "..", "-T"]; 1556 1557 let output = work_dir.run_jj_with(|cmd| cmd.args(args).arg("builtin_log_oneline")); 1558 insta::assert_snapshot!(output, @r" 1559 @ rlvkpnrz test.user 2001-02-03 08:05:09 a0909ee9 [✓︎] (empty) signed 1560 ○ qpvuntsm test.user 2001-02-03 08:05:08 879d5d20 (empty) unsigned 15611562 ~ 1563 [EOF] 1564 "); 1565 1566 let output = work_dir.run_jj_with(|cmd| cmd.args(args).arg("builtin_log_compact")); 1567 insta::assert_snapshot!(output, @r" 1568 @ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a0909ee9 [✓︎] 1569 │ (empty) signed 1570 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 879d5d20 1571 │ (empty) unsigned 1572 ~ 1573 [EOF] 1574 "); 1575 1576 let output = work_dir.run_jj_with(|cmd| cmd.args(args).arg("builtin_log_detailed")); 1577 insta::assert_snapshot!(output, @r" 1578 @ Commit ID: a0909ee96bb5c66311a0c579dc8ebed4456dfc1b 1579 │ Change ID: rlvkpnrzqnoowoytxnquwvuryrwnrmlp 1580 │ Author : Test User <test.user@example.com> (2001-02-03 08:05:09) 1581 │ Committer: Test User <test.user@example.com> (2001-02-03 08:05:09) 1582 │ Signature: good signature by test-display 15831584 │ signed 15851586 ○ Commit ID: 879d5d20fea5930f053e0817033ad4aba924a361 1587 │ Change ID: qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu 1588 ~ Author : Test User <test.user@example.com> (2001-02-03 08:05:08) 1589 Committer: Test User <test.user@example.com> (2001-02-03 08:05:08) 1590 Signature: (no signature) 1591 1592 unsigned 1593 1594 [EOF] 1595 "); 1596 1597 // customization point 1598 let config_val = r#"template-aliases."format_short_cryptographic_signature(signature)"="'status: ' ++ signature.status()""#; 1599 let output = work_dir.run_jj_with(|cmd| { 1600 cmd.args(args) 1601 .arg("builtin_log_oneline") 1602 .args(["--config", config_val]) 1603 }); 1604 insta::assert_snapshot!(output, @r" 1605 @ rlvkpnrz test.user 2001-02-03 08:05:09 a0909ee9 status: good (empty) signed 1606 ○ qpvuntsm test.user 2001-02-03 08:05:08 879d5d20 status: <Error: No CryptographicSignature available> (empty) unsigned 16071608 ~ 1609 [EOF] 1610 "); 1611}