From 1b1eaacc20c54b93b2700bcdec95a4de9128a4de Mon Sep 17 00:00:00 2001 From: ldelossa Date: Fri, 17 Jan 2025 10:05:34 -0500 Subject: [PATCH] fix: support finding pr branch via git config branch keys Fixes #799 When the gh cli tool checks out a branch created from a fork of the origin it places remote tracking info directly in git config at a key derived from branch.{branch_name} prefix. Octo did not consider this when checking if the repository is checked out to the branch pending merge into the origin. Update the `in_pr_branch` function to also check if the current PR has a git configuration which provides details of the upstream fork branch. See the following `man git-config` entries for more details: ``` branch..remote When on branch , it tells git fetch and git push which remote to fetch from/push to. The remote to push to may be overridden with remote.pushDefault (for all branches). The remote to push to, for the current branch, may be further overridden by branch..pushRemote. If no remote is configured, or if you are not on any branch and there is more than one remote defined in the repository, it defaults to origin for fetching and remote.pushDefault for pushing. Additionally, . (a period) is the current local repository (a dot-repository), see branch..merge's final note below. branch..pushRemote When on branch , it overrides branch..remote for pushing. It also overrides remote.pushDefault for pushing from branch . When you pull from one place (e.g. your upstream) and push to another place (e.g. your own publishing repository), you would want to set remote.pushDefault to specify the remote to push to for all branches, and use this option to override it for a specific branch. branch..merge Defines, together with branch..remote, the upstream branch for the given branch. It tells git fetch/git pull/git rebase which branch to merge and can also affect git push (see push.default). When in branch , it tells git fetch the default refspec to be marked for merging in FETCH_HEAD. The value is handled like the remote part of a refspec, and must match a ref which is fetched from the remote given by "branch..remote". The merge information is used by git pull (which at first calls git fetch) to lookup the default branch for merging. Without this option, git pull defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. If you wish to setup git pull so that it merges into from another branch in the local repository, you can point branch..merge to the desired branch, and use the relative path setting . (a period) for branch..remote. ``` Signed-off-by: ldelossa --- lua/octo/utils.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/lua/octo/utils.lua b/lua/octo/utils.lua index a5542aee..c55ca596 100644 --- a/lua/octo/utils.lua +++ b/lua/octo/utils.lua @@ -449,9 +449,14 @@ end --- Determines if we are locally are in a branch matching the pr head ref --- @param pr PullRequest --- @return boolean -function M.in_pr_branch(pr) +function M.in_pr_branch_locally_tracked(pr) local cmd = "git rev-parse --abbrev-ref --symbolic-full-name @{u}" - local local_branch_with_local_remote = vim.split(string.gsub(vim.fn.system(cmd), "%s+", ""), "/") + local cmd_out = vim.fn.system(cmd) + if vim.v.shell_error ~= 0 then + return false + end + + local local_branch_with_local_remote = vim.split(string.gsub(cmd_out, "%s+", ""), "/") local local_remote = local_branch_with_local_remote[1] local local_branch = table.concat(local_branch_with_local_remote, "/", 2) @@ -465,6 +470,62 @@ function M.in_pr_branch(pr) return false end +-- Determines if we are locally in a branch matting the pr head ref when +-- the remote and branch information is stored in the branch's git config values +-- The gh CLI tool stores remote info directly in {branch.{branch}.x} configuration +-- fields and does not create a remote +function M.in_pr_branch_config_tracked(pr) + local branch_cmd = "git rev-parse --abbrev-ref HEAD" + local branch = vim.fn.system(branch_cmd) + if vim.v.shell_error ~= 0 then + return false + end + + if #branch == 0 then + return false + end + + -- trim white space off branch + branch = string.gsub(branch, "%s+", "") + + local merge_config_cmd = string.format('git config --get-regexp "^branch\\.%s\\.merge"', branch) + + local merge_config = vim.fn.system(merge_config_cmd) + if vim.v.shell_error ~= 0 then + return false + end + + if #merge_config == 0 then + return false + end + + -- split merge_config to key, value with space delimeter + local merge_config_kv = vim.split(merge_config, "%s+") + -- use > 2 since there maybe some garbage white space at the end of the map. + if #merge_config_kv < 2 then + return false + end + + local upstream_branch_ref = merge_config_kv[2] + + -- remove the prefix /refs/heads/ from upstream_branch_ref resulting in + -- branch's name. + local upstream_branch_name = string.gsub(upstream_branch_ref, "^refs/heads/", "") + + if upstream_branch_name:lower() == pr.head_ref_name then + return true + end + + return false +end + +--- Determines if we are locally are in a branch matching the pr head ref +--- @param pr PullRequest +--- @return boolean +function M.in_pr_branch(pr) + return M.in_pr_branch_locally_tracked(pr) or M.in_pr_branch_config_tracked(pr) +end + function M.checkout_pr(pr_number) gh.run { args = { "pr", "checkout", pr_number },