879 lines
19 KiB
Markdown
879 lines
19 KiB
Markdown
# Jujutsu Templates - Complete Reference
|
|
|
|
Templates are a functional language in jujutsu for customizing command output. This document provides comprehensive coverage of template syntax, keywords, functions, operators, and practical usage patterns.
|
|
|
|
## Overview
|
|
|
|
The template language enables customization of jujutsu command output via the `-T`/`--template` option. Templates can format text, apply styling, perform calculations, and implement conditional logic.
|
|
|
|
## Basic Usage
|
|
|
|
### Command Line Templates
|
|
|
|
```bash
|
|
# Simple template
|
|
jj log -T 'commit_id.short()'
|
|
|
|
# Multiple components
|
|
jj log -T 'commit_id.short() ++ " " ++ description.first_line()'
|
|
|
|
# With formatting
|
|
jj log -T 'commit_id.short() ++ "\n" ++ indent(" ", description)'
|
|
```
|
|
|
|
### Configuration Templates
|
|
|
|
Set default templates in config (`jj config edit --user`):
|
|
|
|
```toml
|
|
[templates]
|
|
log = '''
|
|
commit_id.short() ++ " "
|
|
++ if(conflict(), "⚠️ ", "")
|
|
++ description.first_line()
|
|
'''
|
|
|
|
op_log = 'id.short() ++ " " ++ description'
|
|
```
|
|
|
|
## Keywords
|
|
|
|
Keywords represent objects and their properties. The available keywords depend on the command context.
|
|
|
|
### Commit Keywords
|
|
|
|
Available in commit templates (e.g., `jj log`, `jj show`):
|
|
|
|
#### Basic Properties
|
|
- `commit_id` - Commit ID (CommitId type)
|
|
- `change_id` - Change ID (ChangeId type)
|
|
- `description` - Commit description (String)
|
|
- `author` - Commit author (Signature type)
|
|
- `committer` - Committer (Signature type)
|
|
- `working_copies` - Workspaces where this is the working copy (String)
|
|
- `current_working_copy` - Boolean, true if this is current working copy
|
|
- `bookmarks` - Bookmarks pointing to this commit (List<RefName>)
|
|
- `tags` - Tags pointing to this commit (List<RefName>)
|
|
- `git_refs` - Git refs pointing to this commit (List<RefName>)
|
|
- `git_head` - Git HEAD if pointing to this commit (RefName)
|
|
- `divergent` - Boolean, true if change has divergent commits
|
|
- `hidden` - Boolean, true if commit is hidden
|
|
- `immutable` - Boolean, true if commit is immutable
|
|
- `conflict` - Boolean, true if commit has conflicts
|
|
- `empty` - Boolean, true if commit has no changes
|
|
- `root` - Boolean, true if this is the root commit
|
|
|
|
#### Relationship Properties
|
|
- `parents` - Parent commits (List<Commit>)
|
|
- `contained_in` - Bookmarks/tags containing this commit (String)
|
|
|
|
#### Tree Properties
|
|
- `tree` - Commit tree object (Tree type)
|
|
|
|
### Operation Keywords
|
|
|
|
Available in operation templates (e.g., `jj op log`):
|
|
|
|
- `current_operation` - Boolean, true for current operation
|
|
- `description` - Operation description (String)
|
|
- `id` - Operation ID (OperationId type)
|
|
- `tags` - Tags on this operation (String)
|
|
- `time` - Operation timestamp (Timestamp type)
|
|
- `user` - User who performed operation (String)
|
|
- `root` - Boolean, true if root operation
|
|
|
|
## Types and Methods
|
|
|
|
### CommitId / ChangeId
|
|
|
|
Hexadecimal identifiers.
|
|
|
|
**Methods:**
|
|
- `.short([len])` - Abbreviated form (default 12 chars)
|
|
- `.shortest([min_len])` - Shortest unique prefix (min 4 chars)
|
|
- `.hex()` - Full hexadecimal string
|
|
|
|
```bash
|
|
jj log -T 'commit_id.short()' # 12 chars
|
|
jj log -T 'commit_id.short(8)' # 8 chars
|
|
jj log -T 'commit_id.shortest()' # Unique prefix
|
|
jj log -T 'change_id.hex()' # Full ID
|
|
```
|
|
|
|
### String
|
|
|
|
Text values.
|
|
|
|
**Methods:**
|
|
- `.contains(needle)` - Check if contains substring
|
|
- `.starts_with(prefix)` - Check if starts with prefix
|
|
- `.ends_with(suffix)` - Check if ends with suffix
|
|
- `.remove_prefix(prefix)` - Remove prefix if present
|
|
- `.remove_suffix(suffix)` - Remove suffix if present
|
|
- `.substr(start, end)` - Extract substring
|
|
- `.first_line()` - First line only
|
|
- `.lines()` - Split into list of lines
|
|
- `.upper()` - Convert to uppercase
|
|
- `.lower()` - Convert to lowercase
|
|
- `.len()` - String length
|
|
|
|
```bash
|
|
jj log -T 'description.first_line()'
|
|
jj log -T 'description.upper()'
|
|
jj log -T 'if(description.contains("bug"), "🐛", "")'
|
|
jj log -T 'description.substr(0, 50)'
|
|
```
|
|
|
|
### Signature
|
|
|
|
Author/committer information.
|
|
|
|
**Methods:**
|
|
- `.name()` - Person's name
|
|
- `.email()` - Email address
|
|
- `.username()` - Username (part before @ in email)
|
|
- `.timestamp()` - Signature timestamp
|
|
|
|
```bash
|
|
jj log -T 'author.name()'
|
|
jj log -T 'author.email()'
|
|
jj log -T 'author.username() ++ " " ++ author.timestamp()'
|
|
```
|
|
|
|
### Timestamp
|
|
|
|
Date and time values.
|
|
|
|
**Methods:**
|
|
- `.ago()` - Relative time ("2 days ago")
|
|
- `.format(format_string)` - Custom format (strftime)
|
|
- `.utc()` - Format in UTC
|
|
|
|
```bash
|
|
jj log -T 'author.timestamp().ago()'
|
|
jj log -T 'author.timestamp().format("%Y-%m-%d")'
|
|
jj log -T 'author.timestamp().format("%Y-%m-%d %H:%M:%S")'
|
|
jj log -T 'author.timestamp().utc()'
|
|
```
|
|
|
|
Format specifiers (strftime):
|
|
- `%Y` - Year (4 digits)
|
|
- `%m` - Month (01-12)
|
|
- `%d` - Day (01-31)
|
|
- `%H` - Hour (00-23)
|
|
- `%M` - Minute (00-59)
|
|
- `%S` - Second (00-59)
|
|
- `%a` - Weekday abbreviation
|
|
- `%b` - Month abbreviation
|
|
|
|
### Boolean
|
|
|
|
True/false values.
|
|
|
|
**Usage in conditionals:**
|
|
```bash
|
|
jj log -T 'if(conflict, "⚠️ CONFLICT", "")'
|
|
jj log -T 'if(empty, "[empty]", description.first_line())'
|
|
jj log -T 'if(current_working_copy, "→ ", " ")'
|
|
```
|
|
|
|
### List
|
|
|
|
Collections of values.
|
|
|
|
**Methods:**
|
|
- `.len()` - Number of items
|
|
- `.join(separator)` - Join into string
|
|
- `.map(|item| template)` - Transform each item
|
|
- `.filter(|item| condition)` - Select items matching condition
|
|
- `.any(|item| condition)` - True if any item matches
|
|
- `.all(|item| condition)` - True if all items match
|
|
|
|
```bash
|
|
# Join parent IDs
|
|
jj log -T 'parents.map(|c| c.commit_id().short()).join(", ")'
|
|
|
|
# Show bookmarks
|
|
jj log -T 'bookmarks.map(|b| b.name()).join(" ")'
|
|
|
|
# Filter and show
|
|
jj log -T 'tags.filter(|t| t.name().starts_with("v")).map(|t| t.name()).join(", ")'
|
|
```
|
|
|
|
### RefName
|
|
|
|
Reference names (bookmarks, tags, git refs).
|
|
|
|
**Methods:**
|
|
- `.name()` - Reference name
|
|
- `.remote()` - Remote name (for remote refs)
|
|
- `.present()` - Boolean, true if ref exists
|
|
|
|
```bash
|
|
jj log -T 'bookmarks.map(|b| b.name()).join(", ")'
|
|
jj log -T 'git_refs.map(|r| r.name()).join(", ")'
|
|
```
|
|
|
|
### Commit
|
|
|
|
Full commit objects (from parents, etc.).
|
|
|
|
**Methods:**
|
|
All commit keywords available as methods:
|
|
- `.commit_id()`
|
|
- `.change_id()`
|
|
- `.description()`
|
|
- `.author()`
|
|
- `.parents()`
|
|
- etc.
|
|
|
|
```bash
|
|
jj log -T 'parents.map(|c| c.commit_id().short()).join(", ")'
|
|
jj log -T 'parents.map(|c| c.author().name()).join(" & ")'
|
|
```
|
|
|
|
## Operators
|
|
|
|
### Template Concatenation (`++`)
|
|
|
|
Combine templates.
|
|
|
|
```bash
|
|
jj log -T 'commit_id.short() ++ " " ++ description.first_line()'
|
|
jj log -T 'author.name() ++ " <" ++ author.email() ++ ">"'
|
|
jj log -T '"[" ++ change_id.short() ++ "]"'
|
|
```
|
|
|
|
### Logical Operators
|
|
|
|
- `x && y` - Logical AND
|
|
- `x || y` - Logical OR
|
|
- `!x` - Logical NOT
|
|
|
|
```bash
|
|
jj log -T 'if(conflict && !empty, "⚠️", "")'
|
|
jj log -T 'if(immutable || hidden, "SKIP", commit_id.short())'
|
|
```
|
|
|
|
### Comparison Operators
|
|
|
|
- `x == y` - Equal
|
|
- `x != y` - Not equal
|
|
- `x < y` - Less than
|
|
- `x > y` - Greater than
|
|
- `x <= y` - Less than or equal
|
|
- `x >= y` - Greater than or equal
|
|
|
|
```bash
|
|
jj log -T 'if(parents.len() > 1, "MERGE", "NORMAL")'
|
|
jj log -T 'if(description.len() < 10, "SHORT", description.first_line())'
|
|
```
|
|
|
|
### Arithmetic Operators
|
|
|
|
- `x + y` - Addition
|
|
- `x - y` - Subtraction
|
|
- `x * y` - Multiplication
|
|
- `x / y` - Division (integer)
|
|
- `x % y` - Modulo
|
|
|
|
```bash
|
|
jj log -T 'if(author.timestamp().format("%H").parse_int() > 12, "PM", "AM")'
|
|
```
|
|
|
|
### Method Calls
|
|
|
|
- `x.method()` - Call method on object
|
|
- `x.method(arg1, arg2)` - Call with arguments
|
|
|
|
```bash
|
|
jj log -T 'commit_id.short(8)'
|
|
jj log -T 'description.substr(0, 50)'
|
|
jj log -T 'author.timestamp().format("%Y-%m-%d")'
|
|
```
|
|
|
|
## Functions
|
|
|
|
Built-in template functions.
|
|
|
|
### Conditional Functions
|
|
|
|
#### `if(condition, then, else)`
|
|
|
|
Conditional output.
|
|
|
|
```bash
|
|
jj log -T 'if(conflict, "⚠️ ", "") ++ description.first_line()'
|
|
jj log -T 'if(empty, "[empty]", commit_id.short())'
|
|
jj log -T 'if(current_working_copy, "→ ", " ") ++ description.first_line()'
|
|
|
|
# Nested conditionals
|
|
jj log -T '
|
|
if(conflict, "⚠️",
|
|
if(empty, "∅",
|
|
if(divergent, "◆", "●")))
|
|
++ " " ++ description.first_line()
|
|
'
|
|
```
|
|
|
|
#### `coalesce(option1, option2, ...)`
|
|
|
|
Return first non-empty value.
|
|
|
|
```bash
|
|
jj log -T 'coalesce(description, "[no description]")'
|
|
jj log -T 'coalesce(bookmarks.map(|b| b.name()).join(", "), "[no bookmarks]")'
|
|
```
|
|
|
|
### Formatting Functions
|
|
|
|
#### `label(label, content)`
|
|
|
|
Apply styling/color labels.
|
|
|
|
```bash
|
|
jj log -T 'label("commit_id", commit_id.short())'
|
|
jj log -T 'label("error", "CONFLICT") ++ " " ++ description'
|
|
jj log -T 'if(conflict, label("error", "⚠"), "") ++ description.first_line()'
|
|
```
|
|
|
|
Common labels:
|
|
- `commit_id` - Commit ID styling
|
|
- `change_id` - Change ID styling
|
|
- `author` - Author name styling
|
|
- `timestamp` - Timestamp styling
|
|
- `bookmark` - Bookmark styling
|
|
- `tag` - Tag styling
|
|
- `error` - Error styling
|
|
- `warning` - Warning styling
|
|
|
|
#### `fill(width, content)`
|
|
|
|
Wrap text to specified width.
|
|
|
|
```bash
|
|
jj log -T 'fill(80, description)'
|
|
jj log -T 'commit_id.short() ++ "\n" ++ fill(72, description)'
|
|
```
|
|
|
|
#### `indent(prefix, content)`
|
|
|
|
Indent lines with prefix.
|
|
|
|
```bash
|
|
jj log -T 'commit_id.short() ++ "\n" ++ indent(" ", description)'
|
|
jj log -T 'description.first_line() ++ "\n" ++ indent(" ", author.name())'
|
|
```
|
|
|
|
#### `pad_start(width, content[, fill_char])`
|
|
|
|
Pad start to width.
|
|
|
|
```bash
|
|
jj log -T 'pad_start(10, commit_id.short())'
|
|
jj log -T 'pad_start(20, author.name(), ".")'
|
|
```
|
|
|
|
#### `pad_end(width, content[, fill_char])`
|
|
|
|
Pad end to width.
|
|
|
|
```bash
|
|
jj log -T 'pad_end(50, description.first_line())'
|
|
jj log -T 'pad_end(30, author.name(), " ") ++ commit_id.short()'
|
|
```
|
|
|
|
#### `truncate_start(width, content)`
|
|
|
|
Truncate from start if too long.
|
|
|
|
```bash
|
|
jj log -T 'truncate_start(50, description.first_line())'
|
|
```
|
|
|
|
#### `truncate_end(width, content)`
|
|
|
|
Truncate from end if too long.
|
|
|
|
```bash
|
|
jj log -T 'truncate_end(50, description.first_line())'
|
|
jj log -T 'pad_end(60, truncate_end(60, description.first_line()))'
|
|
```
|
|
|
|
### List Functions
|
|
|
|
#### `separate(separator, ...values)`
|
|
|
|
Join values with separator, skipping empty ones.
|
|
|
|
```bash
|
|
jj log -T 'separate(" | ", commit_id.short(), author.name(), description.first_line())'
|
|
jj log -T 'separate("\n", description, bookmarks.map(|b| b.name()).join(", "))'
|
|
```
|
|
|
|
Unlike `++`, `separate()` skips empty values:
|
|
```bash
|
|
# If bookmarks is empty, no trailing separator
|
|
jj log -T 'separate(" ", commit_id.short(), bookmarks.map(|b| b.name()).join(","))'
|
|
```
|
|
|
|
### Serialization Functions
|
|
|
|
#### `json(value)`
|
|
|
|
Serialize value to JSON.
|
|
|
|
```bash
|
|
jj log -T 'json(commit_id.short())'
|
|
jj log -T 'json(description.first_line())'
|
|
|
|
# For structured output
|
|
jj log -T '
|
|
"{" ++
|
|
"\"commit\": " ++ json(commit_id.short()) ++ "," ++
|
|
"\"description\": " ++ json(description.first_line()) ++
|
|
"}"
|
|
'
|
|
```
|
|
|
|
## Practical Examples
|
|
|
|
### Compact Log Format
|
|
|
|
```bash
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " "
|
|
++ author.username()
|
|
++ " "
|
|
++ author.timestamp().ago()
|
|
++ " "
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
### Detailed Format
|
|
|
|
```bash
|
|
jj log -T '
|
|
"commit " ++ commit_id.short() ++ "\n"
|
|
++ "Author: " ++ author.name() ++ " <" ++ author.email() ++ ">\n"
|
|
++ "Date: " ++ author.timestamp().format("%Y-%m-%d %H:%M:%S") ++ "\n"
|
|
++ "\n"
|
|
++ indent(" ", description)
|
|
'
|
|
```
|
|
|
|
### Status Indicators
|
|
|
|
```bash
|
|
jj log -T '
|
|
if(current_working_copy, "→", " ")
|
|
++ if(conflict, "⚠", " ")
|
|
++ if(empty, "∅", " ")
|
|
++ if(divergent, "◆", " ")
|
|
++ " "
|
|
++ commit_id.short()
|
|
++ " "
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
### Branch and Tag Display
|
|
|
|
```bash
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " "
|
|
++ separate(" ",
|
|
bookmarks.map(|b| label("bookmark", b.name())).join(" "),
|
|
tags.map(|t| label("tag", t.name())).join(" "))
|
|
++ if(bookmarks.len() > 0 || tags.len() > 0, "\n ", " ")
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
### Parent Commit IDs
|
|
|
|
```bash
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " (parents: "
|
|
++ parents.map(|c| c.commit_id().short()).join(", ")
|
|
++ ") "
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
### Merge Commit Detection
|
|
|
|
```bash
|
|
jj log -T '
|
|
if(parents.len() > 1,
|
|
"MERGE " ++ commit_id.short() ++ "\n"
|
|
++ " parents: " ++ parents.map(|c| c.commit_id().short()).join(", ") ++ "\n"
|
|
++ " " ++ description.first_line(),
|
|
commit_id.short() ++ " " ++ description.first_line())
|
|
'
|
|
```
|
|
|
|
### JSON Output
|
|
|
|
```bash
|
|
jj log -T '
|
|
"{\"commit_id\": " ++ json(commit_id.short())
|
|
++ ", \"change_id\": " ++ json(change_id.short())
|
|
++ ", \"author\": " ++ json(author.name())
|
|
++ ", \"email\": " ++ json(author.email())
|
|
++ ", \"date\": " ++ json(author.timestamp().format("%Y-%m-%d %H:%M:%S"))
|
|
++ ", \"description\": " ++ json(description.first_line())
|
|
++ "}"
|
|
'
|
|
```
|
|
|
|
### Author Statistics
|
|
|
|
```bash
|
|
jj log -T '
|
|
author.name()
|
|
++ " ("
|
|
++ author.email()
|
|
++ ") - "
|
|
++ author.timestamp().format("%Y-%m-%d")
|
|
++ " - "
|
|
++ commit_id.short()
|
|
'
|
|
```
|
|
|
|
### Time-Based Formatting
|
|
|
|
```bash
|
|
jj log -T '
|
|
"["
|
|
++ author.timestamp().format("%Y-%m-%d %H:%M")
|
|
++ "] "
|
|
++ author.username()
|
|
++ ": "
|
|
++ truncate_end(60, description.first_line())
|
|
'
|
|
```
|
|
|
|
## Template Aliases
|
|
|
|
Define reusable template fragments in config:
|
|
|
|
```toml
|
|
[template-aliases]
|
|
# Simple substitutions
|
|
short_commit = 'commit_id.short()'
|
|
full_author = 'author.name() ++ " <" ++ author.email() ++ ">"'
|
|
|
|
# Complex templates
|
|
format_timestamp = 'author.timestamp().format("%Y-%m-%d %H:%M")'
|
|
|
|
status_icons = '''
|
|
if(current_working_copy, "→", " ")
|
|
++ if(conflict, "⚠", " ")
|
|
++ if(empty, "∅", " ")
|
|
'''
|
|
|
|
compact_commit = '''
|
|
commit_id.short()
|
|
++ " "
|
|
++ author.username()
|
|
++ " "
|
|
++ description.first_line()
|
|
'''
|
|
```
|
|
|
|
Use in templates:
|
|
```bash
|
|
jj log -T 'status_icons ++ compact_commit'
|
|
jj log -T 'short_commit ++ " by " ++ full_author'
|
|
```
|
|
|
|
## Advanced Patterns
|
|
|
|
### Conditional Styling
|
|
|
|
```bash
|
|
jj log -T '
|
|
label(
|
|
if(conflict, "error",
|
|
if(empty, "warning", "commit_id")),
|
|
commit_id.short())
|
|
++ " "
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
### Multi-Line Descriptions
|
|
|
|
```bash
|
|
jj log -T '
|
|
commit_id.short() ++ "\n"
|
|
++ separate("\n",
|
|
if(bookmarks.len() > 0, "Bookmarks: " ++ bookmarks.map(|b| b.name()).join(", "), ""),
|
|
if(tags.len() > 0, "Tags: " ++ tags.map(|t| t.name()).join(", "), ""))
|
|
++ "\n"
|
|
++ indent(" ", description)
|
|
++ "\n"
|
|
'
|
|
```
|
|
|
|
### Working Copy Highlighting
|
|
|
|
```bash
|
|
jj log -T '
|
|
if(current_working_copy,
|
|
label("working_copy", ">> " ++ commit_id.short() ++ " <<"),
|
|
" " ++ commit_id.short() ++ " ")
|
|
++ " "
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
### List Filtering and Mapping
|
|
|
|
```bash
|
|
# Show only remote bookmarks
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " "
|
|
++ bookmarks
|
|
.filter(|b| b.remote() != "")
|
|
.map(|b| b.remote() ++ "/" ++ b.name())
|
|
.join(", ")
|
|
'
|
|
|
|
# Show version tags only
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " "
|
|
++ tags
|
|
.filter(|t| t.name().starts_with("v"))
|
|
.map(|t| t.name())
|
|
.join(", ")
|
|
'
|
|
```
|
|
|
|
### Complex Conditional Logic
|
|
|
|
```bash
|
|
jj log -T '
|
|
if(conflict,
|
|
label("error", "⚠ CONFLICT") ++ " " ++ commit_id.short(),
|
|
if(empty,
|
|
label("warning", "∅ EMPTY") ++ " " ++ commit_id.short(),
|
|
if(divergent,
|
|
label("warning", "◆ DIVERGENT") ++ " " ++ commit_id.short(),
|
|
commit_id.short())))
|
|
++ "\n"
|
|
++ if(current_working_copy, "→ ", " ")
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### Avoid Expensive Operations in Large Histories
|
|
|
|
```bash
|
|
# Expensive - computes for every commit
|
|
jj log -T 'commit_id.short() ++ " " ++ contained_in'
|
|
|
|
# Better - only show what's needed
|
|
jj log -r 'latest(all(), 50)' -T 'commit_id.short() ++ " " ++ description.first_line()'
|
|
```
|
|
|
|
### Limit Template Complexity
|
|
|
|
```bash
|
|
# Complex template - may be slow
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " "
|
|
++ parents.map(|c|
|
|
c.commit_id().short()
|
|
++ " by "
|
|
++ c.author().name()
|
|
).join(", ")
|
|
++ " "
|
|
++ description
|
|
'
|
|
|
|
# Simpler alternative
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " (parents: " ++ parents.map(|c| c.commit_id().short()).join(", ") ++ ")"
|
|
++ " "
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Build Templates Incrementally
|
|
|
|
```bash
|
|
# Start simple
|
|
jj log -T 'commit_id.short()'
|
|
|
|
# Add author
|
|
jj log -T 'commit_id.short() ++ " " ++ author.name()'
|
|
|
|
# Add description
|
|
jj log -T 'commit_id.short() ++ " " ++ author.name() ++ " " ++ description.first_line()'
|
|
|
|
# Add formatting
|
|
jj log -T '
|
|
commit_id.short()
|
|
++ " "
|
|
++ truncate_end(20, author.name())
|
|
++ " "
|
|
++ description.first_line()
|
|
'
|
|
```
|
|
|
|
### 2. Use Template Aliases
|
|
|
|
Define commonly-used patterns once:
|
|
|
|
```toml
|
|
[template-aliases]
|
|
my_log = '''
|
|
if(current_working_copy, "→ ", " ")
|
|
++ commit_id.short()
|
|
++ " "
|
|
++ author.username()
|
|
++ " "
|
|
++ author.timestamp().ago()
|
|
++ " "
|
|
++ description.first_line()
|
|
'''
|
|
```
|
|
|
|
```bash
|
|
jj log -T my_log
|
|
```
|
|
|
|
### 3. Test Templates on Small Sets
|
|
|
|
```bash
|
|
# Test on single commit
|
|
jj log -r @ -T 'your template here'
|
|
|
|
# Test on small range
|
|
jj log -r 'latest(all(), 5)' -T 'your template here'
|
|
```
|
|
|
|
### 4. Use Proper Quoting
|
|
|
|
```bash
|
|
# Single quotes for templates (avoid shell expansion)
|
|
jj log -T 'commit_id.short() ++ " " ++ description'
|
|
|
|
# Escape quotes inside template strings
|
|
jj log -T '"[" ++ commit_id.short() ++ "]"'
|
|
```
|
|
|
|
### 5. Leverage `separate()` for Clean Output
|
|
|
|
```bash
|
|
# Rather than manual checking for empty values
|
|
jj log -T 'commit_id.short() ++ if(bookmarks.len() > 0, " " ++ bookmarks.map(|b| b.name()).join(","), "")'
|
|
|
|
# Use separate
|
|
jj log -T 'separate(" ", commit_id.short(), bookmarks.map(|b| b.name()).join(","))'
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Template Syntax Errors
|
|
|
|
```bash
|
|
# Error: missing closing quote
|
|
jj log -T 'commit_id.short()
|
|
# Fix: close the quote
|
|
jj log -T 'commit_id.short()'
|
|
|
|
# Error: mismatched parentheses
|
|
jj log -T 'if(conflict, "X"'
|
|
# Fix: close all parentheses
|
|
jj log -T 'if(conflict, "X", "")'
|
|
```
|
|
|
|
### Type Errors
|
|
|
|
```bash
|
|
# Error: trying to call string method on commit
|
|
jj log -T 'parents.short()'
|
|
# Fix: map over list and call method on each
|
|
jj log -T 'parents.map(|c| c.commit_id().short()).join(", ")'
|
|
|
|
# Error: treating string as boolean
|
|
jj log -T 'if(description, "yes", "no")'
|
|
# Fix: check for empty string
|
|
jj log -T 'if(description != "", "yes", "no")'
|
|
```
|
|
|
|
### Empty Output
|
|
|
|
```bash
|
|
# Check if keywords exist
|
|
jj log -r @ -T 'description'
|
|
|
|
# Verify list length
|
|
jj log -r @ -T 'bookmarks.len()'
|
|
|
|
# Use coalesce for defaults
|
|
jj log -T 'coalesce(bookmarks.map(|b| b.name()).join(","), "[no bookmarks]")'
|
|
```
|
|
|
|
## Integration with Commands
|
|
|
|
### Log Output
|
|
|
|
```bash
|
|
jj log -T 'template'
|
|
jj log -r 'revset' -T 'template'
|
|
jj log --no-graph -T 'template' # Without graph
|
|
```
|
|
|
|
### Show Command
|
|
|
|
```bash
|
|
jj show -T 'template'
|
|
```
|
|
|
|
### Operation Log
|
|
|
|
```bash
|
|
jj op log -T 'template'
|
|
```
|
|
|
|
### Scriptable Output
|
|
|
|
```bash
|
|
# Get commit IDs for scripting
|
|
jj log -r 'mine()' -T 'commit_id.hex()' --no-graph
|
|
|
|
# JSON output
|
|
jj log -r @ -T '{"id": ' ++ json(commit_id.short()) ++ '}' --no-graph
|
|
```
|
|
|
|
## Summary
|
|
|
|
Templates provide powerful output customization in jujutsu:
|
|
|
|
- **Keywords**: `commit_id`, `change_id`, `description`, `author`, `committer`, etc.
|
|
- **Types**: CommitId, String, Signature, Timestamp, Boolean, List, RefName, Commit
|
|
- **Operators**: `++` (concat), `&&/||/!` (logical), `==/!=/</>` (comparison), `+/-/*` (arithmetic)
|
|
- **Functions**: `if()`, `coalesce()`, `label()`, `fill()`, `indent()`, `separate()`, etc.
|
|
- **Methods**: `.short()`, `.first_line()`, `.contains()`, `.map()`, `.filter()`, etc.
|
|
- **Aliases**: Define reusable templates in config
|
|
- **Best practices**: Build incrementally, use aliases, test on small sets
|
|
|
|
For commit selection, see revsets.md. For file filtering, see filesets.md.
|
|
|
|
Master templates to create custom, informative output that fits your workflow perfectly.
|