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

Configure Feed

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

make bookmarks render origin if need

+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())