Comments on deleted lines

This commit is contained in:
Harrison Cramer
2023-04-23 17:21:13 -04:00
parent b0cdc5dd88
commit 182575d95a
4 changed files with 157 additions and 50 deletions

View File

@@ -1,14 +1,17 @@
package main package main
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"mime/multipart"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
@@ -16,6 +19,7 @@ import (
) )
const mrVersionsUrl = "https://gitlab.com/api/v4/projects/%s/merge_requests/%d/versions" const mrVersionsUrl = "https://gitlab.com/api/v4/projects/%s/merge_requests/%d/versions"
const commentUrl = "https://gitlab.com/api/v4/projects/%s/repository/commits/%s/comments"
type MRVersion struct { type MRVersion struct {
ID int `json:"id"` ID int `json:"id"`
@@ -29,11 +33,11 @@ type MRVersion struct {
} }
func (c *Client) Comment() error { func (c *Client) Comment() error {
if len(os.Args) < 6 { if len(os.Args) < 7 {
c.Usage("comment") c.Usage("comment")
} }
lineNumber, fileName, comment := os.Args[3], os.Args[4], os.Args[5] lineNumber, fileName, comment, sha := os.Args[3], os.Args[4], os.Args[5], os.Args[6]
if lineNumber == "" || fileName == "" || comment == "" { if lineNumber == "" || fileName == "" || comment == "" {
c.Usage("comment") c.Usage("comment")
} }
@@ -53,67 +57,97 @@ func (c *Client) Comment() error {
var diffVersionInfo []MRVersion var diffVersionInfo []MRVersion
err = json.Unmarshal(body, &diffVersionInfo) err = json.Unmarshal(body, &diffVersionInfo)
if err != nil {
return fmt.Errorf("Error unmarshalling version info JSON: %w", err)
}
time := time.Now() time := time.Now()
/* This is necessary since we do not know whether the comment is on a line that if sha == "" {
has been changed or not. Making all three of these API calls will let us leave /* This is necessary since we do not know whether the comment is on a line that
the comment regardless. See the Gitlab documentation: https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff */ has been changed or not. Making all three of these API calls will let us leave
wg := sync.WaitGroup{} the comment regardless. See the Gitlab documentation: https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff */
wg.Add(3) wg := sync.WaitGroup{}
wg.Add(2)
resultChannel := make(chan *gitlab.Discussion, 3) resultChannel := make(chan *gitlab.Discussion, 2)
for i := 0; i < 3; i++ { for i := 0; i < 2; i++ {
ii := i ii := i
go func() { go func() {
defer wg.Done() defer wg.Done()
options := gitlab.CreateMergeRequestDiscussionOptions{ options := gitlab.CreateMergeRequestDiscussionOptions{
Body: &comment, Body: &comment,
CreatedAt: &time, CreatedAt: &time,
Position: &gitlab.NotePosition{ Position: &gitlab.NotePosition{
PositionType: "text", PositionType: "text",
BaseSHA: diffVersionInfo[0].BaseCommitSHA, BaseSHA: diffVersionInfo[0].BaseCommitSHA,
HeadSHA: diffVersionInfo[0].HeadCommitSHA, HeadSHA: diffVersionInfo[0].HeadCommitSHA,
StartSHA: diffVersionInfo[0].StartCommitSHA, StartSHA: diffVersionInfo[0].StartCommitSHA,
NewPath: fileName, NewPath: fileName,
OldPath: fileName, OldPath: fileName,
}, },
} }
if ii == 0 { if ii == 0 {
options.Position.NewLine = lineNumberInt options.Position.NewLine = lineNumberInt
} else if ii == 1 { } else {
options.Position.OldLine = lineNumberInt options.Position.NewLine = lineNumberInt
} else { options.Position.OldLine = lineNumberInt
options.Position.NewLine = lineNumberInt }
options.Position.OldLine = lineNumberInt
}
discussion, _, _ := c.git.Discussions.CreateMergeRequestDiscussion(c.projectId, c.mergeId, &options) discussion, _, _ := c.git.Discussions.CreateMergeRequestDiscussion(c.projectId, c.mergeId, &options)
resultChannel <- discussion resultChannel <- discussion
}() }()
}
go func() {
wg.Wait()
close(resultChannel)
}()
var createdDiscussion *gitlab.Discussion
for discussion := range resultChannel {
if discussion != nil {
createdDiscussion = discussion
} }
}
if createdDiscussion == nil { go func() {
return fmt.Errorf("Could not leave comment") wg.Wait()
close(resultChannel)
}()
var createdDiscussion *gitlab.Discussion
for discussion := range resultChannel {
if discussion != nil {
createdDiscussion = discussion
}
}
if createdDiscussion == nil {
return fmt.Errorf("Could not leave comment")
}
} else {
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("note", comment)
_ = writer.WriteField("path", "README.md")
_ = writer.WriteField("line", "3")
_ = writer.WriteField("line_type", "old")
err := writer.Close()
if err != nil {
return fmt.Errorf("Error making form data: %w", err)
}
url := fmt.Sprintf(commentUrl, c.projectId, strings.TrimSpace(sha))
client := &http.Client{}
req, err := http.NewRequest(http.MethodPost, url, payload)
if err != nil {
return fmt.Errorf("Error building request: %w", err)
}
req.Header.Add("PRIVATE-TOKEN", os.Getenv("GITLAB_TOKEN"))
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := client.Do(req)
if err != nil {
return fmt.Errorf("Error making request: %w", err)
}
defer res.Body.Close()
} }
fmt.Println("Left Comment: " + comment[0:min(len(comment), 25)] + "...") fmt.Println("Left Comment: " + comment[0:min(len(comment), 25)] + "...")
return nil return nil
} }
@@ -151,3 +185,29 @@ func getMRVersions(projectId string, mergeId int) (e error, response *http.Respo
return nil, response return nil, response
} }
func (c *Client) OverviewComment() error {
lineNumber, fileName, comment, sha := os.Args[3], os.Args[4], os.Args[5], os.Args[6]
if lineNumber == "" || fileName == "" || comment == "" {
c.Usage("comment")
}
lineNumberInt, err := strconv.Atoi(lineNumber)
if err != nil {
return fmt.Errorf("Not a valid line number: %w", err)
}
postCommitCommentOptions := gitlab.PostCommitCommentOptions{
Note: gitlab.String(comment),
Path: gitlab.String(fileName),
Line: &lineNumberInt,
LineType: gitlab.String("old"),
}
_, _, err = c.git.Commits.PostCommitComment(c.projectId, sha, &postCommitCommentOptions)
if err != nil {
return fmt.Errorf("Error leaving overview comment: %w", err)
}
fmt.Println("Left Overview Comment: " + comment[0:min(len(comment), 25)] + "...")
return nil
}

