---------------------------------------------------------------------------- -- lua/gitea/pulls.lua -- -- Provides functions specifically for Gitea pull requests: -- list, create, merge, comment, etc. -- We add a key binding to merge the PR in the PR buffer. ---------------------------------------------------------------------------- local M = {} local core = require('gitea') local Job = require('plenary.job') local lyaml = require("lyaml") ---------------------------------------------------------------------------- -- fallback for owner/repo ---------------------------------------------------------------------------- local function fallback_owner_repo(owner, repo) if (not owner or owner == "") or (not repo or repo == "") then owner = core._cached_owner repo = core._cached_repo end if not owner or not repo then vim.schedule(function() vim.cmd([[echoerr "No owner/repo provided and none found in .git/config"]]) end) return nil, nil end return owner, repo end ---------------------------------------------------------------------------- -- M.list_pulls ---------------------------------------------------------------------------- function M.list_pulls(owner, repo, on_done) owner, repo = fallback_owner_repo(owner, repo) if not owner or not repo then return end local ep = string.format("/api/v1/repos/%s/%s/pulls", owner, repo) core.request("GET", ep, nil, function(data, err) if err then vim.schedule(function() vim.cmd(('echoerr "Failed to list PRs: %s"'):format(err)) end) return end if on_done then on_done(data) else print(string.format("Got %d PRs for %s/%s", #data, owner, repo)) end end) end ---------------------------------------------------------------------------- -- M.merge_pull ---------------------------------------------------------------------------- function M.merge_pull(owner, repo, pr_number) owner, repo = fallback_owner_repo(owner, repo) if not owner or not repo or not pr_number then vim.schedule(function() vim.cmd([[echoerr "Usage: :Gitea pr merge "]]) end) return end local ep = string.format("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, pr_number) local data = { Do = "merge" } core.request("POST", ep, data, function(_, err) if err then vim.schedule(function() vim.cmd(('echoerr "Failed to merge PR: %s"'):format(err)) end) return end print(("Merged PR #%d"):format(pr_number)) end) end ---------------------------------------------------------------------------- -- M.create_pull ---------------------------------------------------------------------------- function M.create_pull(owner, repo, head, base, title, body, cb) owner, repo = fallback_owner_repo(owner, repo) if not owner or not repo then if cb then cb(nil, "No owner/repo provided") end return end if not head or head == "" or not base or base == "" then if cb then cb(nil, "Head or base branch not specified") end return end local ep = string.format("/api/v1/repos/%s/%s/pulls", owner, repo) local data = { head = head, base = base, title = title or ("PR from " .. head), body = body or "" } core.request("POST", ep, data, function(resp, err) if err then if cb then cb(nil, err) end return end if cb then cb(resp, nil) end end) end ---------------------------------------------------------------------------- -- Buffer approach for the PR (like issues) ---------------------------------------------------------------------------- local function set_pr_buffer_options(buf) vim.api.nvim_buf_set_option(buf, "buftype", "acwrite") -- allow saving vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe") end local function attach_pr_autocmds(buf) vim.api.nvim_create_autocmd("BufWriteCmd", { buffer = buf, callback = function() local owner = vim.api.nvim_buf_get_var(buf, "gitea_pr_owner") local repo = vim.api.nvim_buf_get_var(buf, "gitea_pr_repo") local number = vim.api.nvim_buf_get_var(buf, "gitea_pr_number") local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) local doc_str = table.concat(lines, "\n") -- Parse as YAML local ok, docs = pcall(lyaml.load, doc_str) if not ok or type(docs) ~= "table" then vim.schedule(function() vim.cmd([[echoerr "Failed to parse PR buffer as YAML!"]]) end) return end local pull_data = docs.pull or {} local patch_data = {} if type(pull_data.title) == "string" and pull_data.title ~= "" then patch_data.title = pull_data.title end if type(pull_data.body) == "string" then patch_data.body = pull_data.body end if vim.tbl_count(patch_data) > 0 then local ep = string.format("/api/v1/repos/%s/%s/pulls/%d", owner, repo, number) core.request("PATCH", ep, patch_data, function(_, err) if err then vim.schedule(function() vim.cmd(('echoerr "Failed to update pull request: %s"'):format(err)) end) return end print("[gitea.nvim] Pull request updated.") end) end end, }) end ---------------------------------------------------------------------------- -- Keymap callback to merge the current PR ---------------------------------------------------------------------------- function M.merge_current_pr() local buf = vim.api.nvim_get_current_buf() local owner = vim.api.nvim_buf_get_var(buf, "gitea_pr_owner") local repo = vim.api.nvim_buf_get_var(buf, "gitea_pr_repo") local number = vim.api.nvim_buf_get_var(buf, "gitea_pr_number") M.merge_pull(owner, repo, number) end function M.open_pull_in_buffer(owner, repo, pr_number) owner, repo = fallback_owner_repo(owner, repo) if not owner or not repo or not pr_number then vim.schedule(function() vim.cmd([[echoerr "Usage: open_pull_in_buffer "]]) end) return end -- 1) Get PR details local pr_ep = string.format("/api/v1/repos/%s/%s/pulls/%d", owner, repo, pr_number) core.request("GET", pr_ep, nil, function(pr, err) if err then vim.schedule(function() vim.cmd(('echoerr "Failed to load PR: %s"'):format(err)) end) return end -- 2) Create buffer, fill it vim.schedule(function() local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(buf, string.format("gitea://pr_%d.yaml", pr_number)) vim.api.nvim_buf_set_option(buf, "filetype", "yaml") set_pr_buffer_options(buf) attach_pr_autocmds(buf) vim.api.nvim_buf_set_var(buf, "gitea_pr_owner", owner) vim.api.nvim_buf_set_var(buf, "gitea_pr_repo", repo) vim.api.nvim_buf_set_var(buf, "gitea_pr_number", pr_number) local lines = {} table.insert(lines, "pull:") table.insert(lines, (" number: %d"):format(pr.number or pr_number)) table.insert(lines, (" title: %s"):format(pr.title or "")) table.insert(lines, (" state: %s"):format(pr.state or "")) table.insert(lines, " body: |") if type(pr.body) == "string" and #pr.body > 0 then for line in string.gmatch(pr.body, "[^\r\n]+") do table.insert(lines, " " .. line) end end vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) -- Keymap to merge the current PR from the buffer vim.api.nvim_buf_set_keymap( buf, "n", "pm", ":lua require('gitea.pulls').merge_current_pr()", { noremap = true, silent = true } ) -- Keymap to close the PR buffer quickly vim.api.nvim_buf_set_keymap( buf, "n", "pq", ":bd!", { noremap = true, silent = true } ) -- Show the buffer if core.preview_style == "split" then vim.cmd("vsplit") vim.api.nvim_set_current_buf(buf) else local width = math.floor(vim.o.columns * 0.7) local height = math.floor(vim.o.lines * 0.7) local row = math.floor((vim.o.lines - height) / 2) local col = math.floor((vim.o.columns - width) / 2) vim.api.nvim_open_win(buf, true, { relative = "editor", width = width, height = height, row = row, col = col, style = "minimal", border = "rounded" }) end end) end) end return M