How do core Jujutsu developers configure Jujutsu? #5812
Replies: 6 comments 3 replies
-
I'm not a core developer, but I do have some configs to share! My full config is here. Command aliasesVariants of these kinds of aliases are fairly common I think. [aliases]
tug = ["bookmark", "move", "--from", "heads(::@- & bookmarks())", "--to", "@-"]
rebase-all = ["rebase", "-s", "all:roots(trunk()..mutable())", "-d", "trunk()"] Diff configI use the [ui]
diff.format = "git"
[colors]
"diff removed token" = { fg = "bright red", bg = "#400000", underline = false }
"diff added token" = { fg = "bright green", bg = "#003000", underline = false } Log nodeI use a [templates]
log_node = '''
if(self && !current_working_copy && !immutable && !conflict && in_branch(self),
"◇",
builtin_log_node
)
'''
[template-aliases]
"in_branch(commit)" = 'commit.contained_in("immutable_heads()..bookmarks()")' Revset aliasesOne thing I missed from Git when I first started using [revset-aliases]
"p(n)" = "p(@, n)"
"p(r, n)" = "roots(r | ancestors(r-, n))" |
Beta Was this translation helpful? Give feedback.
-
Here's my whole config (minus a tiny bit of Google-specific stuff): "$schema" = 'https://jj-vcs.github.io/jj/prerelease/config-schema.json'
[user]
name = "Martin von Zweigbergk"
email = "[email protected]"
[git]
sign-on-push = true
subprocess = true
[signing]
backend = "ssh"
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWIyG/iNA5njl7iEht77DFxHnKLU1axp20t4Zcu6vNd [email protected]"
[colors]
"diff token" = { underline = false }
"diff removed token" = { bg = "#221111" }
"diff added token" = { bg = "#002200" }
[aliases]
jj = []
l = ["log", "-r", "ancestors(reachable(@, mutable()), 2)"]
n = ["new"]
[ui]
default-command = "l"
diff-editor = ":builtin" I think the only thing we could potentially upstream from there is the |
Beta Was this translation helpful? Give feedback.
-
Mine is located at https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6, copied below for completeness: ## ------------------------------------------------------------------------------------------------
# Schema published automatically on the website. This allows TOML language
# servers to autocomplete and show documentation for the entries below.
#
# I use the prerelease version as my builds on my machines are often from trunk.
"$schema" = "https://jj-vcs.github.io/jj/prerelease/config-schema.json"
## ------------------------------------------------------------------------------------------------
## ---- Basic settings
[user]
name = "Austin Seipp"
# email has to be configured per-repository
[ui]
default-command = "log"
diff.tool = "difft"
merge-editor = "vscode"
log-synthetic-elided-nodes = true
graph.style = "square"
pager = { command = ["less", "-FRX"], env = { LESSCHARSET = "utf-8" } }
should-sign-off = true
[merge-tools.difft]
program = "difft"
diff-args = ["--color=always", "$left", "$right"]
[merge-tools.mergiraf]
program = "mergiraf"
merge-args = ["merge", "$base", "$left", "$right", "-o", "$output", "--fast"]
merge-conflict-exit-codes = [1]
conflict-marker-style = "git"
[git]
auto-local-bookmark = false
push-bookmark-prefix = "aseipp/push-"
private-commits = 'blacklist()'
colocate = true
subprocess = true
[snapshot]
# FIXME (upstream): why isn't this on by default?
auto-update-stale = true
[gerrit]
enabled = false
url = 'http://example.com'
## ------------------------------------------------------------------------------------------------
## ---- Revsets & filesets
[revsets]
# By default, show the current stack of work.
log = 'stack(@)'
[revset-aliases]
# Useful on Windows. Technically conflicts with any bookmark/tag named 'at', but
# seems OK...
'at' = '@'
# FIXME (upstream): should this be upstream?
'user(x)' = 'author(x) | committer(x)'
# By default, show the repo trunk, the remote bookmarks, and all remote tags. We
# don't want to change these in most cases, but in some repos it's useful.
'immutable_heads()' = 'present(trunk()) | remote_bookmarks() | tags()'
# Useful to ignore this, in many repos. For repos like `jj` these are
# consistently populated with a bunch of auto-generated commits, so ignoring it
# is often nice.
'gh_pages()' = 'ancestors(remote_bookmarks(exact:"gh-pages"))'
# trunk() by default resolves to the latest 'main'/'master' remote bookmark. May
# require customization for repos like nixpkgs.
'trunk()' = 'latest((present(main) | present(master)) & remote_bookmarks())'
# Private and WIP commits that should never be pushed anywhere. Often part of
# work-in-progress merge stacks.
'wip()' = 'description(glob:"wip:*")'
'private()' = 'description(glob:"private:*")'
'blacklist()' = 'wip() | private()'
# stack(x, n) is the set of mutable commits reachable from 'x', with 'n'
# parents. 'n' is often useful to customize the display and return set for
# certain operations. 'x' can be used to target the set of 'roots' to traverse,
# e.g. @ is the current stack.
'stack()' = 'ancestors(reachable(@, mutable()), 2)'
'stack(x)' = 'ancestors(reachable(x, mutable()), 2)'
'stack(x, n)' = 'ancestors(reachable(x, mutable()), n)'
# The current set of "open" works. It is defined as:
#
# - given the set of commits not in trunk, that are written by me,
# - calculate the given stack() for each of those commits
#
# n = 1, meaning that nothing from `trunk()` is included, so all resulting
# commits are mutable by definition.
'open()' = 'stack(trunk().. & mine(), 1)'
# the set of 'ready()' commits. defined as the set of open commits, but nothing
# that is blacklisted or any of their children.
#
# often used with gerrit, which you can use to submit whole stacks at once:
#
# - jj gerrit send -r 'ready()' --dry-run
'ready()' = 'open() ~ blacklist()::'
## ------------------------------------------------------------------------------------------------
## ---- UX/UI Configuration
[aliases]
# Convenient shorthands.
d = ["diff"]
s = ["show"]
ll = ["log", "-T", "builtin_log_detailed"]
nt = ["new", "trunk()"]
# Get all open stacks of work.
open = ["log", "-r", "open()"]
# Better name, IMO.
credit = ["file", "annotate"]
# Retrunk a series. Typically used as `jj retrunk -s ...`, and notably can be
# used with open:
# - jj retrunk -s 'all:roots(open())'
retrunk = ["rebase", "-d", "trunk()"]
# Retrunk the current stack of work.
reheat = ["rebase", "-d", "trunk()", "-s", "all:roots(trunk()..stack(@))"]
# Take content from any change, and move it into @.
# - jj consume xyz path/to/file`
consume = ["squash", "--into", "@", "--from"]
# Eject content from @ into any other change.
# - jj eject xyz --interactive
eject = ["squash", "--from", "@", "--into"]
[colors]
# Base customizations
"normal change_id" = { bold = true, fg = "magenta" }
"immutable change_id" = { bold = false, fg = "bright cyan" }
# Used by log node template
"node" = { bold = true }
"node elided" = { fg = "bright black" }
"node working_copy" = { fg = "green" }
"node conflict" = { fg = "red" }
"node immutable" = { fg = "bright cyan" }
"node wip" = { fg = "yellow" }
"node normal" = { bold = false }
# Used in other various templates
"text link" = { bold = true, fg = "magenta" }
"text warning" = { bold = true, fg = "red" }
[template-aliases]
# Code to hyperlink something for the terminal.
# FIXME (upstream): should this go upstream?
'hyperlink(url, text)' = '''
concat(
raw_escape_sequence("\e]8;;" ++ url ++ "\e\\"),
label("text link", text),
raw_escape_sequence("\e]8;;\e\\"),
)
'''
# Basic customizations.
'format_short_signature(signature)' = '"<" ++ if(signature.email(), signature.email(), label("text warning", "NO EMAIL")) ++ ">"'
'format_timestamp(ts)' = '"[" ++ ts.ago() ++ "]"'
'render_bookmarks(commit)' = '''
commit.bookmarks().map(|b|
if(b.remote(),
b,
hyperlink(gh_pr_base() ++ "/tree/" ++ b.name(), b),
)
)
'''
# Commit header. This includes code to automatically link to Gerrit code reviews
# for matching commits in a very basic way.
'format_short_commit_header(commit)' = '''separate(" ",
format_short_change_id_with_hidden_and_divergent_info(commit),
format_short_signature(commit.author()),
format_timestamp(commit_timestamp(commit)),
render_bookmarks(commit),
commit.tags(),
commit.working_copies(),
format_short_commit_id(commit.commit_id()),
if(has_ghpr_url(commit.description()),
"[" ++
hyperlink(
get_ghpr_url(commit.description()),
"GH: #1234"
) ++ "]"
),
if(has_change_id(commit.description()),
"[" ++
hyperlink(
config("gerrit.url").as_string() ++ "/q/" ++ gerrit_id(change_id),
"CR: " ++ gerrit_id(change_id, 10)
) ++ "]"
),
if(commit.git_head(), label("git_head", "git_head()")),
if(commit.conflict(), label("conflict", "conflict")),
)'''
'has_ghpr_url(s)' = 's.contains("GH-PR: ")'
'get_ghpr_url(s)' = '''
s.lines().filter(|l| l.starts_with("GH-PR: ")).map(|l| l.remove_prefix("GH-PR: ")).join("")
'''
# 6a6a636c is hex("jjcl").
'has_change_id(s)' = 's.contains("Change-Id: I")'
'gerrit_id(c, n)' = '"I" ++ raw_escape_sequence(c.normal_hex().substr(0, n)) ++ if(n > 31, "6a6a636c")'
'gerrit_id(c)' = 'gerrit_id(c, 31)'
'gh_pr_base()' = '"undefined"'
[templates]
# Improve the default representation in the log. This colors
# and changes the node icon for each.
op_log_node = 'if(current_operation, "@", "◉")'
log_node = '''
label("node",
coalesce(
if(!self, label("elided", "⇋")),
if(current_working_copy, label("working_copy", "◉")),
if(conflict, label("conflict", "x")),
if(immutable, label("immutable", "◆")),
if(description.starts_with("wip: "), label("wip", "!")),
label("normal", "○")
)
)
'''
# Draft commit description. Includes:
# - Change-Id fields for Gerrit, and
# - Signed-off-by lines
draft_commit_description = '''
concat(
description,
"\n",
if(
config("gerrit.enabled").as_boolean() && !has_change_id(description),
"\nChange-Id: I" ++ gerrit_id(change_id),
),
if(
config("ui.should-sign-off").as_boolean() && !description.contains("Signed-off-by: " ++ author.name()),
"\nSigned-off-by: " ++ author.name() ++ " <" ++ author.email() ++ ">",
),
"\n",
surround(
"\nJJ: This commit contains the following changes:\n", "",
indent("JJ: ", diff.summary()),
),
)
''' |
Beta Was this translation helpful? Give feedback.
-
Not a core developer but I also hyperlink stuff, have aliases for retrunking stuff and use a custom log revset (https://github.com/avamsi/dotfiles/blob/2b0d28745bdaca3390b689aaae7f76edda6e955f/.jjconfig.toml): # https://github.com/martinvonz/jj/blob/main/docs/config.md
[user]
name = 'Vamsi Avula'
email = '[email protected]'
[revset-aliases]
archived = 'description(glob:"(jj archive)*")'
local = 'all() ~ ancestors(remote_bookmarks(remote=origin))'
'chain(revisions)' = 'ancestors(revisions) & local'
local_unarchived = 'local ~ archived'
og = 'trunk()'
interesting = 'og | parents(local_unarchived) | local_unarchived'
localstale = 'local_unarchived ~ descendants(og)'
[template-aliases]
'commit_timestamp(commit)' = '''
if(commit.current_working_copy() || commit.hidden(),
commit.committer().timestamp().ago(),
commit.author().timestamp().ago()
)
'''
'format_short_change_id(id)' = 'id.shortest()'
'hyperlink(url, text)' = '''
raw_escape_sequence("\e]8;;" ++ url ++ "\e\\") ++
text ++
raw_escape_sequence("\e]8;;\e\\")
'''
'gerrit_change_id(change_id)' = '"Id0000000" ++ change_id.normal_hex()'
'format_short_change_id_with_gerrit_hyperlink(commit)' = '''
hyperlink(
"https://gerrit/q/" ++
coalesce(
commit.description().lines().map(|line|
if(line.starts_with("Change-Id: "),
line.remove_prefix("Change-Id: ")
)
).join(""),
gerrit_change_id(commit.change_id())
),
format_short_change_id(commit.change_id())
)
'''
'format_short_change_id_with_hidden_and_divergent_info(commit)' = '''
if(commit.hidden(),
label("hidden",
format_short_change_id_with_gerrit_hyperlink(commit) ++
" hidden"
),
label(if(commit.divergent(), "divergent"),
format_short_change_id_with_gerrit_hyperlink(commit) ++
if(commit.divergent(), "?")
)
)
'''
'format_short_commit_id(id)' = 'id.shortest(7)'
'format_short_signature(signature)' = '''
coalesce(signature.email().local(), email_placeholder)'''
'format_timestamp(timestamp)' = 'timestamp'
'oneline(commit)' = '''
separate(commit_summary_separator,
format_short_commit_id(commit.commit_id()),
separate(" ",
if(commit.conflict(), label("conflict", "(conflict)")),
if(commit.empty(), label("empty", "(empty)")),
if(commit.description(),
commit.description().first_line(),
label(if(commit.empty(), "empty"), description_placeholder)
)
),
separate(" ",
if(commit.git_head(), label("git_head", "git_head()")),
commit.bookmarks(),
commit.tags(),
commit.working_copies()
),
format_short_change_id_with_hidden_and_divergent_info(commit)
) ++ "\n"
'''
'unarchived(description)' = """
description.remove_prefix("(jj archive) ").remove_suffix("\n")
"""
[aliases]
ab = ['absorb']
am = ['amend']
bg = [
'--config=ui.log-word-wrap=false',
'--ignore-working-copy',
'--no-pager']
bgc = ['bg', '--color=always']
list = ['log', '--no-graph']
reword = ['describe']
archive = [
'util', 'exec', '--', 'sh', '-c', '''
m=$(
jj bgc list \
--template='"(jj archive) " ++ unarchived(description)' \
--revisions="$*") \
&& jj reword --message="$m" "$*"
''', '']
bookmarks = ['list', '--template=bookmarks ++ "\n"']
changes = ['list', '--template=change_id.short() ++ "\n"']
commits = ['list', '--template=commit_id.short() ++ "\n"']
delta = [
'''--config=ui.pager=[
"delta", "--line-numbers", "--navigate", "--side-by-side"]''',
'diff', '--git']
hide = ['abandon']
hideempty = ['hide', 'empty() & local ~ root()']
rebasestale = [
'rebase',
'--source=all:roots(localstale)', '--destination=og', '--skip-emptied']
revlog = ['evolog']
rollback = ['backout']
unarchive = [
'util', 'exec', '--', 'sh', '-c', '''
m=$(
jj bgc list \
--template='unarchived(description)' --revisions="$*") \
&& jj reword --message="$m" "$*"
''', '']
up = ['new']
whatsout = ['diff', '--summary']
[colors]
'description placeholder' = 'red'
'working_copy description placeholder' = 'default'
working_copy.underline = true
[core]
fsmonitor = 'watchman'
watchman.register_snapshot_trigger = true
[git]
push = 'fork'
push-bookmark-prefix = 'av/jj_'
[merge-tools.difft]
diff-args = ['--color=always', '$left', '$right']
[merge-tools.pycharm]
merge-args = ['merge', '$left', '$right', '$base', '$output']
[ui]
diff-editor = ':builtin'
diff-instructions = false
log-word-wrap = true
merge-editor = 'vscode'
[revsets]
short-prefixes = 'interesting'
[templates]
draft_commit_description = '''
separate("\n",
description.remove_suffix("\n"),
if(!description.contains(gerrit_change_id(change_id)),
"\nChange-Id: " ++ gerrit_change_id(change_id)
),
"\n",
surround("JJ: Changes:\n", "", indent("JJ: \t", diff.summary()))
)
''' |
Beta Was this translation helpful? Give feedback.
-
Just a user, but here's a snapshot of my latest. Since I mostly use git and don't yet use jj regularly, I often find it unfamiliar when I pick it up again. In particular I have found jj's ~/.jjconfig.toml: # SM's jj config. Last updated 2025-05
# https://jj-vcs.github.io/jj/latest/config/
[user]
name = "Simon Michael"
email = "[email protected]"
[snapshot]
auto-track = "none()"
[ui]
default-command = "-h"
diff.tool = ["difft", "--color=always", "$left", "$right"]
#conflict-marker-style = "diff" # default
# conflict-marker-style = "snapshot"
# conflict-marker-style = "git"
#log-synthetic-elided-nodes = false # hides "~ (elided revisions)" lines
# https://jj-vcs.github.io/jj/latest/config/#pager
#paginate = "never"
#pager = ":builtin"
#pager = "less -EFRSWX --mouse --use-color --wheel-lines=4 --shift=2"
# https://github.com/jj-vcs/jj/discussions/4690
# pager = "delta"
# [ui.diff]
# format = "git"
# https://jj-vcs.github.io/jj/latest/config/#default-remotes-for-jj-git-fetch-and-jj-git-push
[colors]
bookmarks = { fg="bright green", bold=true }
tags = "green"
timestamp = "default"
git_head = "blue"
git_refs = "blue"
[merge-tools.difft]
diff-args = ["--color=always", "--display=side-by-side-show-both", "$left", "$right"]
# [merge-tools.ediff]
# program = 'sh'
# merge-args = ['-c',
# 'emacsclient -c --eval "(ediff-merge-files-with-ancestor \"$0\" \"$1\" \"$2\" nil \"$3\")"',
# '$left', '$right', '$base', '$output']
[git]
push-new-bookmarks = true
[aliases]
# Custom jj commands. See also .jjbashrc, where more short/compound aliases are defined.
# Some things to watch out for with jj aliases:
# - Keep them minimal; remain familiar with the builtin commands and defaults.
# - If they use -r, a command line -r will add to that, not override it.
# - If they use other options like -T or -n, those can't be overridden on the command line.
# log aliases. Most of these use -T and -n; the b and custom variants also use -r.
# All of them show graph structure; to hide it add --no-graph (can show confused output).
# "l" aliases: list recent changes on multiple branches, elided, as one line.
# add more l's to show more changes: l=10, ll=30, lll=100, llll=all
l = ["log", "-Tlog1", "-n10"]
ll = ["log", "-Tlog1", "-n30"]
lll = ["log", "-Tlog1", "-n100"]
llll = ["log", "-Tlog1"]
# append v to show full descriptions
lv = ["log", "-Tlogv", "-n10"]
llv = ["log", "-Tlogv", "-n30"]
lllv = ["log", "-Tlogv", "-n100"]
llllv = ["log", "-Tlogv"]
# prepend b to show current branch only, non-elided
bl = ["log", "-Tlog1", "-r::@", "-n10"]
bll = ["log", "-Tlog1", "-r::@", "-n30"]
blll = ["log", "-Tlog1", "-r::@", "-n100"]
bllll = ["log", "-Tlog1", "-r::@"]
blv = ["log", "-Tlogv", "-r::@", "-n10"]
bllv = ["log", "-Tlogv", "-r::@", "-n30"]
blllv = ["log", "-Tlogv", "-r::@", "-n100"]
bllllv = ["log", "-Tlogv", "-r::@"]
# "log1" aliases: list changes as one line, showing committer or author time in original or current time zone.
log1 = ["log", "-Tlog1"]
log1a = ["log", "-Tlog1author"]
log1z = ["log", "-Tlog1current"]
log1az = ["log", "-Tlog1authorcurrent"]
# "log" aliases: other custom change lists, using the default template (builtin_log_compact)
#log # default log view: trunk and unmerged/unpushed branches, as two lines
logconflicts = ["log", "-r", "conflicts()"] # conflicting commits
lognew = ["log", "-r", "(master..@):: | (master..@)-"] # commits since master in current branch
logpush = ["log", "-r", "trunk()::@"] # unpushed to origin in current branch
logpushall = ["log", "-r", "remote_bookmarks()..@-"] # unpushed to all remotes in current branch ?
logpushall2 = ["log", "-r", "(remote_bookmarks()..@)::@"] # unpushed to all remotes in current branch ?
logpull = ["log", "-r", "@..trunk()"] # unpulled from origin in current branch
logpullall = ["log", "-r", "@-..remote_bookmarks()"] # unpulled from all remotes in current branch
# op log
o = ["op", "log"] # all operations
oo = ["op", "log", "-T", "builtin_op_log_comfortable"] # all operations, with more whitespace
#difft = ["diff", "--tool=difft"]
tug = ["bookmark", "move", "--from", "closest_bookmark(@-)", "--to", "@-"]
abandonempties = ["abandon", "-r", "description(exact:'') ~ root()"]
# c = ["commit"]
# ci = ["commit", "--interactive"]
# e = ["edit"]
# i = ["git", "init", "--colocate"]
# nb = ["bookmark", "create", "-r @-"] # "new bookmark"
# pull = ["git", "fetch"]
# push = ["git", "push", "--allow-new"]
# r = ["rebase"]
# s = ["squash"]
# si = ["squash", "--interactive"]
[revsets]
# I believe this section is used only for:
# log - customise what jj log shows when neither -r nor any paths are specified https://jj-vcs.github.io/jj/latest/config/#log
# short-prefixes - customise which changes to show with shorter prefixes
# the default (also named as log_default below): trunk and unmerged/unpushed branches
#log = "present(@) | ancestors(immutable_heads().., 2) | present(trunk())"
# custom:
#log = "recent"
#short-prefixes = "(main..@)::" # the current branch
[revset-aliases]
# Custom revsets.
# https://jj-vcs.github.io/jj/latest/revsets
# https://jj-vcs.github.io/jj/latest/revsets/#examples
# HEAD = '@-'
log_default = "present(@) | ancestors(immutable_heads().., 2) | present(trunk())"
recent = "latest(log_default, 30)" # latest 30 of default log
'person(x)' = 'author(x) | committer(x)' # authored or committed by specified person
# "all commits authored by me that are not merged into main that can be rebased on main"
mymergeable = "all:mutable() & mine()"
'closest_bookmark(to)' = 'heads(::to & bookmarks())'
# https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits
# The default set of immutable heads is builtin_immutable_heads(), which in turn
# is defined as present(trunk()) | tags() | untracked_remote_bookmarks().
#"immutable_heads()" = "builtin_immutable_heads()"
#"immutable_heads()" = "builtin_immutable_heads() | '[0-9]*@origin'"
# set all remote bookmarks (commits pushed to remote branches) to be immutable
#'immutable_heads()' = "builtin_immutable_heads() | remote_bookmarks()"
# https://jj-vcs.github.io/jj/latest/templates/
# https://github.com/jj-vcs/jj/blob/main/cli/src/config/templates.toml
# https://docs.rs/chrono/latest/chrono/format/strftime/
[templates]
# The default output template for each command.
# preserve log's default output, for tools/uis which expect that:
#log = "builtin_log_compact" # two lines, description summary only
# or customise it to my preference:
#log = "log1" # one line
#log = "logv" # one line + full description
[template-aliases]
# Additional templates and helpers.
# log templates. Most of these use my smlog() layout.
# Author's name, committer's timestamp and time zone, full descriptions.
logv = 'smlog(original_time(committer.timestamp()), description, bookmarks, tags)'
# Like logv but just one line of description.
log1 = 'smlog(original_time(committer.timestamp()), description.first_line(), bookmarks, tags)'
# Like log1 but shows committer's timestamp localised to current time zone.
log1current = 'smlog(local_time(committer.timestamp()), description.first_line(), bookmarks, tags)'
# Like log1 but shows author's timestamp and time zone.
log1author = 'smlog(original_time(author.timestamp()), description.first_line(), bookmarks, tags)'
# Like log1a but shows author's timestamp localised to current time zone.
log1authorcurrent = 'smlog(local_time(author.timestamp()), description.first_line(), bookmarks, tags)'
# Like log1 but don't show bookmarks.
log1nobookmarks = 'smlog(original_time(committer.timestamp()), description.first_line(), "", tags)'
# Like log1 but don't show tags.
log1notags = 'smlog(original_time(committer.timestamp()), description.first_line(), bookmarks, "")'
# Two lines per change (default layout)
log2 = "builtin_log_compact"
# Two lines per change (sm layout)
# log2 = "smlog2"
# Three lines per change (default layout)
log3 = 'builtin_log_compact ++ "\n"'
# helpers:
'local_time(timestamp)' = 'timestamp.local().format("%Y-%m-%d %H:%M:%S")'
'localised_time(timestamp)' = 'timestamp.local().format("%Y-%m-%d %H:%M:%S %z")'
'original_time(timestamp)' = 'timestamp.format("%Y-%m-%d %H:%M:%S %z")'
# Generate a log template with a more aligned layout for readability:
# both hashes and other fixed width fields on the left,
# bookmarks and tags displayed optionally (used by jjbookmarks, jjtags),
# bookmarks enclosed in brackets to distinguish them from tags,
# description starting on the same line.
'smlog(timestr, description, bookmarks, tags)' = '''
if(root,
format_root_commit(self),
label(if(current_working_copy, "working_copy"),
concat(
separate(" ",
format_short_change_id_with_hidden_and_divergent_info(self),
format_short_commit_id(commit_id),
timestr,
if(bookmarks,surround("[","]",bookmarks),""),
tags,
working_copies,
if(git_head, label("git_head", "git_head()")),
if(conflict, label("conflict", "conflict")),
if(empty, label("empty", "(empty)")),
if(author.email(), author.email().local(), email_placeholder),
if(description,
description,
label(if(empty, "empty"), description_placeholder),
),
) ++ "\n",
),
)
)
''' ~/.jjbashrc: # SM's jj bash scripts. Last updated 2025-05
# Keep these minimal, when possible use ~/.jjconfig.toml instead.
# Add to .bashrc: if [ -f ~/.jjbashrc ]; then source ~/.jjbashrc; fi
# Short aliases
alias jjd='jj diff -s'
alias jjdd='jj diff --stat'
alias jjddd='jj diff'
#alias jjl='jj l'
alias jjs='jj status'
alias jjz='jj-fzf'
# Compound aliases
# jjtags|bookmarks|bookmarksremote [REGEX] - list commits with one or more tags or local or remote bookmarks [matched by REGEX], most recent first, paged
jjtags() { jj log -Tlog1nobookmarks -r "tags(regex:'$1')" --no-graph --color=always | $PAGER; }
jjbookmarks() { jj log -Tlog1notags -r "bookmarks(regex:'$1')" --no-graph --color=always | $PAGER; }
jjbookmarksremote() { jj log -Tlog1notags -r "remote_bookmarks(regex:'$1')" --no-graph --color=always | $PAGER; }
# jjbr [WEEKS] - list the recently active local branches (with commits in the last week, by default)
jjbr() { jj bookmark list -r "bookmarks() & committer_date(after:'${1:-1} week ago')" --sort committer-date- | rg '^\S([^:]+)' -o; }
# jjlu [WEEKS [TEMPLATE]] - list the unmerged changes, if any, in recently active local branches, using one line by default
jjlu() { for b in `jjbr "${1:-1}"`; do printf "\n$b:\n"; jj log --no-graph -T${2:-log1} -r "trunk()..$b"; done; }
# jjlua [TEMPLATE] - list all my unmerged commits not in a release branch, using one line by default
jjlua() { jj log -T${1:-log1} -r "@ | ancestors(trunk()..(visible_heads() & mine() ~ bookmarks(glob:'1.*')), 2) | trunk()"; }
# jjuntrackadded - untrack all files currently added by the working commit. Useful after accidentally auto-tracking stuff.
jjuntrackadded() { jj file untrack `jj status | rg '^A (.*)' -r '$1'`; }
# less useful:
# jjbrlogr [WEEKS [TEMPLATE]] - list the recently-committed unmerged changes in local branches
#jjbrlogr() { jj log ${2:+"-T $2"} -r "ancestors(trunk()..(bookmarks() & committer_date(after:'${1:-1} week ago')), 2)"; } |
Beta Was this translation helpful? Give feedback.
-
Not a core developer but I want to share an alias I find really useful. # doc: https://jj-vcs.github.io/jj/latest/cli-reference/#jj-util-exec
[aliases]
xif = ["util", "exec", "--", "jj-resolve"] and here is the content of #!/usr/bin/env bash
function resolve() {
local files=()
while IFS= read -r file; do
files+=("$file")
done < <(jj resolve --list --no-pager | awk '{print $1}')
# Check if the array is not empty (i.e., there are conflicted files)
if (( ${#files[@]} )); then
# put conflicted files in neovim args list
nvim --clean "${files[@]}"
fi
}
# Run resolve if the function is invoked as a script
if [[ "${#BASH_SOURCE[@]}" -eq 1 ]]; then
resolve
fi I don't really like how merge tools work either in git or jj. For merge conflicts, I want to open all conflicts inside vim/neovim and cycle through them as quickly as possible in order to fix the conflicts and that is exactly what the script above is doing. Here is the git version, for people who may me interested #!/usr/bin/env bash
function resolve() {
local files=()
# Read NULL-separated filenames from git diff output safely into the array
while IFS= read -r -d '' file; do
files+=("$file")
done < <(git diff --name-only --diff-filter=U -z)
# Check if the array is not empty (i.e., there are conflicted files)
if (( ${#files[@]} )); then
# put conflicted files in neovim args list
nvim --clean "${files[@]}"
fi
}
# Run resolve if the function is invoked as a script
if [[ "${#BASH_SOURCE[@]}" -eq 1 ]]; then
resolve
fi |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi, I saw this blog post and thought it was very cool. Does anyone have cool configs like these to share?
https://blog.gitbutler.com/how-git-core-devs-configure-git/
I don't know if all the features presented in the article are available in jj, but the
colorMoved
option looks very useful.Beta Was this translation helpful? Give feedback.
All reactions