Upgrade to Pro — share decks privately, control downloads, hide ads and more …

stupid jj tricks

Avatar for André Arko André Arko
September 28, 2025

stupid jj tricks

Given at JJ Con 2025. A survey of interesting revset aliases, templates, aliases, and other configuration to accomplish common (or not so common) tasks.

Avatar for André Arko

André Arko

September 28, 2025
Tweet

More Decks by André Arko

Other Decks in Technology

Transcript

  1. André Arko | jjcon | September 28, 2025 stupid jj

    tricks revset aliases, templates, aliases, and other con fi guration
  2. André Arko | jjcon | September 28, 2025 stupid jj

    tricks revset aliases, templates, aliases, and other con fi guration
  3. con fi guration scopes use work email in all work

    repos [[--scope]] --when.repositories = ["~/work"] [--scope.user] email = "[email protected]"
  4. con fi gure di ffi ng generate di ff s

    with di ff tastic [ui] diff-formatter = ["difft", “--color=always", “$left", "$right"]
  5. con fi gure di ffi ng generate di ff s

    with delta [[--scope]] --when.commands = ["diff", "show"] [--scope.ui] pager = "delta" diff-formatter = ":git"
  6. con fi gure di ff editing editor for jj split

    or jj squash -i [ui] diff-editor = "meld" # or vimdiff, vscode, etc
  7. con fi gure con fl ict editor editor for jj

    resolve [ui] merge-editor = "meld" # or vimdiff, vscode, mergiraf
  8. con fi gure con fl ict editor editor for jj

    resolve [merge-tools.filemerge] program = "open" edit-args = ["-a", "FileMerge", "-n", "-W", "--args", "-left", "$left", "-right", "$right", "-merge", "$output"] merge-args = ["-a", "FileMerge", "-n", "-W", "--args", "-left", "$left", "-right", "$right", "-ancestor", "$base", "-merge", "$output"]
  9. con fi gure default log revset used by jj log

    [revsets] log = "(trunk()..@):: | (trunk()..@)-"
  10. templates try the builtins fi rst $ jj log -T

    | rg builtin_log - builtin_log_comfortable - builtin_log_compact - builtin_log_compact_full_description - builtin_log_detailed - builtin_log_oneline
  11. template draft commit description pre fi lled in your editor

    on commit [templates] draft_commit_description = ''' concat( coalesce(description, default_commit_description, "\n"), surround( "\nJJ: This commit contains the following changes:\n", "", indent("JJ: ", diff.stat(72)), ), "\nJJ: ignore-rest\n", diff.git(), ) '''
  12. template fragments format_timestamp() [template-aliases] "format_timestamp(timestamp)" = "timestamp.utc()" # 2025-02-17 21:23:47.000

    +00:00 "format_timestamp(timestamp)" = "timestamp" # 2025-02-13 01:53:08.000 -08:00 "format_timestamp(timestamp)" = "timestamp.ago()" # 7 months ago
  13. template fragments log_node [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()")'
  14. template tricks valid json output log_json_stream = ''' "{" ++

    "change_id".escape_json() ++ ": " ++ stringify(change_id).escape_json() ++ ", " ++ "author".escape_json() ++ ": " ++ stringify(author).escape_json() ++ "}\n" '''
  15. revsets whamst 'user(x)' = 'author(x) | committer(x)' 'mine()' = 'user("[email protected]")

    | user("me@domain")' 'wip()' = 'description(glob:"wip:*")' 'private()' = 'description(glob:"private:*")'
  16. revsets stacks 'stack(x, n)' = 'ancestors( reachable(x, mutable() ), n)'

    'stack(x)' = 'stack(x, 2)' 'stack()' = 'stack(@)'
  17. revsets open 'stack(x, n)' = 'ancestors( reachable(x, mutable() ), n)'

    'stack(x)' = 'stack(x, 2)' 'stack()' = 'stack(@)' 'open()' = 'stack(mine() | @, 1)'
  18. revsets open 'stack(x, n)' = 'ancestors( reachable(x, mutable() ), n)'

    'stack(x)' = 'stack(x, 2)' 'stack()' = 'stack(@)' 'open()' = 'stack(mine() | @, 1)' 'ready()' = 'open() ~ descendants( wip() | private() )'
  19. revsets interesting 'uninterested()' = '::remote_bookmarks() | tags()' 'interested()' = 'mine()

    ~ uninterested()' 'open()' = ''' ancestors(interested(), 3) | tracked_remote_bookmarks() | ancestors(@, 3) '''
  20. commands jj absorb --help Move changes from a revision into

    the stack of mutable revisions This command splits changes in the source revision and moves each change to the closest mutable ancestor where the corresponding lines were modified last. If the destination revision cannot be determined unambiguously, the change will be left in the source revision. The source revision will be abandoned if all changes are absorbed into the destination revisions, and if the source revision has no description. The modification made by `jj absorb` can be reviewed by `jj op show -p`.
  21. commands jj parallelize --help Parallelize revisions by making them siblings

    Running `jj parallelize 1::2` will transform the history like this: 3 | 3 2 / \ | -> 1 2 1 \ / | 0 0 The command effectively says "these revisions are actually independent", meaning that they should no longer be ancestors/descendants of each other. However, revisions outside the set that were previously ancestors of a revision in the set will remain ancestors of it. For example, revision 0 above remains an ancestor of both 1 and 2. Similarly, revisions outside the set that were previously descendants of a revision in the set will remain descendants of it. For example, revision 3 above remains a descendant of both 1 and 2.
  22. aliases jj tug [revset-aliases] 'closest_pushable(to)' = 'heads(::to & mutable() &

    ~description(exact:"") & (~empty() | merges()))' [aliases] tug = 'bookmark move --from "heads(::@ & bookmarks())" --to "closest_pushable(@)"'
  23. aliases jj tug $1 tug = ["util", "exec", "--", "sh",

    "-c", """ if [ "x$1" = "x" ]; then jj bookmark move --from "closest_bookmark(@)" --to "closest_pushable(@)" else jj bookmark move --to "closest_pushable(@)" "$@" fi """, ""]
  24. aliases jj pr pr = ["util", "exec", "--", "bash", "-c",

    """ gh pr create --head $( jj log -r 'closest_bookmark(@)' -T 'bookmarks' --no-graph | cut -d ' ' -f 1 ) """]
  25. aliases jj init init = ["util", "exec", "--", "bash", "-c",

    """ jj git init --colocate # only track origin branches, no upstream or other jj bookmark track 'glob:*@origin' """]
  26. aliases jj pull pull = ["util", "exec", "--", "bash", "-c",

    """ closest="$(jj log -r 'closest_bookmark(@)' -n 1 -T 'bookmarks' --no-graph | cut -d ' ' -f 1)" closest="${closest%\\*}" jj git fetch jj log -n 1 -r "${closest}" 2>&1 > /dev/null && jj rebase -d "${closest}" || jj rebase -d 'trunk()' jj log -r 'stack()' """]
  27. aliases jj push push = ["util", "exec", "--", "bash", "-c",

    """ tuggable="$(jj log -r 'closest_bookmark(@)..closest_pushable(@)' -T '"n"' --no-graph)" [[ -n "$tuggable" ]] && jj tug pushable="$(jj log -r 'remote_bookmarks(remote=origin)..@' -T 'bookmarks' --no-graph)" [[ -n "$pushable" ]] && jj git push || echo "Nothing to push." closest="$(jj log -r 'closest_bookmark(@)' -n 1 -T 'bookmarks' --no-graph | cut -d ' ' -f 1)" closest="${closest%\\*}" tracked="$(jj bookmark list -r ${closest} -t -T 'if(remote == "origin", name)')" [[ "$tracked" == "$closest" ]] || jj bookmark track "${closest}@origin" """]
  28. combo tricks jj ll [revset-aliases] 'recent_work' = 'ancestors(visible_heads(), 3) &

    mutable()' [template-aliases] log_with_files = ''' concat([default jj log template here] ++ "\n", if(self.contained_in("recent_work"), diff.summary()) '''
  29. combo tricks jj z z = ["fuzzy_bookmark"] za = ["bookmark",

    "list", "-a"] fuzzy_bookmark = ["util", "exec", "--", "sh", "-c", """ if [ "x$1" = "x" ]; then jj bookmark list else jj bookmark list -a -T 'separate("@", name, remote) ++ "\n"' 2> /dev/null | sort | uniq | fzf -f "$1" | head -n1 | xargs jj new fi """, ""]
  30. acknowledgments & thanks every jj contributor config posters: @martinvonz, @thoughtpolice,

    @pksunkara, @scott2000, @avamsi, @simonmichael, @sunshowers bugged me into trying jj: @steveklabnik, @endsofthreads thank you so much, to all of you