Skip to content

Commit

Permalink
feat(gitbrowse): choose to open repo, branch or file. Closes #10. Closes
Browse files Browse the repository at this point in the history
  • Loading branch information
folke committed Nov 8, 2024
1 parent 42e3a25 commit 92da87c
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 20 deletions.
76 changes: 57 additions & 19 deletions lua/snacks/gitbrowse.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand All @@ -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.open(opts)
function M.get_url(repo, 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" })
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
local lines = vim.split(proc.stdout, "\n")
return repo
end

proc = vim.system({ "git", "rev-parse", "--abbrev-ref", "HEAD" }):wait()
---@param cmd string[]
---@param err string
local function system(cmd, err)
local proc = vim.system(cmd, { text = true }):wait()
if proc.code ~= 0 then
return Snacks.notify.error("Failed to get current branch", { title = "Git Browse" })
Snacks.notify.error({ err, proc.stderr, proc.stdout }, { title = "Git Browse" })
error(err)
end
local branch = proc.stdout:gsub("\n", "")
return vim.split(vim.trim(proc.stdout), "\n")
end

---@param opts? snacks.gitbrowse.Config
function M.open(opts)
pcall(M._open, opts) -- errors are handled with notifications
end

---@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
Expand Down
2 changes: 1 addition & 1 deletion tests/gitbrowse_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 92da87c

Please sign in to comment.