View File

@@ -14,6 +14,7 @@ const (
approve = "approve" approve = "approve"
revoke = "revoke" revoke = "revoke"
comment = "comment" comment = "comment"
overviewComment = "overviewComment"
deleteComment = "deleteComment" deleteComment = "deleteComment"
editComment = "editComment" editComment = "editComment"
reply = "reply" reply = "reply"
@@ -44,6 +45,8 @@ func main() {
errCheck(c.DeleteComment()) errCheck(c.DeleteComment())
case editComment: case editComment:
errCheck(c.EditComment()) errCheck(c.EditComment())
case overviewComment:
errCheck(c.OverviewComment())
case info: case info:
errCheck(c.Info()) errCheck(c.Info())
case reply: case reply:

View File

@@ -19,11 +19,43 @@ M.create_comment = function()
keymaps.set_popup_keymaps(commentPopup, M.confirm_create_comment) keymaps.set_popup_keymaps(commentPopup, M.confirm_create_comment)
end end
M.find_deletion_commit = function(file)
local current_line = vim.api.nvim_get_current_line()
local command = string.format("git log -S '%s' %s", current_line, file)
local handle = io.popen(command)
local output = handle:read("*line")
if output == nil then
notify("Error reading SHA of deletion commit", "error")
return ""
end
handle:close()
local words = {}
for word in output:gmatch("%S+") do
table.insert(words, word)
end
return words[2]
end
-- Sends the comment to Gitlab -- Sends the comment to Gitlab
M.confirm_create_comment = function(text) M.confirm_create_comment = function(text)
if u.base_invalid() then return end if u.base_invalid() then return end
local relative_file_path = u.get_relative_file_path() local relative_file_path = u.get_relative_file_path()
local current_line_number = u.get_current_line_number() local current_line_number = u.get_current_line_number()
if relative_file_path == nil then return end
-- If leaving a comment on a deleted line, get hash value + proper filename
local sha = ""
local is_base_file = relative_file_path:find(".git")
if is_base_file then -- We are looking at a deletion.
local _, path = u.split_diff_view_filename(relative_file_path)
sha = M.find_deletion_commit(path)
if sha == "" then
return
end
end
Job:new({ Job:new({
command = state.BIN, command = state.BIN,
args = { args = {
@@ -32,6 +64,7 @@ M.confirm_create_comment = function(text)
current_line_number, current_line_number,
relative_file_path, relative_file_path,
text, text,
sha
}, },
-- TODO: Render the tree after comment creation. Refresh? -- TODO: Render the tree after comment creation. Refresh?
on_stdout = u.print_success, on_stdout = u.print_success,

View File

@@ -215,6 +215,16 @@ local read_file = function(file_path)
return file_contents return file_contents
end end
local split_diff_view_filename = function(filename)
local hash, path = filename:match("://%.git/(/?[0-9a-f]+)(/.*)$")
if hash and path then
path = path:gsub("%.git/", ""):gsub("^/", "")
hash = hash:gsub("^/", "")
end
return hash, path
end
M.get_relative_file_path = get_relative_file_path M.get_relative_file_path = get_relative_file_path
M.get_current_line_number = get_current_line_number M.get_current_line_number = get_current_line_number
M.get_buffer_text = get_buffer_text M.get_buffer_text = get_buffer_text
@@ -233,5 +243,6 @@ M.print_error = print_error
M.create_popup_state = create_popup_state M.create_popup_state = create_popup_state
M.exit = exit M.exit = exit
M.read_file = read_file M.read_file = read_file
M.split_diff_view_filename = split_diff_view_filename
M.P = P M.P = P
return M return M