terminal user interface to jujutsu. Focused on speed and clarity
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

make bookmarks render origin if needed

+767 -1
+752
docs/jj_template.md
··· 1 + 2 + # Templates 3 + 4 + Jujutsu supports a functional language to customize output of commands. 5 + The language consists of literals, keywords, operators, functions, and 6 + methods. 7 + 8 + A couple of `jj` commands accept a template via `-T`/`--template` option. 9 + 10 + ## Keywords 11 + 12 + Keywords represent objects of different types; the types are described in 13 + a follow-up section. In addition to context-specific keywords, the top-level 14 + object can be referenced as `self`. 15 + 16 + ### Commit keywords 17 + 18 + In `jj log` templates, all 0-argument methods of [the `Commit` 19 + type](#commit-type) are available as keywords. For example, `commit_id` is 20 + equivalent to `self.commit_id()`. 21 + 22 + ### Operation keywords 23 + 24 + In `jj op log` templates, all 0-argument methods of [the `Operation` 25 + type](#operation-type) are available as keywords. For example, 26 + `current_operation` is equivalent to `self.current_operation()`. 27 + 28 + ## Operators 29 + 30 + The following operators are supported. 31 + 32 + * `x.f()`: Method call. 33 + * `-x`: Negate integer value. 34 + * `!x`: Logical not. 35 + * `x * y`, `x / y`, `x % y`: Multiplication/division/remainder. Operands must 36 + be `Integer`s. 37 + * `x + y`, `x - y`: Addition/subtraction. Operands must be `Integer`s. 38 + * `x >= y`, `x > y`, `x <= y`, `x < y`: Greater than or equal/greater than/ 39 + lesser than or equal/lesser than. Operands must be `Integer`s. 40 + * `x == y`, `x != y`: Equal/not equal. Operands must be either `Boolean`, 41 + `Integer`, or `String`. 42 + * `x && y`: Logical and, short-circuiting. 43 + * `x || y`: Logical or, short-circuiting. 44 + * `x ++ y`: Concatenate `x` and `y` templates. 45 + 46 + (listed in order of binding strengths) 47 + 48 + ## Global functions 49 + 50 + The following functions are defined. 51 + 52 + * `fill(width: Integer, content: Template) -> Template`: Fill lines at 53 + the given `width`. 54 + * `indent(prefix: Template, content: Template) -> Template`: Indent 55 + non-empty lines by the given `prefix`. 56 + * `pad_start(width: Integer, content: Template, [fill_char: Template])`: Pad (or 57 + right-justify) content by adding leading fill characters. The `content` 58 + shouldn't have newline character. 59 + * `pad_end(width: Integer, content: Template, [fill_char: Template])`: Pad (or 60 + left-justify) content by adding trailing fill characters. The `content` 61 + shouldn't have newline character. 62 + * `pad_centered(width: Integer, content: Template, [fill_char: Template])`: Pad 63 + content by adding both leading and trailing fill characters. If an odd number 64 + of fill characters are needed, the trailing fill will be one longer than the 65 + leading fill. The `content` shouldn't have newline characters. 66 + * `truncate_start(width: Integer, content: Template, [ellipsis: Template])`: 67 + Truncate `content` by removing leading characters. The `content` shouldn't 68 + have newline character. If `ellipsis` is provided and `content` was truncated, 69 + prepend the `ellipsis` to the result. 70 + * `truncate_end(width: Integer, content: Template, [ellipsis: Template])`: 71 + Truncate `content` by removing trailing characters. The `content` shouldn't 72 + have newline character. If `ellipsis` is provided and `content` was truncated, 73 + append the `ellipsis` to the result. 74 + * `hash(content: Stringify) -> String`: 75 + Hash the input and return a hexadecimal string representation of the digest. 76 + * `label(label: Stringify, content: Template) -> Template`: Apply a custom 77 + [color label](#color-labels) to the content. The `label` is evaluated as a 78 + space-separated string. 79 + * `raw_escape_sequence(content: Template) -> Template`: Preserves any escape 80 + sequences in `content` (i.e., bypasses sanitization) and strips labels. 81 + Note: This function is intended for escape sequences and as such, its output 82 + is expected to be invisible / of no display width. Outputting content with 83 + nonzero display width may break wrapping, indentation etc. 84 + * `stringify(content: Stringify) -> String`: Format `content` to string. This 85 + effectively removes color labels. 86 + * `json(value: Serialize) -> String`: Serialize `value` in JSON format. 87 + * `if(condition: Boolean, then: Template, [else: Template]) -> Template`: 88 + Conditionally evaluate `then`/`else` template content. 89 + * `coalesce(content: Template...) -> Template`: Returns the first **non-empty** 90 + content. 91 + * `concat(content: Template...) -> Template`: 92 + Same as `content_1 ++ ... ++ content_n`. 93 + * `join(separator: Template, content: Template...) -> Template`: Insert 94 + `separator` between `content`s. 95 + * `separate(separator: Template, content: Template...) -> Template`: Insert 96 + `separator` between **non-empty** `content`s. 97 + * `surround(prefix: Template, suffix: Template, content: Template) -> Template`: 98 + Surround **non-empty** content with texts such as parentheses. 99 + * `config(name: StringLiteral) -> ConfigValue`: Look up configuration value by `name`. 100 + 101 + ## Built-in Aliases 102 + 103 + * `hyperlink(url, text)`: Creates a clickable hyperlink using [OSC8 escape sequences](https://github.com/Alhadis/OSC8-Adoption). 104 + The `text` will be displayed and clickable, linking to the given `url` in 105 + terminals that support OSC8 hyperlinks. 106 + 107 + ## Types 108 + 109 + ### `AnnotationLine` type 110 + 111 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_ 112 + 113 + The following methods are defined. 114 + 115 + * `.commit() -> Commit`: Commit responsible for changing the relevant line. 116 + * `.content() -> Template`: Line content including newline character. 117 + * `.line_number() -> Integer`: 1-based line number. 118 + * `.original_line_number() -> Integer`: 1-based line number in the original commit. 119 + * `.first_line_in_hunk() -> Boolean`: False when the directly preceding line 120 + references the same commit. 121 + 122 + ### `Boolean` type 123 + 124 + _Conversion: `Boolean`: yes, `Serialize`: yes, `Template`: yes_ 125 + 126 + No methods are defined. Can be constructed with `false` or `true` literal. 127 + 128 + ### `Commit` type 129 + 130 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_ 131 + 132 + This type cannot be printed. The following methods are defined. 133 + 134 + * `.description() -> String` 135 + * `.trailers() -> List<Trailer>`: The trailers at the end of the commit 136 + description that are formatted as `<key>: <value>`. These are returned in the 137 + same order as they appear in the description, and there may be multiple 138 + `Trailer`s with the same key. 139 + * `.change_id() -> ChangeId` 140 + * `.commit_id() -> CommitId` 141 + * `.parents() -> List<Commit>` 142 + * `.author() -> Signature` 143 + * `.committer() -> Signature` 144 + * `.signature() -> Option<CryptographicSignature>`: Cryptographic signature if 145 + the commit was signed. 146 + * `.mine() -> Boolean`: Commits where the author's email matches the email of 147 + the current user. 148 + * `.working_copies() -> List<WorkspaceRef>`: For multi-workspace repositories, 149 + returns a list of workspace references for each workspace whose working-copy 150 + commit matches the current commit. 151 + * `.current_working_copy() -> Boolean`: True for the working-copy commit of the 152 + current workspace. 153 + * `.bookmarks() -> List<CommitRef>`: Local and remote bookmarks pointing to the 154 + commit. A tracked remote bookmark will be included only if its target is 155 + different from the local one. 156 + * `.local_bookmarks() -> List<CommitRef>`: All local bookmarks pointing to the 157 + commit. 158 + * `.remote_bookmarks() -> List<CommitRef>`: All remote bookmarks pointing to the 159 + commit. 160 + * `.tags() -> List<CommitRef>`: Local and remote tags pointing to the commit. A 161 + tracked remote tag will be included only if its target is different from the 162 + local one. 163 + * `.local_tags() -> List<CommitRef>`: All local tags pointing to the commit. 164 + * `.remote_tags() -> List<CommitRef>`: All remote tags pointing to the commit. 165 + * `.divergent() -> Boolean`: True if the commit's change ID corresponds to multiple 166 + visible commits. 167 + * `.hidden() -> Boolean`: True if the commit is not visible (a.k.a. abandoned). 168 + * `.change_offset() -> Option<Integer>`: The [change offset](glossary.md#change-offset) 169 + of this commit. May not be available for some commits. 170 + * `.immutable() -> Boolean`: True if the commit is included in [the set of 171 + immutable commits](config.md#set-of-immutable-commits). 172 + * `.contained_in(revset: StringLiteral) -> Boolean`: True if the commit is included in 173 + [the provided revset](revsets.md). 174 + * `.conflict() -> Boolean`: True if the commit contains merge conflicts. 175 + * `.empty() -> Boolean`: True if the commit modifies no files. 176 + * `.diff([files: StringLiteral]) -> TreeDiff`: Changes from the parents within [the 177 + `files` expression](filesets.md). All files are compared by default, but it is 178 + likely to change in future version to respect the command line path arguments. 179 + * `.files([files: StringLiteral]) -> List<TreeEntry>`: Files that exist in this commit, 180 + matching [the `files` expression](filesets.md). Use `.diff().files()` to list 181 + changed files. 182 + * `.conflicted_files() -> List<TreeEntry>`: Conflicted files in this commit. 183 + * `.root() -> Boolean`: True if the commit is the root commit. 184 + 185 + ### `CommitEvolutionEntry` type 186 + 187 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_ 188 + 189 + This type cannot be printed. The following methods are defined. 190 + 191 + * `.commit() -> Commit`: New commit. 192 + * `.operation() -> Operation`: Operation where the commit was created or 193 + rewritten. 194 + * `.predecessors() -> List<Commit>`: Predecessor commits of this entry. 195 + * `.inter_diff([files: StringLiteral]) -> TreeDiff`: Changes between this commit and its 196 + predecessor version(s), rebased onto the parents of this commit to avoid unrelated 197 + changes (similar to `jj evolog -p`). 198 + 199 + ### `ChangeId` type 200 + 201 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 202 + 203 + The following methods are defined. 204 + 205 + * `.normal_hex() -> String`: Normal hex representation (0-9a-f) instead of the 206 + canonical "reversed" (z-k) representation. 207 + * `.short([len: Integer]) -> String` 208 + * `.shortest([min_len: Integer]) -> ShortestIdPrefix`: Shortest unique prefix. 209 + 210 + ### `CommitId` type 211 + 212 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 213 + 214 + The following methods are defined. 215 + 216 + * `.short([len: Integer]) -> String` 217 + * `.shortest([min_len: Integer]) -> ShortestIdPrefix`: Shortest unique prefix. 218 + 219 + ### `CommitRef` type 220 + 221 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 222 + 223 + The following methods are defined. 224 + 225 + * `.name() -> RefSymbol`: Local bookmark or tag name. 226 + * `.remote() -> Option<RefSymbol>`: Remote name if this is a remote ref. 227 + * `.present() -> Boolean`: True if the ref points to any commit. 228 + * `.conflict() -> Boolean`: True if [the bookmark or tag is 229 + conflicted](bookmarks.md#conflicts). 230 + * `.normal_target() -> Option<Commit>`: Target commit if the ref is not 231 + conflicted and points to a commit. 232 + * `.removed_targets() -> List<Commit>`: Old target commits if conflicted. 233 + * `.added_targets() -> List<Commit>`: New target commits. The list usually 234 + contains one "normal" target. 235 + * `.tracked() -> Boolean`: True if the ref is tracked by a local ref. The local 236 + ref might have been deleted (but not pushed yet.) 237 + * `.tracking_present() -> Boolean`: True if the ref is tracked by a local ref, 238 + and if the local ref points to any commit. 239 + * `.tracking_ahead_count() -> SizeHint`: Number of commits ahead of the tracking 240 + local ref. 241 + * `.tracking_behind_count() -> SizeHint`: Number of commits behind of the 242 + tracking local ref. 243 + * `.synced() -> Boolean`: For a local bookmark, true if synced with all tracked 244 + remotes. For a remote bookmark, true if synced with the tracking local 245 + bookmark. 246 + 247 + ### `ConfigValue` type 248 + 249 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 250 + 251 + This type can be printed in TOML syntax. The following methods are defined. 252 + 253 + * `.as_boolean() -> Boolean`: Extract boolean. 254 + * `.as_integer() -> Integer`: Extract integer. 255 + * `.as_string() -> String`: Extract string. This does not convert non-string 256 + value (e.g. integer) to string. 257 + * `.as_string_list() -> List<String>`: Extract list of strings. 258 + 259 + ### `CryptographicSignature` type 260 + 261 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_ 262 + 263 + The following methods are defined. 264 + 265 + * `.status() -> String`: The signature's status (`"good"`, `"bad"`, `"unknown"`, 266 + `"invalid"`). 267 + * `.key() -> String`: The signature's key id representation (for GPG and SSH, 268 + this is the public key fingerprint). 269 + * `.display() -> String`: The signature's display string (for GPG, this is the 270 + formatted primary user ID; for SSH, this is the principal). 271 + 272 + !!! warning 273 + 274 + Calling any of `.status()`, `.key()`, or `.display()` is slow, as it incurs 275 + the performance cost of verifying the signature (for example shelling out 276 + to `gpg` or `ssh-keygen`). Though consecutive calls will be faster, because 277 + the backend caches the verification result. 278 + 279 + !!! info 280 + 281 + As opposed to calling any of `.status()`, `.key()`, or `.display()`, 282 + checking for signature presence through boolean coercion is fast: 283 + ``` 284 + if(commit.signature(), "commit has a signature", "commit is unsigned") 285 + ``` 286 + 287 + ### `DiffStats` type 288 + 289 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_ 290 + 291 + This type can be printed as a histogram of the changes. The following methods 292 + are defined. 293 + 294 + * `.files() -> List<DiffStatEntry>`: Per-file stats for changed files. 295 + * `.total_added() -> Integer`: Total number of insertions. 296 + * `.total_removed() -> Integer`: Total number of deletions. 297 + 298 + ### `DiffStatEntry` type 299 + 300 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_ 301 + 302 + This type holds the diff stats per file. The following methods are defined. 303 + 304 + * `.bytes_delta() -> Integer`: The difference in size of the file, in bytes. 305 + * `.lines_added() -> Integer`: Number of lines added. 306 + * `.lines_removed() -> Integer`: Number of lines deleted. 307 + * `.path() -> RepoPath`: Path to the entry. If the entry is a copy/rename, this 308 + points to the target (or right) entry. 309 + * `.display_diff_path() -> String`: Format path for display, taking into account copy/rename information. 310 + * `.status() -> String`: One of `"modified"`, `"added"`, `"removed"`, `"copied"`, or `"renamed"`. 311 + * `.status_char() -> String`: One of `"M"` (modified), `"A"` (added), `"D"` (removed), 312 + `"C"` (copied), or `"R"` (renamed). 313 + 314 + ### `Email` type 315 + 316 + _Conversion: `Boolean`: yes, `Serialize`: yes, `Template`: yes_ 317 + 318 + The email field of a signature may or may not look like an email address. It may 319 + be empty, may not contain the symbol `@`, and could in principle contain 320 + multiple `@`s. 321 + 322 + The following methods are defined. 323 + 324 + * `.local() -> String`: the part of the email before the first `@`, usually the 325 + username. 326 + * `.domain() -> String`: the part of the email after the first `@` or the empty 327 + string. 328 + 329 + ### `Integer` type 330 + 331 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 332 + 333 + No methods are defined. 334 + 335 + ### `List` type 336 + 337 + _Conversion: `Boolean`: yes, `Serialize`: maybe, `Template`: maybe_ 338 + 339 + A list can be implicitly converted to `Boolean`. The following methods are 340 + defined. 341 + 342 + * `.len() -> Integer`: Number of elements in the list. 343 + * `.join(separator: Template) -> Template`: Concatenate elements with 344 + the given `separator`. 345 + * `.filter(|item| expression) -> List`: Filter list elements by predicate 346 + `expression`. Example: `description.lines().filter(|s| s.contains("#"))` 347 + * `.map(|item| expression) -> ListTemplate`: Apply template `expression` 348 + to each element. Example: `parents.map(|c| c.commit_id().short())` 349 + * `.any(|item| expression) -> Boolean`: Returns true if any element satisfies 350 + the predicate `expression`. Example: `parents.any(|c| c.description().contains("fix"))` 351 + * `.all(|item| expression) -> Boolean`: Returns true if all elements satisfy 352 + the predicate `expression`. Example: `parents.all(|c| c.mine())` 353 + 354 + ### `List<Trailer>` type 355 + 356 + The following methods are defined. See also the `List` type. 357 + 358 + * `.contains_key(key: Stringify) -> Boolean`: True if the commit description 359 + contains at least one trailer with the key `key`. 360 + 361 + ### `ListTemplate` type 362 + 363 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_ 364 + 365 + The following methods are defined. 366 + 367 + * `.join(separator: Template) -> Template`: Concatenate elements with 368 + the given `separator`. 369 + 370 + ### `Operation` type 371 + 372 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_ 373 + 374 + This type cannot be printed. The following methods are defined. 375 + 376 + * `.current_operation() -> Boolean` 377 + * `.description() -> String` 378 + * `.id() -> OperationId` 379 + * `.tags() -> String` 380 + * `.time() -> TimestampRange` 381 + * `.user() -> String` 382 + * `.snapshot() -> Boolean`: True if the operation is a snapshot operation. 383 + * `.root() -> Boolean`: True if the operation is the root operation. 384 + * `.parents() -> List<Operation>` 385 + 386 + ### `OperationId` type 387 + 388 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 389 + 390 + The following methods are defined. 391 + 392 + * `.short([len: Integer]) -> String` 393 + 394 + ### `Option` type 395 + 396 + _Conversion: `Boolean`: yes, `Serialize`: maybe, `Template`: maybe_ 397 + 398 + An option can be implicitly converted to `Boolean` denoting whether the 399 + contained value is set. If set, all methods of the contained value can be 400 + invoked. If not set, an error will be reported inline on method call. 401 + 402 + On comparison between two optional values or optional and non-optional values, 403 + unset value is not an error. Unset value is considered less than any set values. 404 + 405 + ### `RefSymbol` type 406 + 407 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 408 + 409 + [A `String` type](#string-type), but is formatted as revset symbol by quoting 410 + and escaping if necessary. Unlike strings, this cannot be implicitly converted 411 + to `Boolean`. 412 + 413 + ### `RepoPath` type 414 + 415 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 416 + 417 + A slash-separated path relative to the repository root. The following methods 418 + are defined. 419 + 420 + * `.absolute() -> String`: Format as absolute path using platform-native 421 + separator. 422 + * `.display() -> String`: Format path for display. The formatted path uses 423 + platform-native separator, and is relative to the current working directory. 424 + * `.parent() -> Option<RepoPath>`: Parent directory path. 425 + 426 + ### `Serialize` type 427 + 428 + An expression that can be serialized in machine-readable format such as JSON. 429 + 430 + !!! note 431 + 432 + Field names and value types in the serialized output are usually stable 433 + across jj versions, but the backward compatibility isn't guaranteed. If the 434 + underlying data model is updated, the serialized output may change. 435 + 436 + ### `ShortestIdPrefix` type 437 + 438 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 439 + 440 + The following methods are defined. 441 + 442 + * `.prefix() -> String` 443 + * `.rest() -> String` 444 + * `.upper() -> ShortestIdPrefix` 445 + * `.lower() -> ShortestIdPrefix` 446 + 447 + ### `Signature` type 448 + 449 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 450 + 451 + The following methods are defined. 452 + 453 + * `.name() -> String` 454 + * `.email() -> Email` 455 + * `.timestamp() -> Timestamp` 456 + 457 + ### `SizeHint` type 458 + 459 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: no_ 460 + 461 + This type cannot be printed. The following methods are defined. 462 + 463 + * `.lower() -> Integer`: Lower bound. 464 + * `.upper() -> Option<Integer>`: Upper bound if known. 465 + * `.exact() -> Option<Integer>`: Exact value if upper bound is known and it 466 + equals to the lower bound. 467 + * `.zero() -> Boolean`: True if upper bound is known and is `0`. Equivalent to 468 + `.upper() == 0`. 469 + 470 + ### `String` type 471 + 472 + _Conversion: `Boolean`: yes, `Serialize`: yes, `Template`: yes_ 473 + 474 + A string can be implicitly converted to `Boolean`. The following methods are 475 + defined. 476 + 477 + * `.len() -> Integer`: Length in UTF-8 bytes. 478 + * `.contains(needle: Stringify) -> Boolean`: Whether the string contains the 479 + provided stringifiable value as a substring. 480 + * `.match(needle: StringPattern) -> String`: Extracts 481 + the first matching part of the string for the given pattern. 482 + 483 + An empty string is returned if there is no match. 484 + * `.replace(pattern: StringPattern, replacement: Stringify, [limit: Integer]) -> String`: 485 + Replace occurrences of the given `pattern` with the `replacement` string. 486 + 487 + By default, all occurrences are replaced. If `limit` is specified, at most 488 + that many occurrences are replaced. 489 + 490 + Supports capture groups in patterns using `$0` (entire match), `$1`, `$2` etc. 491 + * `.first_line() -> String` 492 + * `.lines() -> List<String>`: Split into lines excluding newline characters. 493 + * `.split(separator: StringPattern, [limit: Integer]) -> List<String>`: Split into 494 + substrings by the given `separator` pattern. If `limit` is specified, it 495 + determines the maximum number of elements in the result, with the remainder 496 + of the string returned as the final element. A `limit` of 0 returns an empty list. 497 + * `.upper() -> String` 498 + * `.lower() -> String` 499 + * `.starts_with(needle: Stringify) -> Boolean` 500 + * `.ends_with(needle: Stringify) -> Boolean` 501 + * `.remove_prefix(needle: Stringify) -> String`: Removes the passed prefix, if 502 + present. 503 + * `.remove_suffix(needle: Stringify) -> String`: Removes the passed suffix, if 504 + present. 505 + * `.trim() -> String`: Removes leading and trailing whitespace 506 + * `.trim_start() -> String`: Removes leading whitespace 507 + * `.trim_end() -> String`: Removes trailing whitespace 508 + * `.substr(start: Integer, end: Integer) -> String`: Extract substring. The 509 + `start`/`end` indices should be specified in UTF-8 bytes. Indices are 0-based 510 + and `end` is exclusive. Negative values count from the end of the string, 511 + with `-1` being the last byte. If the `start` index is in the middle of a UTF-8 512 + codepoint, the codepoint is fully part of the result. If the `end` index is in 513 + the middle of a UTF-8 codepoint, the codepoint is not part of the result. 514 + * `.escape_json() -> String`: Serializes the string in JSON format. This 515 + function is useful for making machine-readable templates. For example, you 516 + can use it in a template like `'{ "foo": ' ++ foo.escape_json() ++ ' }'` to 517 + return a JSON/JSONL. 518 + 519 + ### `StringLiteral` type 520 + 521 + A string literal known at parse time. Unlike `Stringify`, this cannot be a 522 + dynamic expression - it must be a literal value like `"main"` or `"format"`. 523 + 524 + String literals must be surrounded by single or double quotes (`'` or `"`). 525 + A double-quoted string literal supports the following escape sequences: 526 + 527 + * `\"`: double quote 528 + * `\\`: backslash 529 + * `\t`: horizontal tab 530 + * `\r`: carriage return 531 + * `\n`: new line 532 + * `\0`: null 533 + * `\e`: escape (i.e., `\x1b`) 534 + * `\xHH`: byte with hex value `HH` 535 + 536 + Other escape sequences are not supported. Any UTF-8 characters are allowed 537 + inside a string literal, with two exceptions: unescaped `"`-s and uses of `\` 538 + that don't form a valid escape sequence. 539 + 540 + A single-quoted string literal has no escape syntax. `'` can't be expressed 541 + inside a single-quoted string literal. 542 + 543 + String literals have their own type so that the value can be validated at parse 544 + time. For example, `contained_in(revset)` requires a literal so the revset can 545 + be parsed and checked before the template is evaluated. 546 + 547 + ### `Stringify` type 548 + 549 + An expression that can be converted to a `String`. 550 + 551 + Any types that can be converted to `Template` can also be `Stringify`. Unlike 552 + `Template`, color labels are stripped. 553 + 554 + ### `StringPattern` type 555 + 556 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_ 557 + 558 + These are the exact same as the [String pattern type] in revsets, except that 559 + quotes are mandatory. 560 + 561 + Literal strings may be used, which are interpreted as case-sensitive substring 562 + matching. 563 + 564 + Currently `StringPattern` values cannot be passed around as values and may 565 + only occur directly in the call site they are used in. 566 + 567 + [String pattern type]: revsets.md#string-patterns 568 + 569 + ### `Template` type 570 + 571 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_ 572 + 573 + Most types can be implicitly converted to `Template`. No methods are defined. 574 + 575 + ### `Timestamp` type 576 + 577 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 578 + 579 + The following methods are defined. 580 + 581 + * `.ago() -> String`: Format as relative timestamp. 582 + * `.format(format: StringLiteral) -> String`: Format with [the specified strftime-like 583 + format string](https://docs.rs/chrono/latest/chrono/format/strftime/). 584 + * `.utc() -> Timestamp`: Convert timestamp into UTC timezone. 585 + * `.local() -> Timestamp`: Convert timestamp into local timezone. 586 + * `.after(date: StringLiteral) -> Boolean`: True if the timestamp is exactly at or 587 + after the given date. Supported date formats are the same as the revset 588 + [Date pattern type]. 589 + * `.before(date: StringLiteral) -> Boolean`: True if the timestamp is before, but 590 + not including, the given date. Supported date formats are the same as the 591 + revset [Date pattern type]. 592 + 593 + [Date pattern type]: revsets.md#date-patterns 594 + 595 + ### `TimestampRange` type 596 + 597 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 598 + 599 + The following methods are defined. 600 + 601 + * `.start() -> Timestamp` 602 + * `.end() -> Timestamp` 603 + * `.duration() -> String` 604 + 605 + ### `Trailer` type 606 + 607 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: yes_ 608 + 609 + The following methods are defined. 610 + 611 + * `.key() -> String` 612 + * `.value() -> String` 613 + 614 + ### `TreeDiff` type 615 + 616 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_ 617 + 618 + This type cannot be printed. The following methods are defined. 619 + 620 + * `.files() -> List<TreeDiffEntry>`: Changed files. 621 + * `.color_words([context: Integer]) -> Template`: Format as a word-level diff 622 + with changes indicated only by color. 623 + * `.git([context: Integer]) -> Template`: Format as a Git diff. 624 + * `.stat([width: Integer]) -> DiffStats`: Calculate stats of changed lines. 625 + * `.summary() -> Template`: Format as a list of status code and path pairs. 626 + 627 + ### `TreeDiffEntry` type 628 + 629 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_ 630 + 631 + This type cannot be printed. The following methods are defined. 632 + 633 + * `.path() -> RepoPath`: Path to the entry. If the entry is a copy/rename, this 634 + points to the target (or right) entry. 635 + * `.display_diff_path() -> String`: Format path for display, taking into account copy/rename information. 636 + * `.status() -> String`: One of `"modified"`, `"added"`, `"removed"`, 637 + `"copied"`, or `"renamed"`. 638 + * `.status_char() -> String`: Single-character status indicator: `"M"` for modified, 639 + `"A"` for added, `"D"` for removed, `"C"` for copied, or `"R"` for renamed. 640 + * `.source() -> TreeEntry`: The source (or left) entry. 641 + * `.target() -> TreeEntry`: The target (or right) entry. 642 + 643 + ### `TreeEntry` type 644 + 645 + _Conversion: `Boolean`: no, `Serialize`: no, `Template`: no_ 646 + 647 + This type cannot be printed. The following methods are defined. 648 + 649 + * `.path() -> RepoPath`: Path to the entry. 650 + * `.conflict() -> Boolean`: True if the entry is a merge conflict. 651 + * `.conflict_side_count() -> Integer`: Number of sides in the merge conflict (1 if not 652 + conflicted, 2 or more for multi-way merges). 653 + * `.file_type() -> String`: One of `"file"`, `"symlink"`, `"tree"`, 654 + `"git-submodule"`, or `"conflict"`. 655 + * `.executable() -> Boolean`: True if the entry is an executable file. 656 + 657 + ### `WorkspaceRef` type 658 + 659 + _Conversion: `Boolean`: no, `Serialize`: yes, `Template`: yes_ 660 + 661 + The following methods are defined. 662 + 663 + * `.name() -> RefSymbol`: Returns the workspace name as a symbol. 664 + * `.target() -> Commit`: Returns the working-copy commit of this workspace. 665 + 666 + ## Color labels 667 + 668 + You can [customize the output colors][config-colors] by using color labels. `jj` 669 + adds some labels automatically; they can also be added manually. 670 + 671 + Template fragments are usually **automatically** labeled with the command name, 672 + the context (or the top-level object), and the method names. For example, the 673 + following template is labeled as `op_log operation id short` automatically: 674 + 675 + ```sh 676 + jj op log -T 'self.id().short()' 677 + ``` 678 + 679 + The exact names of such labels are often straightforward, but are not currently 680 + documented. You can discover the actual label names used with the 681 + `--color=debug` option, e.g. 682 + 683 + ```sh 684 + jj op log -T 'self.id().short()' --color=debug 685 + ``` 686 + 687 + Additionally, you can **manually** insert arbitrary labels using the 688 + `label(label, content)` function. For example, 689 + 690 + ```sh 691 + jj op log -T '"ID: " ++ self.id().short().substr(0, 1) ++ label("id short", "<redacted>")' 692 + ``` 693 + 694 + will print "ID:" in the default style, and the string `<redacted>` in the same 695 + style as the first character of the id. It would also be fine to use an 696 + arbitrary template instead of the string `"<redacted>"`, possibly including 697 + nested invocations of `label()`. 698 + 699 + You are free to use custom label names as well. This will only have a visible 700 + effect if you also [customize their colors][config-colors] explicitly. 701 + 702 + [config-colors]: config.md#custom-colors-and-styles 703 + 704 + ## Configuration 705 + 706 + The default templates and aliases() are defined in the `[templates]` and 707 + `[template-aliases]` sections of the config respectively. The exact definitions 708 + can be seen in the [`cli/src/config/templates.toml`][1] file in jj's source 709 + tree. 710 + 711 + [1]: https://github.com/jj-vcs/jj/blob/main/cli/src/config/templates.toml 712 + 713 + <!--- TODO: Find a way to embed the default config files in the docs --> 714 + 715 + New keywords and functions can be defined as aliases, by using any 716 + combination of the predefined keywords/functions and other aliases. 717 + 718 + Alias functions can be overloaded by the number of parameters. However, builtin 719 + functions will be shadowed by name, and can't co-exist with aliases. 720 + 721 + For example: 722 + 723 + ```toml 724 + [template-aliases] 725 + 'commit_change_ids' = ''' 726 + concat( 727 + format_field("Commit ID", commit_id), 728 + format_field("Change ID", change_id), 729 + ) 730 + ''' 731 + 'format_field(key, value)' = 'key ++ ": " ++ value ++ "\n"' 732 + ``` 733 + 734 + ## Examples 735 + 736 + Get short commit IDs of the working-copy parents: 737 + 738 + ```sh 739 + jj log --no-graph -r @ -T 'parents.map(|c| c.commit_id().short()).join(",")' 740 + ``` 741 + 742 + Show machine-readable list of full commit and change IDs: 743 + 744 + ```sh 745 + jj log --no-graph -T 'commit_id ++ " " ++ change_id ++ "\n"' 746 + ``` 747 + 748 + Print the description of the current commit, defaulting to `(no description set)`: 749 + 750 + ```sh 751 + jj log -r @ --no-graph -T 'coalesce(description, "(no description set)\n")' 752 + ```
+15 -1
jj_tui/lib/jj_json.ml
··· 49 49 ++ ',"hidden":' ++ json(hidden) 50 50 ++ ',"divergent":' ++ json(divergent) 51 51 ++ ',"empty":' ++ json(empty) 52 - ++ ',"bookmarks":[' ++ bookmarks.map(|b| json(b.name())).join(",") ++ ']' 52 + ++ ',"bookmarks":[' 53 + ++ bookmarks 54 + .map(|b| 55 + json( 56 + stringify( 57 + if( 58 + b.remote(), 59 + b.name() ++ "@" ++ b.remote(), 60 + if(b.tracked() && !b.synced(), b.name() ++ "*", b.name()) 61 + ) 62 + ) 63 + ) 64 + ) 65 + .join(",") 66 + ++ ']' 53 67 ++ ',"author":{"email":' ++ json(author.email().local()) ++ ',"timestamp":' ++ json(author.timestamp().local().format("%Y-%m-%d %H:%M:%S")) ++ '}' 54 68 ++ ',"change_id_prefix":' ++ json(change_id.shortest(8).prefix()) 55 69 ++ ',"change_id_rest":' ++ json(change_id.shortest(8).rest())