{"id":50399015,"url":"https://github.com/mrjones2014/jj-gh","last_synced_at":"2026-06-11T17:00:27.973Z","repository":{"id":359883087,"uuid":"1246162006","full_name":"mrjones2014/jj-gh","owner":"mrjones2014","description":"jj tools for working with GitHub from your terminal.","archived":false,"fork":false,"pushed_at":"2026-06-07T20:27:04.000Z","size":810,"stargazers_count":22,"open_issues_count":8,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-06-07T22:21:25.338Z","etag":null,"topics":["cli","jj","jj-vcs","pull-requests"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrjones2014.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-21T23:54:26.000Z","updated_at":"2026-06-07T20:24:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mrjones2014/jj-gh","commit_stats":null,"previous_names":["mrjones2014/jj-gh"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/mrjones2014/jj-gh","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrjones2014%2Fjj-gh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrjones2014%2Fjj-gh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrjones2014%2Fjj-gh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrjones2014%2Fjj-gh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrjones2014","download_url":"https://codeload.github.com/mrjones2014/jj-gh/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrjones2014%2Fjj-gh/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34208761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","jj","jj-vcs","pull-requests"],"created_at":"2026-05-30T22:02:29.149Z","updated_at":"2026-06-11T17:00:27.955Z","avatar_url":"https://github.com/mrjones2014.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jj-gh\n\n`jj` tools for working with GitHub from your terminal.\n\n- Create PRs locally from your preferred editor, for any arbitrary revision ID\n  - Intelligently supports stacked PRs by choosing the correct base if the revision has an ancestor bookmark for which an open PR exists\n- Enable auto-merge for a PR by its revision ID, without having to know/find its PR number (e.g. `jj pr auto-merge zqxy`)\n- Create local bookmarks for PRs, including across forks (e.g. `jj pr fetch 1234 \u0026\u0026 jj new pr-1234/...`, useful for testing PRs to OSS repos)\n- Show PR metadata like number and CI status in commit graph (e.g. `jj pr log`)\n- Interactively re-stack PRs (update PR base branch based on local revision graph shape)\n\nall from the comfort of your terminal, without touching GitHub's clunky web UI.\nWorks great when combined with the [jj megamerge](https://isaaccorbrey.com/notes/jujutsu-megamerges-for-fun-and-profit) workflow!\n\nSee [DOCS.md](./DOCS.md) for all commands, flags, and features. PRs welcome and encouraged!\n\n| Writing up a PR in Neovim                                                                                  | PR number and GitHub Actions status in commit log graph                                                                   |\n| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |\n| ![writing a PR in Neovim](https://github.com/user-attachments/assets/41efbe41-6b68-4a92-b0c2-776ac129dca8) | ![PR number and CI status in commit log](https://github.com/user-attachments/assets/e52826cf-2924-460c-a608-b2ccd0e7a2a7) |\n\n## Requirements\n\n`jj` must be on `PATH`. `pr fetch` additionally requires a colocated git repo\nand `git` on `PATH` (`jj` cannot yet fetch arbitrary refs like\n`refs/pull/123/head`, so the fetch step shells out to `git`).\n\n## Install\n\n\u003cdetails\u003e\n\n  \u003csummary\u003eWith Nix\u003c/summary\u003e\n\nAdd the flake input:\n\n```nix\n{\n  inputs.jj-gh.url = \"github:mrjones2014/jj-gh\";\n  outputs =\n    {\n      self,\n      nixpkgs,\n      jj-gh,\n      ...\n    }:\n    {\n      # use jj-gh.packages.${system}.default\n    };\n}\n```\n\nYou can either use the overlay directly, or use the `home-manager` module.\n\n```nix\n{ jj-gh, pkgs, ... }:\n{\n  # overlay, not needed if using home-manager module\n  nixpkgs.overlays = [ jj-gh.overlays.default ];\n  home.packages = [ pkgs.jj-gh ];\n\n  # home-manager\n  imports = [ jj-gh.homeManagerModules.default ];\n  programs.jujutsu.gh = {\n    enable = true;\n    # Map of `jj` alias name -\u003e `jj-gh` subcommand. Each entry installs the\n    # alias *and* drops a completion overlay for `jj \u003cname\u003e \u003ctab\u003e` into any\n    # shell home-manager has enabled (fish/bash/zsh).\n    # aliases = { pr = \"pr\"; };\n    settings = {\n      gh_askpass = [\n        \"op\"\n        \"read\"\n        \"op://Private/GitHub/token\"\n      ];\n    };\n  };\n}\n```\n\nNot required, but you may also opt-in to using our Cachix binary cache:\n\nURL: \u003chttps://jj-gh.cachix.org\u003e\n\nPublic Key: `jj-gh.cachix.org-1:N1uFBMDd9znlhDa68BRqLSXYzXXJ2+WHVuwxpGxCtDo=`\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\n  \u003csummary\u003eFrom crates.io\u003c/summary\u003e\n\nRequires a Rust toolchain.\n\n```sh\ncargo install jj-gh\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\n  \u003csummary\u003eFrom source\u003c/summary\u003e\n\nRequires a Rust toolchain. Clone this repository, then from the repo root:\n\n```sh\ncargo install --path .\n```\n\n\u003c/details\u003e\n\n### Setup a `jj` alias\n\nSet up `pr` as a built-in `jj` subcommand so you can write `jj pr create \u003crev\u003e`. If you use the `home-manager` module this is already done for you.\n\n```toml\n# ~/.config/jj/config.toml\n[aliases]\npr = [\"util\", \"exec\", \"--\", \"jj-gh\", \"pr\"]\n```\n\nNow `jj pr create \u003crev\u003e` (and the alias `jj pr c \u003crev\u003e`) and `jj pr fetch \u003cpr-num\u003e` (alias `jj pr f \u003cpr-num\u003e`) work like any other `jj`\nsubcommand.\n\n### Usage\n\n```sh\n# Show `jj log` enriched with PR metadata\njj pr log\n# create a new PR from `rev-id`, supports stacked bookmarks\njj pr create rev-id\n# edit an existing PR who's head ref is `rev-id`\njj pr edit rev-id\n# enable auto-merge for PR who's head ref is `rev-id`;\n# does not work if merge queues are enabled, this is\n# a limitation in the GitHub API, see:\n# https://github.com/mrjones2014/jj-gh/issues/103\njj pr auto-merge rev-id\n```\n\n#### Tips and Tricks\n\nIf you're a Neovim user you can use a plugin like [blink-cmp-git](https://github.com/Kaiser-Yang/blink-cmp-git) or\n[cmp-git](https://github.com/petertriho/cmp-git) (for `nvim-cmp`) to get completions for GitHub issues and collaborators (for reviewers).\n\nIf you want to customize settings in your editor based on it being part of a `jj-gh` flow, you can use the `env` command\nas part of your editor command configuration:\n\n```toml\n[jj-gh]\neditor = [\n  \"env\",\n  \"JJ_GH=1\",\n  \"nvim\",\n  \"+10\",     # skip cursor past frontmatter\n]\n```\n\nthen check, in Neovim for example, `vim.env.JJ_GH` in Lua to make customizations specific to when you're editing PRs with `jj-gh`.\n\n### Shell completions\n\n`jj-gh completions \u003cshell\u003e` prints a standard completion script for the `jj-gh` binary. For the more common case where you\ninvoke `jj-gh` through a `jj` alias (e.g. `jj pr \u003ctab\u003e`), pass `--jj-alias \u003cNAME\u003e --subcommand \u003cNAME\u003e` (both required together)\nto emit an overlay that adds completions for the alias on top of jj's own completion script.\n\n```sh\n# fish\njj util completion fish | source\njj-gh completions fish --jj-alias pr --subcommand pr | source\n\n# bash\neval \"$(jj util completion bash)\"\neval \"$(jj-gh completions bash --jj-alias pr --subcommand pr)\"\n\n# zsh (after compinit)\nsource \u003c(jj util completion zsh)\nsource \u003c(jj-gh completions zsh --jj-alias pr --subcommand pr)\n```\n\nThe overlay must be sourced _after_ `jj util completion \u003cshell\u003e` so it can chain to jj's completer when the alias is not the one being completed.\n\nIf you use the `home-manager` module with `programs.fish.enable` / `programs.bash.enable` / `programs.zsh.enable` set, the matching overlay is\nwired up automatically for every entry in `programs.jujutsu.gh.aliases`. You only need the manual steps above when installing via\n`cargo install` or from source.\n\n## Config\n\nAdd a `[jj-gh]` table to any jj config layer (global `~/.config/jj/config.toml` or repo-local config via `jj config edit --repo`).\nOptions related to PR metadata may also be overridden via the [markdown frontmatter](#frontmatter-format) when your editor opens.\n\n```toml\n[jj-gh]\n# Auth (one source required; see \"Token source precedence\" below for env vars and CLI flag)\ngh_askpass = [\"op\", \"read\", \"op://Personal/github/token\"] # preferred\ngh_token = \"ghp_...\"                                      # plain token, less safe\naskpass_timeout_secs = 20                                 # default 20\n\n# Behavior\ndefault_base_branch = \"main\" # default \"master\"\ndraft = false                # default false\nauto_merge = false           # default false; enable auto-merge on PR after creation\nauto_merge_method = \"merge\"  # default \"merge\"; one of \"merge\", \"squash\", \"rebase\"\n\n# DEPRECATED: this is now auto-detected from the repo, will be removed in a future version\ndefault_remote = \"origin\" # default remote to use\n\nupstream_remote = \"upstream\" # default remote to use for cross-fork PR fetching\n\n# PR body template. `pr_create_template` is a jj template string, evaluated\n# against the revset being PR'd in chronological order. `pr_create_template_file`\n# is a markdown file path. See \"PR body template resolution\" below for the full\n# precedence list and \"Template aliases\" for what's available inside\n# `pr_create_template`.\n# Example: emit each commit's full description, separated by blank lines.\npr_create_template = 'description ++ \"\\n\"'\n# if not set, by default this will look for the following candidates:\n# .github/PULL_REQUEST_TEMPLATE.md\n# .github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md\n# .github/pull_request_template.md\n# .github/PULL_REQUEST_TEMPLATE/pull_request_template.md\npr_create_template_file = \".github/PULL_REQUEST_TEMPLATE.md\"\n\n# Bookmark name template for `pr fetch`. A jj template string, evaluated once\n# against `root()` with `pr_*` aliases pre-populated from the PR's metadata.\n# Default: '\"pr-\" ++ pr_number ++ \"/\" ++ pr_branch'. See \"Template aliases\" for\n# the full list.\npr_fetch_bookmark_template = '\"pr-\" ++ pr_number ++ \"/\" ++ pr_branch'\n\n# default template to use for `jj pr log`, a jj template string.\n# the default template mimics jj's default template with PR metadata\n# added inline.\npr_log_template = 'format_short_commit_header(self) ++ \"#\" ++ surround(\" \", \"\", pr_number)'\n\n# default template to be used in interactive `jj pr restack` UI;\n# by default, it re-uses the `jj pr log` template, but may also be customized\n# separately\npr_restack_template = 'format_short_commit_header(self) ++ \"#\" ++ surround(\" \", \"\", pr_number)'\n\n# Editor command, shell-words split. Falls back to $VISUAL, then $EDITOR.\neditor = [\n  \"nvim\",\n  \"+10\",  # +10 jumps your cursor past the frontmatter\n]\n\n# enable or disable the use of nerdfont icons\n# (e.g. in the `pr log` default template)\n# NOTE: if you have issues with nerdfont icons, its most likely your `$PAGER`,\n# you can fix it by either using something like `bat` (https://github.com/sharkdp/bat)\n# as your pager, or setting\n# ~/.config/jj/config.toml\n# [ui]\n# pager = { command = [\"less\", \"-FRX\"], env = { LESSCHARSET = \"utf-8\", LESSUTFCHARDEF = \"E000-F8FF:p,F0000-FFFFD:p,100000-10FFFD:p\" } }\nnerdfonts = true\n```\n\nConfig precedence (high to low):\n\n1. CLI flags\n1. env (`GH_ASKPASS`, `JJ_GH_TEMPLATE`, `JJ_GH_TEMPLATE_FILE`)\n1. `$JJ_GH_EXTRA_CONFIG` file\n1. `jj` repo-local config file\n1. `jj` global config file\n1. built-in defaults\n\n`JJ_GH_TEMPLATE` maps to `pr_create_template` (jj template string).\n`JJ_GH_TEMPLATE_FILE` maps to `pr_create_template_file` (path to a markdown\ntemplate).\n\nToken source precedence (high to low):\n\n1. `--gh-askpass` CLI flag\n1. `gh_askpass` from merged config\n1. `$JJ_GH_TOKEN` environment variable\n1. `$GH_TOKEN` environment variable (matches the `gh` CLI convention)\n1. `gh_token` from merged config (plain text, less safe)\n1. Attempting to run `gh auth token`\n\nEnv vars override `gh_token` from config, but a configured `gh_askpass` still\nwins. Use `$JJ_GH_TOKEN` when you need a different token for `jj-gh` than for\nthe `gh` CLI itself. You may also run `gh auth login` before running `jj-gh`\nto use the GitHub CLI's authentication.\n\n## Template aliases\n\n`jj-gh` renders three different template surfaces through jj's template engine,\neach with its own set of injected aliases. Aliases are pre-quoted strings, so\nuse them directly without wrapping in `\"...\"`.\n\n### `pr create` body (`-T` / `pr_create_template`)\n\nEvaluated against the revset being PR'd, in chronological order (`--reversed`),\nso a multi-commit stack renders bottom-up. All standard jj template builtins\nwork (`description`, `commit_id`, `author`, etc.). Injected aliases:\n\n- `pr_title`: default title (first-line description of the oldest commit on\n  the stack).\n- `pr_base`: resolved base branch; owner-qualified (`owner:branch`) for\n  cross-fork PRs.\n- `pr_head_branch`: existing local bookmark on the rev, or empty if the rev\n  is unpushed.\n- `pr_oldest_rev_id`: 40-char hex commit SHA of the oldest commit in the\n  revset. Because the template runs once per commit, static content like a\n  fixed PR header would otherwise be duplicated N times for an N-commit\n  stack. Comparing `commit_id.short(40) == pr_oldest_rev_id` lets the\n  template emit such content exactly once, at the bottom-most commit (which\n  lands at the top of the output thanks to `--reversed`). Example:\n\n```jjtemplate\nif(commit_id.short(40) == pr_oldest_rev_id,\n  \"Fixes \\n\\n\",\n  \"\"\n) ++ \"- `\" ++ description.first_line() ++ \"`\\n\"\n```\n\nThe rendered output seeds the buffer your editor opens; you can still edit\nthe body and frontmatter before the PR is submitted.\n\n### `pr fetch` bookmark name (`-T` / `pr_fetch_bookmark_template`)\n\nEvaluated once against `root()` (no commit context). Injected aliases:\n\n- `pr_number`: PR number as a decimal string.\n- `pr_title`: PR title.\n- `pr_branch`: head ref name (the source branch on the PR's fork).\n- `pr_url`: PR's `html_url`.\n- `pr_head_sha`: 40-char hex commit SHA of the PR's head.\n- `pr_head_user`: PR's head fork owner login, or empty if the fork was\n  deleted.\n- `pr_head_repo`: PR's head fork repository name, or empty if the fork was\n  deleted.\n- `pr_slug`: sanitized lowercase ASCII slug of the title (max 50 chars),\n  suitable for embedding in a bookmark name.\n\n### `pr log` (`-T` forwarded to `jj log`)\n\nPer-commit aliases, each keyed on `commit_id` and empty for commits without\na matching open PR:\n\n- `pr_number`: PR number as a string.\n- `pr_url`: PR URL.\n- `pr_ci_status`: `SUCCESS`, `FAILED`, or `PENDING`.\n- `pr_merge_status`: merged / in-merge-queue / auto-merge label.\n- `pr_meta`: pre-formatted hyperlinked PR number, colored CI icon, and merge\n  status.\n\n### `pr restack`\n\nSame aliases available as `pr log`. By default, `pr restack` uses the same template as `pr log`.\n\n## PR body template resolution\n\n`pr create` picks the body template from the following sources, highest first:\n\n1. `--no-template` flag (skip templating entirely).\n2. `-T` / `--template` CLI (jj template string).\n3. `--template-file` CLI (path).\n4. Repo-layer `pr_create_template` (jj template string from repo, workspace,\n   or `$JJ_GH_EXTRA_CONFIG` config).\n5. Repo-layer `pr_create_template_file` (path).\n6. Auto-detected `.github/PULL_REQUEST_TEMPLATE.md` (case variants included).\n7. User-layer `pr_create_template` (from your global jj config).\n8. User-layer `pr_create_template_file` (path from your global jj config).\n\nThe split between repo and user layers lets you set a global default jj\ntemplate while still picking up per-repo `.github/PULL_REQUEST_TEMPLATE.md`\nfiles when contributing to OSS.\n\n## Frontmatter format\n\n```yaml\ntitle: \"\" # required, non-empty\nbase: \"main\" # required; pre-filled with resolved base branch, or \"owner:main\" for cross-fork PRs\nlabels: [] # list of strings, applied via a follow-up API call after creation\ndraft: false # bool\nauto_merge: false # bool; enable GitHub auto-merge once required checks pass\n# this value is not present by default but may be set here as well\nauto_merge_method: \"merge\" # one of \"merge\", \"squash\", \"rebase\"\n```\n\n## GitHub token permissions\n\nI recommend using either classic or OAuth tokens, as certain functionality is not possible with fine-grained tokens due to certain\ndata behing behind permissions that do not exist for fine-grained tokens.\n\n**OAuth token:**\n\nLogin with the browser flow, then no further configuration is needed; `jj-gh` will check `gh auth token` for a token source by default.\n\n```sh\ngh auth login\n```\n\n**Classic personal access token:**\n\n- Private repos: `repo` (full control).\n- Public repos only: `public_repo` is sufficient for `pr create` and `pr fetch`.\n\n**Fine-grained personal access token**, with access to the target repositories:\n\nNote that some functionality may not work with fine-grained tokens. Some data in the GitHub API are gated by permissions which\nare not possible to grant to fine-grained tokens. See: [#167](https://github.com/mrjones2014/jj-gh/issues/167) for more information.\n\n| Permission      | Level          | Used by                                                                                         |\n| --------------- | -------------- | ----------------------------------------------------------------------------------------------- |\n| Metadata        | Read           | every API call (always required)                                                                |\n| Commit Statuses | Read           | Used to show GitHub Actions status for PRs in `jj pr log`                                       |\n| Contents        | Read           | `pr create` (resolving the base branch ref), `pr fetch` (fetching `refs/pull/\u003cn\u003e/head` via git) |\n| Pull requests   | Read and write | `pr create` (list + create, enable auto-merge), `pr fetch` (get)                                |\n| Issues          | Read and write | `pr create` when applying labels (GitHub labels go through the Issues API)                      |\n\nIf you don't apply labels, you can drop the Issues permission. PRs are treated as Issues for the purposes of applying labels in GitHub's API.\n\n## Output and logging\n\nAll log output goes to `STDERR`; the final PR URL (or any value the command prints) goes to `STDOUT`. Pipe-friendly:\n\n```sh\nURL=$(jj pr create zxi)\necho \"Opened $URL\"\n```\n\n- TTY on `STDOUT`: default log level is `INFO`.\n- Piped `STDOUT`: default log level drops to `ERROR`, so only failures appear on `STDERR`.\n- Override with `-v` / `-vv`, `-q`, `--log-level \u003clevel\u003e`, or `$JJ_GH_LOG`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrjones2014%2Fjj-gh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrjones2014%2Fjj-gh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrjones2014%2Fjj-gh/lists"}