From c631083b7daec2611e18acf2457429b15244e160 Mon Sep 17 00:00:00 2001 From: Willem Jan Noort Date: Sat, 22 Jun 2024 23:17:57 +0200 Subject: [PATCH] feat: improve git remote handling --- lua/adopure/config/internal.lua | 3 +- lua/adopure/config/meta.lua | 1 + lua/adopure/git.lua | 72 ++++++++++++++++++++++----------- lua/adopure/utils.lua | 20 ++++++--- spec/git_remote_spec.lua | 62 ++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 31 deletions(-) create mode 100644 spec/git_remote_spec.lua diff --git a/lua/adopure/config/internal.lua b/lua/adopure/config/internal.lua index cca54b5..d747522 100644 --- a/lua/adopure/config/internal.lua +++ b/lua/adopure/config/internal.lua @@ -7,10 +7,10 @@ ---@class adopure.InternalConfig ---@field pat_token string|nil ---@field hl_groups adopure.InternalHighlights +---@field preferred_remotes string[] local InternalConfig = {} function InternalConfig:new() - ---@type adopure.InternalConfig local default_config = { pat_token = os.getenv("AZURE_DEVOPS_EXT_PAT"), hl_groups = { @@ -19,6 +19,7 @@ function InternalConfig:new() inactive = "DiagnosticUnderlineOk", inactive_sign = "@comment.note", }, + preferred_remotes = {}, } local user_config = type(vim.g.adopure) == "function" and vim.g.adopure() or vim.g.adopure or {} local config = vim.tbl_deep_extend("force", default_config, user_config or {}) diff --git a/lua/adopure/config/meta.lua b/lua/adopure/config/meta.lua index 1b0b66a..185e2f3 100644 --- a/lua/adopure/config/meta.lua +++ b/lua/adopure/config/meta.lua @@ -7,6 +7,7 @@ ---@class adopure.Config ---@field pat_token? string ---@field hl_groups? adopure.Highlights +---@field preferred_remotes? string[] local config = {} diff --git a/lua/adopure/git.lua b/lua/adopure/git.lua index 7057427..fa0703c 100644 --- a/lua/adopure/git.lua +++ b/lua/adopure/git.lua @@ -1,30 +1,47 @@ local M = {} ----@param remote_url string +---@param remote_stdout string[] +---@return string remote +local function elect_remote(remote_stdout) + local preferred_remotes = require("adopure.config.internal").preferred_remotes + for _, remote_line in ipairs(remote_stdout) do + local name_and_details = vim.split(remote_line, "\t") + local remote_name = name_and_details[1] + if vim.tbl_contains(preferred_remotes, remote_name) then + return remote_line + end + end + for _, remote_line in ipairs(remote_stdout) do + if remote_line:find("azure.com") or remote_line:find("visualstudio.com") then + return remote_line + end + end + vim.notify("adopure unable to elect azure devops remote url; taking the first", 3) + return remote_stdout[1] +end + +---@param remote_stdout string ---@return string organization_url ---@return string project_name ---@return string repository_name -local function extract_git_details(remote_url) - local organization_url, project_name, repository_name - if remote_url:find("@ssh.dev.azure.com") then - local _, _, base_url, org_name, project_name_extracted, repo_name_extracted = - remote_url:find(".-@(ssh.dev.azure.com):v3/(.-)/(.-)/(.+)%s*%(fetch%)") - organization_url = "https://" .. base_url:gsub("ssh.", "") .. "/" .. org_name - project_name = project_name_extracted - repository_name = repo_name_extracted - elseif remote_url:find("https") then - local https_pattern = "(https://)[^@]*@([^/]+)/([^/]+)/([^/]+)/_git/([^%s]+)" - local _, _, protocol, domain, org_name, project_name_extracted, repo_name_extracted = - remote_url:find(https_pattern) - organization_url = protocol .. domain .. "/" .. org_name - project_name = project_name_extracted - repository_name = repo_name_extracted +local function extract_git_details(remote_stdout) + local host, project_name, repository_name, organization_name + local url_with_type = vim.split(remote_stdout, "\t")[2] + local url = vim.split(url_with_type, " ")[1] + if url:find("@ssh") then + local ssh_base + ssh_base, organization_name, project_name, repository_name = unpack(vim.split(url, "/")) + host = vim.split(vim.split(ssh_base, ":")[1], "@ssh.")[2] end - - local trim_pattern = "^%s*(.-)%s*$" - return organization_url:gsub(trim_pattern, "%1") .. "/", - project_name:gsub(trim_pattern, "%1"), - repository_name:gsub(trim_pattern, "%1") + if remote_stdout:find("https://") then + local https_base, user_domain + https_base, repository_name = unpack(vim.split(url, "/_git/")) + user_domain, organization_name, project_name = unpack(vim.split(https_base, "/"), 3) + local user_at_host_parts = vim.split(user_domain, "@") + host = user_at_host_parts[#user_at_host_parts] + end + local organization_url = table.concat({ "https://", host, "/", organization_name, "/" }) + return organization_url, project_name, repository_name end ---Get config from git remote @@ -38,9 +55,16 @@ function M.get_remote_config() cwd = ".", }) get_remotes:start() - ---@type string - local remote = require("adopure.utils").await_result(get_remotes)[1] - return extract_git_details(remote) + local result = require("adopure.utils").await_result(get_remotes) + if result.stderr[1] then + if not result.stdout[1] then + error(result.stderr[1]) + end + vim.notify(result.stderr[1], 3) + end + assert(result.stdout[1], "No remote found to extract details;") + local elected_remote = elect_remote(result.stdout) + return extract_git_details(elected_remote) end ---@param pull_request adopure.PullRequest diff --git a/lua/adopure/utils.lua b/lua/adopure/utils.lua index f050934..4a720a8 100644 --- a/lua/adopure/utils.lua +++ b/lua/adopure/utils.lua @@ -1,17 +1,25 @@ local M = {} +---@class adopure.JobResult +---@field stdout string[] +---@field stderr string[] + --- Await result of plenary job ---@diagnostic disable-next-line: undefined-doc-name ---@param job Job ----@return unknown +---@return adopure.JobResult function M.await_result(job) - local result + local stdout, stderr while true do - if result then - return result + if (stdout and stdout[1]) or (stderr and stderr[1]) then + return { + stdout = stdout, + stderr = stderr, + } end - vim.wait(1000, function() + vim.wait(200, function() ---@diagnostic disable-next-line: missing-return,undefined-field - result = job:result() + stdout = job:result() + stderr = job:stderr_result() end) end end diff --git a/spec/git_remote_spec.lua b/spec/git_remote_spec.lua new file mode 100644 index 0000000..b6b1ae4 --- /dev/null +++ b/spec/git_remote_spec.lua @@ -0,0 +1,62 @@ +local assert = require("luassert.assert") +local plenary_new_job = require("plenary.job").new +local remotes = { + first_fetch = "first\tgit@ssh.dev.azure.com:v3/first_org/first_project.nvim/first_repo.nvim (fetch)", + first_push = "first\tgit@ssh.dev.azure.com:v3/first_org/first_project.nvim/first_repo.nvim (push)", + git_fetch = "origin\tgit@github.com:Willem-J-an/adopure.nvim.git (fetch)", + git_push = "origin\tgit@github.com:Willem-J-an/adopure.nvim.git (push)", + second_fetch = "second\thttps://second_org@dev.azure.com/second_org/second_project/_git/second_repo (fetch)", + second_push = "second\thttps://second_org@dev.azure.com/second_org/second_project/_git/second_repo (push)", + third_fetch = "third\thttps://dev.azure.com/third_org/third_project/_git/third_repo (fetch)", + third_push = "third\thttps://dev.azure.com/third_org/third_project/_git/third_repo (push)", +} + +describe("get remote config", function() + ---@param remote_stdout string[] + local function mock_git_remote(remote_stdout) + require("plenary.job").new = function(_1, _2) ---@diagnostic disable-line duplicate-set-field + local _ = _1 and _2 + return { + start = function(_) end, + result = function(_) + return remote_stdout + end, + stderr_result = function(_) + return {} + end, + } + end + end + + it("returns preferred ssh details", function() + require("adopure.config.internal").preferred_remotes = { "first" } + mock_git_remote(vim.tbl_values(remotes)) + local organization_url, project_name, repository_name = require("adopure.git").get_remote_config() + assert.are.same("https://dev.azure.com/first_org/", organization_url) + assert.are.same("first_project.nvim", project_name) + assert.are.same("first_repo.nvim", repository_name) + end) + + it("returns preferred https details with user", function() + require("adopure.config.internal").preferred_remotes = { "second" } + mock_git_remote(vim.tbl_values(remotes)) + local organization_url, project_name, repository_name = require("adopure.git").get_remote_config() + assert.are.same("https://dev.azure.com/second_org/", organization_url) + assert.are.same("second_project", project_name) + assert.are.same("second_repo", repository_name) + end) + + + it("returns preferred https details without user", function() + require("adopure.config.internal").preferred_remotes = { "third" } + mock_git_remote(vim.tbl_values(remotes)) + local organization_url, project_name, repository_name = require("adopure.git").get_remote_config() + assert.are.same("https://dev.azure.com/third_org/", organization_url) + assert.are.same("third_project", project_name) + assert.are.same("third_repo", repository_name) + end) + + after_each(function() + require("plenary.job").new = plenary_new_job + end) +end)