/ / | /
___ (___ ___ ___| ___ ___ _ _
| )| | | )|___) \ ) | ) \ )| | | )
|__/ | |__ |__/ |__ \/ | / \/ | | /
__/ -
Open remote git repositories in the comfort of Neovim.
A plugin to open remote Git repositories inside Neovim by managing ephemeral
shallow clones automatically. It aims to provide a similar experience to
GitHub.dev
but directly within Neovim.
git-dev-2024-04-14_13.39.17.webm
- ๐จ Features
- ๐จ Installation
- ๐ Usage
- โ๏ธ Options
- ๐ธ๏ธ URL Parsing
- ๐ Recipes
- ๐ฎ Future Plans / Thoughts
- ๐ License
- Open remote Git repositories inside Neovim at branch, tag or commit.
- Supports most URLs from GitHub, GitLab, Gitea and Codeberg.
- Seamless integration with your workflow (e.g. LSP and tree-sitter).
- Ephemeral repositories - cleanup when Neovim exits.
Use your favorite plugin manager:
{
"moyiz/git-dev.nvim",
event = "VeryLazy",
opts = {},
}
Lazier (documentation will not be available until first use):
{
"moyiz/git-dev.nvim",
lazy = true,
cmd = { "GitDevOpen", "GitDevCleanAll" },
opts = {},
}
See Options.
API: require("git-dev").open(repo, ref, opts)
Command: GitDevOpen
Open the repository in Neovim.
repo
-string
- A partial or full Git URI.ref
-table
- Target reference to checkout (default:nil
). Emptyref
will checkout the default branch. Examples:{ branch = "..." }|{ tag = "..." }|{ commit = "..." }
. If more than one is specified, the priority is:commit
>tag
>branch
.opts
-table
- Override plugin configuration for this call (default:nil
). See Options below.
-- :GitDevOpen moyiz/git-dev.nvim
require("git-dev").open("moyiz/git-dev.nvim")
-- :GitDevOpen derailed/k9s '{ tag = "v0.32.4" }'
require("git-dev").open("derailed/k9s", { tag = "v0.32.4" })
-- :GitDevOpen echasnovski/mini.nvim '{ branch = "stable" }' '{ ephemeral = false }'
require("git-dev").open("echasnovski/mini.nvim", { branch = "stable "}, { ephemeral = false })
-- :GitDevOpen https://git.savannah.gnu.org/git/bash.git '{}' '{ read_only = false }'
require("git-dev").open("https://git.savannah.gnu.org/git/bash.git", {}, { read_only = false })
API: require("git-dev").clean_all()
Command: GitDevCleanAll
Clean all cached local repositories.
Caution: It will delete the repositories directory itself. If you changed the default value, make sure that the new directory is being used only for this purpose.
By either using the lua function require("git-dev").clean_all()
or the command
GitDevCleanAll
.
API: require("git-dev").parse(repo, opts)
Parses a Git URL.
repo
-string
- A partial or full Git URI.opts
-table
- Override plugin configuration for this call (default:nil
). See Options below.
See URL Parsing.
API: require("git-dev").toggle_ui(win_config)
Command: GitDevToggleUI
Manually toggle the window showing git-dev
output. Accepts optional table to
override default window configuration.
win_config
-vim.api.keyset.win_config
- Override window configuration for this call.
M.config = {
-- Whether to delete an opened repository when nvim exits.
-- If `true`, it will create an auto command for opened repositories
-- to delete the local directory when nvim exists.
ephemeral = true,
-- Set buffers of opened repositories to be read-only and unmodifiable.
read_only = true,
-- Whether / how to CD into opened repository.
-- Options: global|tab|window|none
cd_type = "global",
-- The actual `open` behavior.
---@param dir string The path to the local repository.
---@param repo_uri string The URI that was used to clone this repository.
---@param selected_path? string A relative path to a file in this repository.
opener = function(dir, repo_uri, selected_path)
M.ui:print("Opening " .. repo_uri)
local dest =
vim.fn.fnameescape(selected_path and dir .. "/" .. selected_path or dir)
vim.cmd("edit " .. dest)
end,
-- Location of cloned repositories. Should be dedicated for this purpose.
repositories_dir = vim.fn.stdpath "cache" .. "/git-dev",
-- Extend the builtin URL parsers.
-- Should map domains to parse functions. See |parser.lua|.
extra_domain_to_parser = nil,
git = {
-- Name / path of `git` command.
command = "git",
-- Default organization if none is specified.
-- If given repository name does not contain '/' and `default_org` is
-- not `nil` nor empty, it will be prepended to the given name.
default_org = nil,
-- Base URI to use when given repository name is scheme-less.
base_uri_format = "https://github.com/%s.git",
-- Arguments for `git clone`.
-- Triggered when repository does not exist locally.
-- It will clone submodules too, disable it if it is too slow.
clone_args = "--jobs=2 --single-branch --recurse-submodules "
.. "--shallow-submodules --progress",
-- Arguments for `git fetch`.
-- Triggered when repository is already exists locally to refresh the local
-- copy.
fetch_args = "--jobs=2 --no-all --update-shallow -f --prune --no-tags",
-- Arguments for `git checkout`.
-- Triggered when a branch, tag or commit is given.
checkout_args = "-f --recurse-submodules",
},
-- UI configuration
ui = {
-- Auto-close window after repository was opened.
auto_close = true,
-- Delay window closing.
close_after_ms = 3000,
-- Window mode. A workaround to remove `relative`.
-- Options: floating|split
mode = "floating",
-- Window configuration for floating mode.
-- See `:h nvim_open_win`.
---@type win_config
floating_win_config = {
title = "git-dev",
title_pos = "center",
anchor = "NE",
style = "minimal",
border = "rounded",
relative = "editor",
width = 79,
height = 9,
row = 1,
col = vim.o.columns,
},
-- Window configuration for split mode.
-- See `:h nvim_open_win`.
---@type win_config
split_win_config = {
split = "right",
width = 79,
},
},
-- Print command outputs.
verbose = false,
}
It is reasonable to assume that browsing arbitrary Git repositories will probably begin in a web browser. The main purpose of this feature is to allow quicker transition from the currently viewed branch / tag / commit / file to Neovim.
This plugin supports multiple types and flavors of URLs. It will accept most GitHub, GitLab, Gitea and Codeberg URLs, and will try to extract the actual git repository URL, selected branch / tag / commit and selected file.
If such extraction was successful, opener
will be provided with
selected_path
, which is a relative path of a file in the repository. Its main
use-case is to auto-open currently viewed file.
Nested branches (contain slashes) are supported.
Notice that passing explicit ref
to GitDevOpen
will take precedence on
parsed fields.
- GitHub
https://github.com/<repo>
https://github.com/<repo>.git
https://github.com/<repo>/tree/<branch>
https://github.com/<repo>/tree/<tag>
https://github.com/<repo>/blob/<branch>
https://github.com/<repo>/blob/<branch>/<file_path>
https://github.com/<repo>/blob/<tag>
https://github.com/<repo>/blob/<tag>/<file_path>
- GitLab
https://gitlab.com/<repo>
https://gitlab.com/<repo>.git
https://gitlab.com/<repo>/-/tree/<branch>
https://gitlab.com/<repo>/-/tree/<tag>
https://gitlab.com/<repo>/-/blob/<branch>
https://gitlab.com/<repo>/-/blob/<branch>/<file_path>
https://gitlab.com/<repo>/-/blob/<tag>
https://gitlab.com/<repo>/-/blob/<tag>/<file_path>
- Gitea
https://gitea.com/<repo>
https://gitea.com/<repo>.git
https://gitea.com/<repo>/(src|raw)/tag/<tag>
https://gitea.com/<repo>/(src|raw)/tag/<tag>/<file_path>
https://gitea.com/<repo>/(src|raw)/branch/<branch>
https://gitea.com/<repo>/(src|raw)/branch/<branch>/<file_path>
https://gitea.com/<repo>/(src|raw)/commit/<commit_id>
https://gitea.com/<repo>/(src|raw)/commit/<commit_id>/<file_path>
- Codeberg - Same as Gitea.
Open README.md
in main branch:
require("git-dev").open("https://github.com/echasnovski/mini.nvim/blob/main/README.md")
Parser output:
{
branch = "main",
repo_url = "https://github.com/echasnovski/mini.nvim.git",
selected_path = "README.md",
type = "http"
}
Open cmd/scan/main.go
in acook/generic_docker_source_entry
branch:
require("git-dev").open("https://gitlab.com/gitlab-org/code-creation/repository-x-ray/-/blob/acook/generic_docker_source_entry/cmd/scan/main.go")
Parser output:
{
branch = "acook/generic_docker_source_entry",
repo_url = "https://gitlab.com/gitlab-org/code-creation/repository-x-ray.git",
selected_path = "cmd/scan/main.go",
type = "http"
}
See lua/git-dev/parser_spec.lua
for more examples.
(Or: GitDevOpen https://github.com/moyiz/git-dev/blob/master/lua/git-dev/parser_spec.lua
)
Notice this feature is quite experimental. If you encounter any issues or have any questions or requests, feel free to reach out.
The sky is the limit. I have settled for this key binding at the moment (set via
lazy.nvim
):
{
"moyiz/git-dev.nvim",
...
keys = {
{
"<leader>go",
function()
local repo = vim.fn.input "Repository name / URI: "
if repo ~= "" then
require("git-dev").open(repo)
end
end,
desc = "[O]pen a remote git repository",
}
}
...
}
To open with nvim-tree:
opts = {
opener = function(dir, _, selected_path)
-- vim.cmd("Oil " .. vim.fn.fnameescape(dir))
vim.cmd("NvimTreeOpen " .. vim.fn.fnameescape(dir))
if selected_path then
vim.cmd("edit " .. selected_path)
end
end
}
opts = {
opener = function(dir, _, selected_path)
vim.cmd("Neotree " .. dir)
if selected_path then
vim.cmd("edit " .. selected_path)
end
end
}
Recommended. Repositories will be opened in a new tab and its CWD will be set.
opts = {
cd_type = "tab",
opener = function(dir, _, selected_path)
vim.cmd "tabnew"
vim.cmd("Neotree " .. dir)
if selected_path then
vim.cmd("edit " .. selected_path)
end
end
}
It does not make much sense on its own, but a showcase for getting both the repository URL and the local directory.
opts = {
cd_type = "none",
opener = function(_, repo_url)
-- vim.cmd("!librewolf " .. repo_url)
vim.cmd("!firefox " .. repo_url)
end
}
By default, this plugin accepts partial repository URI (e.g. org/repo
) by
applying it onto a format string. This behavior can be customized by setting
git.base_uri_format
to change the URI, or git.default_org
to prepend a
default organization name if the given repository name does not contain /
.
-- Change default URI
opts = {
git = {
base_uri_format = "https://git.home.arpa/%s.git",
}
}
-- Open my own repositories by name with SSH.
-- E.g. "git-dev.nvim" rather than "moyiz/git-dev.nvim"
opts = {
git = {
default_org = "moyiz",
base_uri_format = "[email protected]:%s.git",
}
}
-- Enforce only full URIs (do not accept partial names).
opts = {
git = {
base_uri_format = "%s"
}
}
All repositories in my home Gitea service are private. Cloning such repositories
using HTTP URLs will require inserting user and password. Since my SSH keys are
already set, a custom parser can workaround it by leveraging the domain
parameter of the parser function.
opts = {
extra_domain_to_parser = {
["git.home.arpa"] = function(parser, text, _)
text = text:gsub("https://([^/]+)/(.*)$", "ssh://git@%1:2222/%2")
return parser:parse_gitea_like_url(text, "ssh://[email protected]:2222")
end,
},
}
Notice that my Gitea service listens on port 2222 for SSH. This custom parser
tricks parse_gitea_like_url
by converting a HTTP URL to SSH like URL (which
is not a valid git URI). I.e.
https://git.home.arpa/homelab/k8s/src/commit/ef3fec4973042f0e0357a136d927fe2839350170/apps/gitea/kustomization.yaml
To:
ssh://[email protected]:2222/homelab/k8s/src/commit/ef3fec4973042f0e0357a136d927fe2839350170/apps/gitea/kustomization.yaml
Then, the parser trims the "domain" and proceeds as usual. Output:
{
commit = "ef3fec4973042f0e0357a136d927fe2839350170",
repo_url = "ssh://[email protected]:2222/homelab/k8s.git",
selected_path = "apps/gitea/kustomization.yaml",
type = "http"
}
TBD
- Telescope extension to view, open and manage cloned repositories (will
require
ephemeral = false
). - Open repository in visual selection / current "word".
See License.