feat(gitbrowse): choose to open repo, branch or file. Closes #10. Closes #17

This commit is contained in:
Folke Lemaitre 2024-11-08 10:39:55 +01:00
parent 42e3a25d95
commit 92da87c910
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
2 changed files with 61 additions and 23 deletions

View file

@ -18,9 +18,11 @@ local defaults = {
end
vim.ui.open(url)
end,
---@type "repo" | "branch" | "file"
what = "file", -- what to open. not all remotes support all types
-- patterns to transform remotes to an actual URL
-- stylua: ignore
patterns = {
remote_patterns = {
{ "^(https?://.*)%.git$" , "%1" },
{ "^git@(.+):(.+)%.git$" , "https://%1/%2" },
{ "^git@(.+):(.+)$" , "https://%1/%2" },
@ -34,48 +36,84 @@ local defaults = {
{ ":%d+" , "" },
{ "%.git$" , "" },
},
url_patterns = {
["github.com"] = {
branch = "/tree/{branch}",
file = "/blob/{branch}/{file}#L{line}",
},
["gitlab.com"] = {
branch = "/-/tree/{branch}",
file = "/-/blob/{branch}/{file}#L{line}",
},
},
}
---@private
---@param remote string
---@param opts? snacks.gitbrowse.Config
function M.get_url(remote, opts)
function M.get_repo(remote, opts)
opts = Snacks.config.get("gitbrowse", defaults, opts)
local ret = remote
for _, pattern in ipairs(opts.patterns) do
ret = ret:gsub(pattern[1], pattern[2])
for _, pattern in ipairs(opts.remote_patterns) do
ret = ret:gsub(pattern[1], pattern[2]) --[[@as string]]
end
return ret:find("https://") == 1 and ret or ("https://%s"):format(ret)
end
---@param repo string
---@param opts? snacks.gitbrowse.Config
function M.get_url(repo, opts)
opts = Snacks.config.get("gitbrowse", defaults, opts)
for remote, patterns in pairs(opts.url_patterns) do
if repo:find(remote) then
return patterns[opts.what] and (repo .. patterns[opts.what]) or repo
end
end
return repo
end
---@param cmd string[]
---@param err string
local function system(cmd, err)
local proc = vim.system(cmd, { text = true }):wait()
if proc.code ~= 0 then
Snacks.notify.error({ err, proc.stderr, proc.stdout }, { title = "Git Browse" })
error(err)
end
return vim.split(vim.trim(proc.stdout), "\n")
end
---@param opts? snacks.gitbrowse.Config
function M.open(opts)
opts = Snacks.config.get("gitbrowse", defaults, opts)
local proc = vim.system({ "git", "remote", "-v" }, { text = true }):wait()
if proc.code ~= 0 then
return Snacks.notify.error("Failed to get git remotes", { title = "Git Browse" })
end
local lines = vim.split(proc.stdout, "\n")
pcall(M._open, opts) -- errors are handled with notifications
end
proc = vim.system({ "git", "rev-parse", "--abbrev-ref", "HEAD" }):wait()
if proc.code ~= 0 then
return Snacks.notify.error("Failed to get current branch", { title = "Git Browse" })
end
local branch = proc.stdout:gsub("\n", "")
---@param opts? snacks.gitbrowse.Config
function M._open(opts)
opts = Snacks.config.get("gitbrowse", defaults, opts)
local file = vim.api.nvim_buf_get_name(0) ---@type string?
file = file and (vim.uv.fs_stat(file) or {}).type == "file" and vim.fs.normalize(file) or nil
local cwd = file and vim.fn.fnamemodify(file, ":h") or vim.fn.getcwd()
local fields = {
branch = system({ "git", "-C", cwd, "rev-parse", "--abbrev-ref", "HEAD" }, "Failed to get current branch")[1],
file = file and system({ "git", "-C", cwd, "ls-files", "--full-name", file }, "Failed to get git file path")[1],
line = file and vim.fn.line("."),
}
opts.what = opts.what == "file" and not fields.file and "branch" or opts.what
opts.what = opts.what == "branch" and not fields.branch and "repo" or opts.what
local remotes = {} ---@type {name:string, url:string}[]
for _, line in ipairs(lines) do
for _, line in ipairs(system({ "git", "-C", cwd, "remote", "-v" }, "Failed to get git remotes")) do
local name, remote = line:match("(%S+)%s+(%S+)%s+%(fetch%)")
if name and remote then
local url = M.get_url(remote, opts)
if url:find("github") and branch and branch ~= "master" and branch ~= "main" then
url = ("%s/tree/%s"):format(url, branch)
end
if url then
local repo = M.get_repo(remote, opts)
if repo then
table.insert(remotes, {
name = name,
url = url,
url = M.get_url(repo, opts):gsub("(%b{})", function(key)
return fields[key:sub(2, -2)] or key
end),
})
end
end

View file

@ -35,7 +35,7 @@ local git_remotes_cases = {
describe("util.lazygit", function()
for remote, expected in pairs(git_remotes_cases) do
it("should parse git remote " .. remote, function()
local url = gitbrowse.get_url(remote)
local url = gitbrowse.get_repo(remote)
assert.are.equal(expected, url)
end)
end