# 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) - `tags` - Tags pointing to this commit (List) - `git_refs` - Git refs pointing to this commit (List) - `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) - `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.