From 16b93a10d3750c2cf0deb822bebc04f58674c4e9 Mon Sep 17 00:00:00 2001 From: Michal Trybus Date: Wed, 10 Dec 2025 20:23:31 +0100 Subject: [PATCH 1/2] feat(gitbrowse): use upstream branch name when available. See #2613 When branch is not overridden via options, the local name of the current branch is transformed into the name of the remote branch that corresponds to the local branch via (transitive) upstream relation (as established via git branch --set-upstream-to). When branch is overridden, its upstream branch name is not looked up, as we are assuming that a user-specified branch name is in the context of the remote. --- lua/snacks/gitbrowse.lua | 47 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/lua/snacks/gitbrowse.lua b/lua/snacks/gitbrowse.lua index 22bc1e57c..381dfc2f8 100644 --- a/lua/snacks/gitbrowse.lua +++ b/lua/snacks/gitbrowse.lua @@ -146,6 +146,33 @@ function M.open(opts) end end +---@param cwd string +---@param local_branch string +---@return string? remote +---@return string? remote_branch +local function get_upstream_remote_and_branch(cwd, local_branch) + local remote = "." + local branch = "refs/heads/" .. local_branch + while remote == "." do + local descs = system({ "git", "-C", cwd, + "for-each-ref", "--format=%(upstream:remotename) %(upstream:remoteref)", branch, + }, "Failed to find upstream branch") + if #descs == 0 or descs[1] == "" then + return nil, nil + end + descs = vim.split(descs[1], " ") + remote, branch = descs[1], descs[2] + end + local remote_branch, _ = branch:gsub("^refs/heads/", "", 1) + return remote, remote_branch +end + +---@param cwd string +---@return string +local function get_current_branch(cwd) + return system({ "git", "-C", cwd, "rev-parse", "--abbrev-ref", "HEAD" }, "Failed to get current branch")[1] +end + ---@param opts? snacks.gitbrowse.Config function M._open(opts) opts = Snacks.config.get("gitbrowse", defaults, opts) @@ -153,10 +180,12 @@ function M._open(opts) file = file and (uv.fs_stat(file) or {}).type == "file" and svim.fs.normalize(file) or nil local cwd = file and vim.fn.fnamemodify(file, ":h") or vim.fn.getcwd() + local local_branch = get_current_branch(cwd) + local upstream, remote_branch = get_upstream_remote_and_branch(cwd, local_branch) + ---@type snacks.gitbrowse.Fields local fields = { - branch = opts.branch - or system({ "git", "-C", cwd, "rev-parse", "--abbrev-ref", "HEAD" }, "Failed to get current branch")[1], + branch = opts.branch or local_branch, file = file and system({ "git", "-C", cwd, "ls-files", "--full-name", file }, "Failed to get git file path")[1], line_start = opts.line_start, line_end = opts.line_end, @@ -210,13 +239,27 @@ function M._open(opts) if name and remote then local repo = M.get_repo(remote, opts) if repo then + local old_fields_branch = fields.branch + if opts.branch == nil and name == upstream then + fields.branch = remote_branch or fields.branch + end table.insert(remotes, { name = name, url = M.get_url(repo, fields, opts), }) + fields.branch = old_fields_branch end end end + table.sort(remotes, function(a, b) + if a.name == upstream then + return true + elseif b.name == upstream then + return false + else + return a.name < b.name + end + end) local function open(remote) if remote then From 0ae1034ff0d41ce2b9d77a5334dae52bf9d16f62 Mon Sep 17 00:00:00 2001 From: Michal Trybus Date: Wed, 10 Dec 2025 21:41:15 +0100 Subject: [PATCH 2/2] feat(gitbrowse): add option only_upstream_remote. Closes #2613 Setting this to true limits the remotes list to the single element when branch is not overridden via options and its upstream remote is found, effectively bypassing vim.ui.select. --- doc/snacks.nvim-gitbrowse.txt | 2 ++ docs/gitbrowse.md | 2 ++ lua/snacks/gitbrowse.lua | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/snacks.nvim-gitbrowse.txt b/doc/snacks.nvim-gitbrowse.txt index ddbe6935a..a0df29b10 100644 --- a/doc/snacks.nvim-gitbrowse.txt +++ b/doc/snacks.nvim-gitbrowse.txt @@ -55,6 +55,8 @@ Open the repo of the active file in the browser (e.g., GitHub) branch = nil, ---@type string? line_start = nil, ---@type number? line_end = nil, ---@type number? + ---@type boolean + only_upstream_remote = false, -- if branch = nil and upstream of the current branch is found, do not consider other remotes -- patterns to transform remotes to an actual URL remote_patterns = { { "^(https?://.*)%.git$" , "%1" }, diff --git a/docs/gitbrowse.md b/docs/gitbrowse.md index 8e176326e..412df50e8 100644 --- a/docs/gitbrowse.md +++ b/docs/gitbrowse.md @@ -43,6 +43,8 @@ Open the repo of the active file in the browser (e.g., GitHub) branch = nil, ---@type string? line_start = nil, ---@type number? line_end = nil, ---@type number? + ---@type boolean + only_upstream_remote = false, -- if branch = nil and upstream of the current branch is found, do not consider other remotes -- patterns to transform remotes to an actual URL remote_patterns = { { "^(https?://.*)%.git$" , "%1" }, diff --git a/lua/snacks/gitbrowse.lua b/lua/snacks/gitbrowse.lua index 381dfc2f8..8023eef95 100644 --- a/lua/snacks/gitbrowse.lua +++ b/lua/snacks/gitbrowse.lua @@ -31,6 +31,8 @@ local defaults = { branch = nil, ---@type string? line_start = nil, ---@type number? line_end = nil, ---@type number? + ---@type boolean + only_upstream_remote = false, -- if branch = nil and upstream of the current branch is found, do not consider other remotes -- patterns to transform remotes to an actual URL -- stylua: ignore remote_patterns = { @@ -236,7 +238,8 @@ function M._open(opts) 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 skip = opts.only_upstream_remote and opts.branch == nil and upstream ~= nil and name ~= upstream + if name and remote and not skip then local repo = M.get_repo(remote, opts) if repo then local old_fields_branch = fields.branch