mirror of
https://github.com/harivansh-afk/forge.nvim.git
synced 2026-04-15 09:01:15 +00:00
354 lines
14 KiB
Markdown
354 lines
14 KiB
Markdown
# forge.nvim
|
|
|
|
**Forge-agnostic git workflow for Neovim**
|
|
|
|
PR, issue, and CI workflows across GitHub, GitLab, and more — without leaving
|
|
your editor.
|
|
|
|
## Features
|
|
|
|
- Automatic forge detection from git remote (GitHub via `gh`, GitLab via `glab`,
|
|
Codeberg/Gitea/Forgejo via `tea`)
|
|
- PR lifecycle: list, create (compose buffer with template discovery, diff stat,
|
|
reviewers), checkout, worktree, review diff, merge, approve, close/reopen,
|
|
draft toggle
|
|
- Issue management: list, browse, close/reopen, state filtering
|
|
- CI/CD: view runs per-branch or repo-wide, stream logs, filter by status
|
|
- Code review via [diffs.nvim](https://github.com/barrettruth/diffs.nvim) with
|
|
unified/split toggle and quickfix navigation
|
|
- Commit and branch browsing with checkout, diff, and URL generation
|
|
- File/line permalink generation and yanking (commit and branch URLs)
|
|
- [fzf-lua](https://github.com/ibhagwan/fzf-lua) pickers with contextual
|
|
keybinds
|
|
- Pluggable source registration for custom or self-hosted forges
|
|
|
|
## Dependencies
|
|
|
|
- Neovim 0.10.0+
|
|
- [fzf-lua](https://github.com/ibhagwan/fzf-lua)
|
|
- At least one forge CLI:
|
|
- [`gh`](https://cli.github.com/) for GitHub
|
|
- [`glab`](https://gitlab.com/gitlab-org/cli) for GitLab
|
|
- [`tea`](https://gitea.com/gitea/tea) for Codeberg/Gitea/Forgejo
|
|
- [vim-fugitive](https://github.com/tpope/vim-fugitive) (optional, for fugitive
|
|
keymaps and split diff)
|
|
- [diffs.nvim](https://github.com/barrettruth/diffs.nvim) (optional, for review
|
|
mode)
|
|
|
|
## Installation
|
|
|
|
### [lazy.nvim](https://github.com/folke/lazy.nvim)
|
|
|
|
```lua
|
|
{
|
|
'barrettruth/forge.nvim',
|
|
dependencies = { 'ibhagwan/fzf-lua' },
|
|
}
|
|
```
|
|
|
|
### [mini.deps](https://github.com/echasnovski/mini.deps)
|
|
|
|
```lua
|
|
MiniDeps.add({
|
|
source = 'barrettruth/forge.nvim',
|
|
depends = { 'ibhagwan/fzf-lua' },
|
|
})
|
|
```
|
|
|
|
### [luarocks](https://luarocks.org/modules/barrettruth/forge.nvim)
|
|
|
|
```
|
|
luarocks install forge.nvim
|
|
```
|
|
|
|
### Manual
|
|
|
|
```sh
|
|
git clone https://github.com/barrettruth/forge.nvim \
|
|
~/.local/share/nvim/site/pack/plugins/start/forge.nvim
|
|
```
|
|
|
|
## Usage
|
|
|
|
forge.nvim works through two entry points: the `:Forge` command and the `<c-g>`
|
|
picker.
|
|
|
|
`:Forge` with no arguments (or `<c-g>`) opens the top-level picker — PRs,
|
|
issues, CI, commits, branches, worktrees, and browse actions. Each sub-picker
|
|
has contextual keybinds shown in the fzf header.
|
|
|
|
PR creation opens a compose buffer (markdown) pre-filled from commit messages
|
|
and repo templates. First line is the title, everything after the blank line is
|
|
the body. Draft, reviewers, and base branch are set in the HTML comment block
|
|
below. Write (`:w`) to push and create.
|
|
|
|
## Configuration
|
|
|
|
Configure via `vim.g.forge`. All fields are optional — defaults shown below.
|
|
|
|
```lua
|
|
vim.g.forge = {
|
|
ci = { lines = 10000 },
|
|
sources = {},
|
|
keys = {
|
|
picker = '<c-g>',
|
|
next_qf = ']q', prev_qf = '[q',
|
|
next_loc = ']l', prev_loc = '[l',
|
|
review_toggle = 's',
|
|
terminal_open = 'gx',
|
|
fugitive = {
|
|
create = 'cpr', create_draft = 'cpd',
|
|
create_fill = 'cpf', create_web = 'cpw',
|
|
},
|
|
},
|
|
picker_keys = {
|
|
pr = {
|
|
checkout = 'default', diff = 'ctrl-d', worktree = 'ctrl-w',
|
|
checks = 'ctrl-t', browse = 'ctrl-x', manage = 'ctrl-e',
|
|
create = 'ctrl-a', toggle = 'ctrl-o', refresh = 'ctrl-r',
|
|
},
|
|
issue = { browse = 'default', close_reopen = 'ctrl-s', toggle = 'ctrl-o', refresh = 'ctrl-r' },
|
|
checks = { log = 'default', browse = 'ctrl-x', failed = 'ctrl-f', passed = 'ctrl-p', running = 'ctrl-n', all = 'ctrl-a' },
|
|
ci = { log = 'default', browse = 'ctrl-x', refresh = 'ctrl-r' },
|
|
commits = { checkout = 'default', diff = 'ctrl-d', browse = 'ctrl-x', yank = 'ctrl-y' },
|
|
branches = { diff = 'ctrl-d', browse = 'ctrl-x' },
|
|
},
|
|
display = {
|
|
icons = { open = '+', merged = 'm', closed = 'x', pass = '*', fail = 'x', pending = '~', skip = '-', unknown = '?' },
|
|
widths = { title = 45, author = 15, name = 35, branch = 25 },
|
|
limits = { pulls = 100, issues = 100, runs = 30 },
|
|
},
|
|
}
|
|
```
|
|
|
|
Set `keys = false` to disable all keymaps. Set `picker_keys = false` to disable
|
|
all picker keybinds. Set any individual key to `false` to disable it.
|
|
|
|
### Examples
|
|
|
|
Disable quickfix/loclist keymaps:
|
|
|
|
```lua
|
|
vim.g.forge = {
|
|
keys = { next_qf = false, prev_qf = false, next_loc = false, prev_loc = false },
|
|
}
|
|
```
|
|
|
|
Nerd font icons:
|
|
|
|
```lua
|
|
vim.g.forge = {
|
|
display = {
|
|
icons = { open = '', merged = '', closed = '', pass = '', fail = '', pending = '', skip = '', unknown = '' },
|
|
},
|
|
}
|
|
```
|
|
|
|
Self-hosted GitLab:
|
|
|
|
```lua
|
|
vim.g.forge = {
|
|
sources = { gitlab = { hosts = { 'gitlab.mycompany.com' } } },
|
|
}
|
|
```
|
|
|
|
Override PR picker bindings:
|
|
|
|
```lua
|
|
vim.g.forge = {
|
|
picker_keys = { pr = { checkout = 'ctrl-o', diff = 'default' } },
|
|
}
|
|
```
|
|
|
|
## Commands
|
|
|
|
`:Forge` with no arguments opens the top-level picker. Subcommands:
|
|
|
|
| Command | Description |
|
|
| --------------------------------------------- | --------------------------------- |
|
|
| `:Forge pr` | List open PRs |
|
|
| `:Forge pr --state={open,closed,all}` | List PRs by state |
|
|
| `:Forge pr create [--draft] [--fill] [--web]` | Create PR |
|
|
| `:Forge pr checkout {num}` | Checkout PR branch |
|
|
| `:Forge pr diff {num}` | Review PR diff |
|
|
| `:Forge pr worktree {num}` | Fetch PR into worktree |
|
|
| `:Forge pr checks {num}` | Show PR checks |
|
|
| `:Forge pr browse {num}` | Open PR in browser |
|
|
| `:Forge pr manage {num}` | Merge/approve/close/draft actions |
|
|
| `:Forge issue` | List all issues |
|
|
| `:Forge issue --state={open,closed,all}` | List issues by state |
|
|
| `:Forge issue browse {num}` | Open issue in browser |
|
|
| `:Forge issue close {num}` | Close issue |
|
|
| `:Forge issue reopen {num}` | Reopen issue |
|
|
| `:Forge ci` | CI runs for current branch |
|
|
| `:Forge ci --all` | CI runs for all branches |
|
|
| `:Forge commit` | Browse commits |
|
|
| `:Forge commit checkout {sha}` | Checkout commit |
|
|
| `:Forge commit diff {sha}` | Review commit diff |
|
|
| `:Forge commit browse {sha}` | Open commit in browser |
|
|
| `:Forge branch` | Browse branches |
|
|
| `:Forge branch diff {name}` | Review branch diff |
|
|
| `:Forge branch browse {name}` | Open branch in browser |
|
|
| `:Forge worktree` | List worktrees |
|
|
| `:Forge browse [--root] [--commit]` | Open file/repo/commit in browser |
|
|
| `:Forge yank [--commit]` | Yank permalink for file/line |
|
|
| `:Forge review end` | End review session |
|
|
| `:Forge review toggle` | Toggle split/unified review |
|
|
| `:Forge cache clear` | Clear all caches |
|
|
|
|
## Keymaps
|
|
|
|
### Global
|
|
|
|
| Key | Mode | Description |
|
|
| ----------- | ---- | -------------------------------- |
|
|
| `<c-g>` | n, v | Open forge picker |
|
|
| `]q` / `[q` | n | Next/prev quickfix entry (wraps) |
|
|
| `]l` / `[l` | n | Next/prev loclist entry (wraps) |
|
|
|
|
### Fugitive buffer
|
|
|
|
Active in `fugitive` filetype buffers when a forge is detected.
|
|
|
|
| Key | Description |
|
|
| ----- | ----------------------------------- |
|
|
| `cpr` | Create PR (compose buffer) |
|
|
| `cpd` | Create draft PR |
|
|
| `cpf` | Create PR from commits (no compose) |
|
|
| `cpw` | Push and open web creation |
|
|
|
|
### Review
|
|
|
|
Active during a review session.
|
|
|
|
| Key | Description |
|
|
| --- | ------------------------- |
|
|
| `s` | Toggle unified/split diff |
|
|
|
|
### Terminal (log buffers)
|
|
|
|
Active on CI/check log terminals when a URL is available.
|
|
|
|
| Key | Description |
|
|
| ---- | ------------------------- |
|
|
| `gx` | Open run/check in browser |
|
|
|
|
## Picker Actions
|
|
|
|
Keybinds shown in the fzf header. `default` = `enter`.
|
|
|
|
| Picker | Key | Action |
|
|
| ------------ | ------------------------------ | ---------------------------------- |
|
|
| **PR** | `enter` | Checkout |
|
|
| | `ctrl-d` | Review diff |
|
|
| | `ctrl-w` | Worktree |
|
|
| | `ctrl-t` | Checks |
|
|
| | `ctrl-x` | Browse |
|
|
| | `ctrl-e` | Manage (merge/approve/close/draft) |
|
|
| | `ctrl-a` | Create new |
|
|
| | `ctrl-o` | Cycle state (open/closed/all) |
|
|
| | `ctrl-r` | Refresh |
|
|
| **Issue** | `enter` | Browse |
|
|
| | `ctrl-s` | Close/reopen |
|
|
| | `ctrl-o` | Cycle state |
|
|
| | `ctrl-r` | Refresh |
|
|
| **Checks** | `enter` | View log (tails if running) |
|
|
| | `ctrl-x` | Browse |
|
|
| | `ctrl-f` / `ctrl-p` / `ctrl-n` | Filter: failed / passed / running |
|
|
| | `ctrl-a` | Show all |
|
|
| **CI** | `enter` | View log (tails if running) |
|
|
| | `ctrl-x` | Browse |
|
|
| | `ctrl-r` | Refresh |
|
|
| **Commits** | `enter` | Checkout (detached) |
|
|
| | `ctrl-d` | Review diff |
|
|
| | `ctrl-x` | Browse |
|
|
| | `ctrl-y` | Yank hash |
|
|
| **Branches** | `ctrl-d` | Review diff |
|
|
| | `ctrl-x` | Browse |
|
|
|
|
## Custom Sources
|
|
|
|
Register a custom forge source for self-hosted or alternative platforms:
|
|
|
|
```lua
|
|
require('forge').register('mygitea', require('my_gitea_source'))
|
|
```
|
|
|
|
Route remotes to your source by host:
|
|
|
|
```lua
|
|
vim.g.forge = {
|
|
sources = { mygitea = { hosts = { 'gitea.internal.dev' } } },
|
|
}
|
|
```
|
|
|
|
A source is a table implementing the `forge.Forge` interface. Required fields:
|
|
`name` (string), `cli` (string, checked via `executable()`), `kinds`
|
|
(`{ issue, pr }`), and `labels` (`{ issue, pr, pr_one, pr_full, ci }`).
|
|
|
|
Required methods (all receive `self`): `list_pr_json_cmd`,
|
|
`list_issue_json_cmd`, `pr_json_fields`, `issue_json_fields`, `view_web`,
|
|
`browse`, `browse_root`, `browse_branch`, `browse_commit`, `checkout_cmd`,
|
|
`yank_branch`, `yank_commit`, `fetch_pr`, `pr_base_cmd`, `pr_for_branch_cmd`,
|
|
`checks_cmd`, `check_log_cmd`, `check_tail_cmd`, `list_runs_json_cmd`,
|
|
`list_runs_cmd`, `normalize_run`, `run_log_cmd`, `run_tail_cmd`, `merge_cmd`,
|
|
`approve_cmd`, `repo_info`, `pr_state`, `close_cmd`, `reopen_cmd`,
|
|
`close_issue_cmd`, `reopen_issue_cmd`, `draft_toggle_cmd`, `create_pr_cmd`,
|
|
`create_pr_web_cmd`, `default_branch_cmd`, `template_paths`.
|
|
|
|
See `lua/forge/github.lua`, `lua/forge/gitlab.lua`, or `lua/forge/codeberg.lua`
|
|
for complete implementations. The `forge.Forge` class definition with full type
|
|
annotations is in `lua/forge/init.lua`.
|
|
|
|
### Skeleton
|
|
|
|
```lua
|
|
local M = {
|
|
name = 'mygitea',
|
|
cli = 'tea',
|
|
kinds = { issue = 'issues', pr = 'pulls' },
|
|
labels = { issue = 'Issues', pr = 'PRs', pr_one = 'PR', pr_full = 'Pull Requests', ci = 'CI/CD' },
|
|
}
|
|
|
|
function M:list_pr_json_cmd(state)
|
|
return { 'tea', 'pr', 'list', '--state', state, '--output', 'json' }
|
|
end
|
|
|
|
function M:pr_json_fields()
|
|
return { number = 'number', title = 'title', branch = 'head', state = 'state', author = 'poster', created_at = 'created_at' }
|
|
end
|
|
|
|
return M
|
|
```
|
|
|
|
## Health
|
|
|
|
Run `:checkhealth forge` to verify your setup. Checks for `git`, forge CLIs
|
|
(`gh`, `glab`, `tea`), required plugins (`fzf-lua`), optional plugins
|
|
(`diffs.nvim`, `vim-fugitive`), and any registered custom sources.
|
|
|
|
## FAQ
|
|
|
|
**Q: How do I create a PR?** `<c-g>` -> Pull Requests -> `ctrl-a` to compose. Or
|
|
from fugitive: `cpr` (compose), `cpd` (draft), `cpf` (instant), `cpw` (web).
|
|
|
|
**Q: Does review mode require diffs.nvim?** Yes. Without
|
|
[diffs.nvim](https://github.com/barrettruth/diffs.nvim), the diff action and
|
|
review toggling are unavailable.
|
|
|
|
**Q: How does forge detection work?** forge.nvim reads the `origin` remote URL
|
|
and matches against known hosts and any custom `sources.<name>.hosts`. The first
|
|
match wins, and the CLI must be in `$PATH`.
|
|
|
|
**Q: Can I use this with self-hosted GitLab/Gitea?** Yes. Add your host to
|
|
`vim.g.forge.sources`. See the [examples](#examples).
|
|
|
|
**Q: What does `ctrl-o` do in pickers?** Cycles the state filter: open -> closed
|
|
-> all -> open.
|
|
|
|
**Q: How do I merge/approve/close a PR?** `ctrl-e` on a PR in the picker opens
|
|
the manage picker. Available actions depend on your repository permissions.
|
|
|
|
**Q: Does this work without a forge remote?** Partially. Commits, branches, and
|
|
worktrees work in any git repo. PRs, issues, CI, and browse require a detected
|
|
forge.
|