Change to HTTP Model (#5)

This commit is contained in:
Harrison (Harry) Cramer
2023-05-19 17:28:58 -07:00
committed by GitHub
parent fe0d09d582
commit 63fc025070
21 changed files with 689 additions and 430 deletions

View File

@@ -1,18 +1,46 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func (c *Client) Approve() error {
func (c *Client) Approve() (string, int, error) {
_, _, err := c.git.MergeRequestApprovals.ApproveMergeRequest(c.projectId, c.mergeId, nil, nil)
_, res, err := c.git.MergeRequestApprovals.ApproveMergeRequest(c.projectId, c.mergeId, nil, nil)
if err != nil {
return fmt.Errorf("Approving MR failed: %w", err)
return "", res.Response.StatusCode, fmt.Errorf("Approving MR failed: %w", err)
}
fmt.Println("Success! Approved MR.")
return nil
return "Success! Approved MR.", http.StatusOK, nil
}
func ApproveHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
client := r.Context().Value("client").(Client)
msg, status, err := client.Approve()
w.WriteHeader(status)
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: status,
}
json.NewEncoder(w).Encode(response)
return
}
response := SuccessResponse{
Message: msg,
Status: http.StatusOK,
}
json.NewEncoder(w).Encode(response)
}

View File

@@ -16,22 +16,37 @@ type Client struct {
git *gitlab.Client
}
type Logger struct {
Active bool
}
func (l Logger) Printf(s string, args ...interface{}) {
logString := fmt.Sprintf(s+"\n", args...)
file, err := os.OpenFile("./logs", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer file.Close()
_, err = file.Write([]byte(logString))
}
/* This will initialize the client with the token and check for the basic project ID and command arguments */
func (c *Client) Init(branchName string) error {
if len(os.Args) < 3 {
return errors.New("Must provide command and projectId")
if len(os.Args) < 2 {
return errors.New("Must provide project ID!")
}
command, projectId := os.Args[1], os.Args[2]
c.command = command
projectId := os.Args[1]
c.projectId = projectId
if projectId == "" {
return errors.New("Must provide projectId")
return errors.New("Project ID cannot be empty")
}
git, err := gitlab.NewClient(os.Getenv("GITLAB_TOKEN"))
var l Logger
git, err := gitlab.NewClient(os.Getenv("GITLAB_TOKEN"), gitlab.WithCustomLogger(l))
if err != nil {
return fmt.Errorf("Failed to create client: %v", err)
}
@@ -61,8 +76,3 @@ func (c *Client) Init(branchName string) error {
return nil
}
func (c *Client) Usage(command string) {
fmt.Printf("Usage: gitlab-nvim %s <project-id> ...args", command)
os.Exit(1)
}

View File

@@ -5,12 +5,11 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"strconv"
"time"
"github.com/xanzy/go-gitlab"
@@ -29,20 +28,192 @@ type MRVersion struct {
RealSize string `json:"real_size"`
}
func (c *Client) Comment() error {
if len(os.Args) < 6 {
c.Usage("comment")
type PostCommentRequest struct {
LineNumber int `json:"line_number"`
FileName string `json:"file_name"`
Comment string `json:"comment"`
}
type DeleteCommentRequest struct {
NoteId int `json:"note_id"`
DiscussionId string `json:"discussion_id"`
}
type EditCommentRequest struct {
Comment string `json:"comment"`
NoteId int `json:"note_id"`
DiscussionId string `json:"discussion_id"`
}
func CommentHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodDelete:
DeleteComment(w, r)
case http.MethodPost:
PostComment(w, r)
case http.MethodPatch:
EditComment(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func DeleteComment(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
c := r.Context().Value("client").(Client)
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not read request body"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
lineNumber, fileName, comment := os.Args[3], os.Args[4], os.Args[5]
if lineNumber == "" || fileName == "" || comment == "" {
c.Usage("comment")
defer r.Body.Close()
var deleteCommentRequest DeleteCommentRequest
err = json.Unmarshal(body, &deleteCommentRequest)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not read JSON from request"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
res, err := c.git.Discussions.DeleteMergeRequestDiscussionNote(c.projectId, c.mergeId, deleteCommentRequest.DiscussionId, deleteCommentRequest.NoteId)
w.WriteHeader(res.Response.StatusCode)
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: res.Response.StatusCode,
}
json.NewEncoder(w).Encode(response)
return
}
response := SuccessResponse{
Message: "Comment deleted succesfully",
Status: http.StatusOK,
}
json.NewEncoder(w).Encode(response)
}
func PostComment(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
c := r.Context().Value("client").(Client)
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not read request body"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
defer r.Body.Close()
var postCommentRequest PostCommentRequest
err = json.Unmarshal(body, &postCommentRequest)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not unmarshal data from request body"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
res, err := c.PostComment(postCommentRequest)
for k, v := range res.Header {
w.Header().Set(k, v[0])
}
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: res.StatusCode,
}
json.NewEncoder(w).Encode(response)
return
}
response := SuccessResponse{
Message: "Comment created succesfully",
Status: http.StatusOK,
}
json.NewEncoder(w).Encode(response)
// w.WriteHeader(res.StatusCode)
// io.Copy(w, res.Body)
}
func EditComment(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
c := r.Context().Value("client").(Client)
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not read request body"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
defer r.Body.Close()
var editCommentRequest EditCommentRequest
err = json.Unmarshal(body, &editCommentRequest)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not unmarshal data from request body"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
options := gitlab.UpdateMergeRequestDiscussionNoteOptions{
Body: gitlab.String(editCommentRequest.Comment),
}
_, res, err := c.git.Discussions.UpdateMergeRequestDiscussionNote(c.projectId, c.mergeId, editCommentRequest.DiscussionId, editCommentRequest.NoteId, &options)
for k, v := range res.Header {
w.Header().Set(k, v[0])
}
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: res.StatusCode,
}
json.NewEncoder(w).Encode(response)
return
}
response := SuccessResponse{
Message: "Comment edited succesfully",
Status: http.StatusOK,
}
json.NewEncoder(w).Encode(response)
}
func (c *Client) PostComment(cr PostCommentRequest) (*http.Response, error) {
err, response := getMRVersions(c.projectId, c.mergeId)
if err != nil {
log.Fatalf("Error making diff thread: %s", err)
return nil, fmt.Errorf("Error making diff thread: %e", err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
@@ -50,7 +221,7 @@ func (c *Client) Comment() error {
var diffVersionInfo []MRVersion
err = json.Unmarshal(body, &diffVersionInfo)
if err != nil {
return fmt.Errorf("Error unmarshalling version info JSON: %w", err)
return nil, fmt.Errorf("Error unmarshalling version info JSON: %w", err)
}
/* This is necessary since we do not know whether the comment is on a line that
@@ -65,14 +236,14 @@ func (c *Client) Comment() error {
See the Gitlab documentation: https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff */
for i := 0; i < 3; i++ {
ii := i
_, err := c.CommentOnDeletion(lineNumber, fileName, comment, diffVersionInfo[0], ii)
if err == nil {
fmt.Println("Left Comment: " + comment[0:min(len(comment), 25)] + "...")
return nil
res, err := c.CommentOnDeletion(cr.LineNumber, cr.FileName, cr.Comment, diffVersionInfo[0], ii)
if err == nil && res.StatusCode >= 200 && res.StatusCode <= 299 {
return res, nil
}
}
return fmt.Errorf("Could not leave comment")
return nil, fmt.Errorf("Could not leave comment")
}
@@ -115,7 +286,7 @@ func getMRVersions(projectId string, mergeId int) (e error, response *http.Respo
Creates a new merge request discussion https://docs.gitlab.com/ee/api/discussions.html#create-new-merge-request-thread
The go-gitlab client was not working for this API specifically 😢
*/
func (c *Client) CommentOnDeletion(lineNumber string, fileName string, comment string, diffVersionInfo MRVersion, i int) (*http.Response, error) {
func (c *Client) CommentOnDeletion(lineNumber int, fileName string, comment string, diffVersionInfo MRVersion, i int) (*http.Response, error) {
deletionDiscussionUrl := fmt.Sprintf("https://gitlab.com/api/v4/projects/%s/merge_requests/%d/discussions", c.projectId, c.mergeId)
@@ -132,12 +303,12 @@ func (c *Client) CommentOnDeletion(lineNumber string, fileName string, comment s
_ = writer.WriteField("position[old_path]", fileName)
_ = writer.WriteField("position[new_path]", fileName)
if i == 0 {
_ = writer.WriteField("position[old_line]", lineNumber)
_ = writer.WriteField("position[old_line]", fmt.Sprintf("%d", lineNumber))
} else if i == 1 {
_ = writer.WriteField("position[new_line]", lineNumber)
_ = writer.WriteField("position[new_line]", fmt.Sprintf("%d", lineNumber))
} else {
_ = writer.WriteField("position[old_line]", lineNumber)
_ = writer.WriteField("position[new_line]", lineNumber)
_ = writer.WriteField("position[old_line]", fmt.Sprintf("%d", lineNumber))
_ = writer.WriteField("position[new_line]", fmt.Sprintf("%d", lineNumber))
}
err := writer.Close()
@@ -152,39 +323,9 @@ func (c *Client) CommentOnDeletion(lineNumber string, fileName string, comment s
return nil, 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 nil, fmt.Errorf("Error making request: %w", err)
}
defer res.Body.Close()
return res, nil
}
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
return res, err
}

View File

@@ -1,29 +0,0 @@
package main
import (
"fmt"
"os"
"strconv"
)
func (c *Client) DeleteComment() error {
discussionId, noteId := os.Args[3], os.Args[4]
if discussionId == "" || noteId == "" {
c.Usage("deleteComment")
}
noteIdInt, err := strconv.Atoi(noteId)
if err != nil {
return fmt.Errorf("Could not convert noteId to int: %w", err)
}
_, err = c.git.Discussions.DeleteMergeRequestDiscussionNote(c.projectId, c.mergeId, discussionId, noteIdInt)
if err != nil {
return fmt.Errorf("Could not delete comment: %w", err)
}
fmt.Println("Deleted comment")
return nil
}

View File

@@ -1,46 +0,0 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"github.com/xanzy/go-gitlab"
)
func (c *Client) EditComment() error {
if len(os.Args) < 6 {
return errors.New("Must provide commentId, noteId, and edited text")
}
discussionId, noteId, newCommentText := os.Args[3], os.Args[4], os.Args[5]
if discussionId == "" || newCommentText == "" {
return errors.New("Must provide commentId, noteId, and edited text")
}
options := gitlab.UpdateMergeRequestDiscussionNoteOptions{
Body: gitlab.String(newCommentText),
}
noteIdInt, err := strconv.Atoi(noteId)
if err != nil {
return errors.New("Not a valid noteId")
}
gitlabNote, _, err := c.git.Discussions.UpdateMergeRequestDiscussionNote(c.projectId, c.mergeId, discussionId, noteIdInt, &options)
if err != nil {
return fmt.Errorf("Failed to edit comment: %w", err)
}
note, err := json.Marshal(gitlabNote)
if err != nil {
return fmt.Errorf("Failed to marshal note: %w", err)
}
fmt.Println(string(note))
return nil
}

View File

@@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
@@ -10,13 +11,13 @@ import (
const mrUrl = "https://gitlab.com/api/v4/projects/%s/merge_requests/%d"
func (c *Client) Info() error {
func (c *Client) Info() ([]byte, error) {
url := fmt.Sprintf(mrUrl, c.projectId, c.mergeId)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("Failed to build read request: %w", err)
return nil, fmt.Errorf("Failed to build read request: %w", err)
}
req.Header.Set("PRIVATE-TOKEN", os.Getenv("GITLAB_TOKEN"))
@@ -25,22 +26,42 @@ func (c *Client) Info() error {
res, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("Failed to make info request: %w", err)
return nil, fmt.Errorf("Failed to make info request: %w", err)
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 {
return errors.New(fmt.Sprintf("Recieved non-200 response: %d", res.StatusCode))
return nil, errors.New(fmt.Sprintf("Recieved non-200 response: %d", res.StatusCode))
}
body, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("Failed to parse read response: %w", err)
return nil, fmt.Errorf("Failed to parse read response: %w", err)
}
/* This response is parsed into a table in our Lua code */
fmt.Println(string(body))
return body, nil
return nil
}
func InfoHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
client := r.Context().Value("client").(Client)
msg, err := client.Info()
if err != nil {
errResp := map[string]string{"message": err.Error()}
response, _ := json.Marshal(errResp)
w.WriteHeader(http.StatusInternalServerError)
w.Write(response)
return
}
w.WriteHeader(http.StatusOK)
w.Write(msg)
}

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"net/http"
"sort"
"encoding/json"
@@ -11,6 +12,11 @@ import (
type SortableDiscussions []*gitlab.Discussion
type DiscussionsResponse struct {
SuccessResponse
Discussions []*gitlab.Discussion `json:"discussions"`
}
func (n SortableDiscussions) Len() int {
return len(n)
}
@@ -26,16 +32,16 @@ func (n SortableDiscussions) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func (c *Client) ListDiscussions() error {
func (c *Client) ListDiscussions() ([]*gitlab.Discussion, int, error) {
mergeRequestDiscussionOptions := gitlab.ListMergeRequestDiscussionsOptions{
Page: 1,
PerPage: 250,
}
discussions, _, err := c.git.Discussions.ListMergeRequestDiscussions(c.projectId, c.mergeId, &mergeRequestDiscussionOptions, nil)
discussions, res, err := c.git.Discussions.ListMergeRequestDiscussions(c.projectId, c.mergeId, &mergeRequestDiscussionOptions, nil)
if err != nil {
return fmt.Errorf("Listing discussions failed: %w", err)
return nil, res.Response.StatusCode, fmt.Errorf("Listing discussions failed: %w", err)
}
var realDiscussions []*gitlab.Discussion
@@ -52,9 +58,35 @@ func (c *Client) ListDiscussions() error {
sortedDiscussions := SortableDiscussions(realDiscussions)
sort.Sort(sortedDiscussions)
discussionsOutput, err := json.Marshal(sortedDiscussions)
fmt.Println(string(discussionsOutput))
return nil
return sortedDiscussions, http.StatusOK, nil
}
func ListDiscussionsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
c := r.Context().Value("client").(Client)
msg, status, err := c.ListDiscussions()
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: status,
}
json.NewEncoder(w).Encode(response)
return
}
response := DiscussionsResponse{
SuccessResponse: SuccessResponse{
Message: "Discussions successfully fetched.",
Status: http.StatusOK,
},
Discussions: msg,
}
json.NewEncoder(w).Encode(response)
}

View File

@@ -1,61 +1,59 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"strings"
)
const (
star = "star"
info = "info"
approve = "approve"
revoke = "revoke"
comment = "comment"
overviewComment = "overviewComment"
deleteComment = "deleteComment"
editComment = "editComment"
reply = "reply"
listDiscussions = "listDiscussions"
)
func main() {
branchName, err := getCurrentBranch()
errCheck(err)
if branchName == "main" || branchName == "master" {
return
log.Fatalf("Cannot run on %s branch", branchName)
}
/* Initialize Gitlab client */
var c Client
errCheck(c.Init(branchName))
switch c.command {
case star:
errCheck(c.Star())
case approve:
errCheck(c.Approve())
case revoke:
errCheck(c.Revoke())
case comment:
errCheck(c.Comment())
case deleteComment:
errCheck(c.DeleteComment())
case editComment:
errCheck(c.EditComment())
case overviewComment:
errCheck(c.OverviewComment())
case info:
errCheck(c.Info())
case reply:
errCheck(c.Reply())
case listDiscussions:
errCheck(c.ListDiscussions())
default:
c.Usage("command")
m := http.NewServeMux()
m.Handle("/approve", withGitlabContext(http.HandlerFunc(ApproveHandler), c))
m.Handle("/revoke", withGitlabContext(http.HandlerFunc(RevokeHandler), c))
m.Handle("/star", withGitlabContext(http.HandlerFunc(StarHandler), c))
m.Handle("/info", withGitlabContext(http.HandlerFunc(InfoHandler), c))
m.Handle("/discussions", withGitlabContext(http.HandlerFunc(ListDiscussionsHandler), c))
m.Handle("/comment", withGitlabContext(http.HandlerFunc(CommentHandler), c))
m.Handle("/reply", withGitlabContext(http.HandlerFunc(ReplyHandler), c))
server := &http.Server{
Addr: fmt.Sprintf(":%s", os.Args[2]),
Handler: m,
}
done := make(chan bool)
go server.ListenAndServe()
/* This print is detected by the Lua code and used to fetch project information */
fmt.Println("Server started.")
<-done
}
type ResponseError struct {
message string
}
func withGitlabContext(next http.HandlerFunc, c Client) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(context.Background(), "client", c)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func errCheck(err error) {

View File

@@ -2,40 +2,90 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"io"
"net/http"
"time"
"github.com/xanzy/go-gitlab"
)
func (c *Client) Reply() error {
type ReplyRequest struct {
DiscussionId string `json:"discussion_id"`
Reply string `json:"reply"`
}
if len(os.Args) < 5 {
return errors.New("Must provide discussionId and reply text")
}
type ReplyResponse struct {
SuccessResponse
Note *gitlab.Note `json:"note"`
}
discussionId, reply := os.Args[3], os.Args[4]
func (c *Client) Reply(r ReplyRequest) (*gitlab.Note, int, error) {
now := time.Now()
options := gitlab.AddMergeRequestDiscussionNoteOptions{
Body: gitlab.String(reply),
Body: gitlab.String(r.Reply),
CreatedAt: &now,
}
gitlabNote, _, err := c.git.Discussions.AddMergeRequestDiscussionNote(c.projectId, c.mergeId, discussionId, &options)
note, res, err := c.git.Discussions.AddMergeRequestDiscussionNote(c.projectId, c.mergeId, r.DiscussionId, &options)
if err != nil {
return fmt.Errorf("Could not leave reply: %w", err)
return nil, res.Response.StatusCode, fmt.Errorf("Could not leave reply: %w", err)
}
output, err := json.Marshal(gitlabNote)
if err != nil {
return fmt.Errorf("Could not marshal note: %w", err)
}
fmt.Println(string(output))
return nil
return note, http.StatusOK, nil
}
func ReplyHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
c := r.Context().Value("client").(Client)
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not read request body"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
defer r.Body.Close()
var replyRequest ReplyRequest
err = json.Unmarshal(body, &replyRequest)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
errMsg := map[string]string{"message": "Could not read JSON from request"}
jsonMsg, _ := json.Marshal(errMsg)
w.Write(jsonMsg)
return
}
note, status, err := c.Reply(replyRequest)
w.Header().Set("Content-Type", "application/json")
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: status,
}
json.NewEncoder(w).Encode(response)
return
}
response := ReplyResponse{
SuccessResponse: SuccessResponse{
Message: fmt.Sprintf("Replied: %s", note.Body),
Status: http.StatusOK,
},
Note: note,
}
json.NewEncoder(w).Encode(response)
}

View File

@@ -1,18 +1,47 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func (c *Client) Revoke() error {
func (c *Client) Revoke() (string, int, error) {
_, err := c.git.MergeRequestApprovals.UnapproveMergeRequest(c.projectId, c.mergeId, nil, nil)
res, err := c.git.MergeRequestApprovals.UnapproveMergeRequest(c.projectId, c.mergeId, nil, nil)
if err != nil {
return fmt.Errorf("Revoking approval failed: %w", err)
return "", res.Response.StatusCode, fmt.Errorf("Revoking approval failed: %w", err)
}
fmt.Println("Success! Revoked MR approval.")
return "Success! Revoked MR approval.", http.StatusOK, nil
return nil
}
func RevokeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
client := r.Context().Value("client").(Client)
msg, status, err := client.Revoke()
w.WriteHeader(status)
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: status,
}
json.NewEncoder(w).Encode(response)
return
}
response := SuccessResponse{
Message: msg,
Status: http.StatusOK,
}
json.NewEncoder(w).Encode(response)
}

View File

@@ -1,17 +1,46 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/xanzy/go-gitlab"
)
func (c *Client) Star() error {
project, _, err := c.git.Projects.StarProject(c.projectId)
func (c *Client) Star() (*gitlab.Project, int, error) {
project, res, err := c.git.Projects.StarProject(c.projectId)
if err != nil {
return fmt.Errorf("Starring project failed: %w", err)
return nil, res.Response.StatusCode, fmt.Errorf("Starring project failed: %w", err)
}
log.Printf("Success! Starred project: %s", project.Name)
return project, http.StatusOK, nil
return nil
}
func StarHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
client := r.Context().Value("client").(Client)
project, status, err := client.Star()
w.Header().Set("Content-Type", "application/json")
if err != nil {
response := ErrorResponse{
Message: err.Error(),
Status: status,
}
json.NewEncoder(w).Encode(response)
return
}
response := SuccessResponse{
Message: fmt.Sprintf("Starred project %s successfully!", project.Name),
Status: http.StatusOK,
}
json.NewEncoder(w).Encode(response)
}

12
cmd/start.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import (
"fmt"
"os"
)
func (c *Client) Start() error {
processId := os.Getpid()
fmt.Println(processId)
return nil
}

11
cmd/types.go Normal file
View File

@@ -0,0 +1,11 @@
package main
type ErrorResponse struct {
Message string `json:"message"`
Status int `json:"status"`
}
type SuccessResponse struct {
Message string `json:"message"`
Status int `json:"status"`
}

1
logs Normal file
View File

@@ -0,0 +1 @@
[DEBUG] GET https://gitlab.com/api/v4/merge_requests?source_branch=develop&state=opened

View File

@@ -1,18 +0,0 @@
local u = require("gitlab.utils")
local state = require("gitlab.state")
local Job = require("plenary.job")
local M = {}
-- Approves the current merge request
M.approve = function()
if u.base_invalid() then return end
Job:new({
command = state.BIN,
args = { "approve", state.PROJECT_ID },
on_stdout = u.print_success,
on_stderr = u.print_error
}):start()
end
return M

View File

@@ -1,7 +1,7 @@
local Menu = require("nui.menu")
local NuiTree = require("nui.tree")
local notify = require("notify")
local Job = require("plenary.job")
local job = require("gitlab.job")
local state = require("gitlab.state")
local u = require("gitlab.utils")
local keymaps = require("gitlab.keymaps")
@@ -56,20 +56,9 @@ M.confirm_create_comment = function(text)
end
end
Job:new({
command = state.BIN,
args = {
"comment",
state.PROJECT_ID,
current_line_number,
relative_file_path,
text,
sha
},
-- TODO: Render the tree after comment creation. Refresh?
on_stdout = u.print_success,
on_stderr = u.print_error
}):start()
local json = string.format('{ "line_number": %d, "file_name": "%s", "comment": "%s" }', current_line_number,
relative_file_path, text)
job.run_job("comment", "POST", json)
end
M.delete_comment = function()
@@ -118,29 +107,18 @@ M.delete_comment = function()
local discussion_id = node:get_id()
discussion_id = string.sub(discussion_id, 2) -- Remove the "-" at the start
note_id = string.sub(note_id, 2) -- Remove the "-" at the start
Job:new({
command = state.BIN,
args = {
"deleteComment",
state.PROJECT_ID,
discussion_id,
note_id,
},
on_stdout = function(_, line)
vim.schedule(function()
if line ~= nil and line ~= "" then
notify(line, "info")
local json = string.format('{"discussion_id": "%s", "note_id": %d}', discussion_id, note_id)
job.run_job("comment", "DELETE", json, function(data)
notify(data.message, "success")
state.tree:remove_node("-" .. note_id)
local discussion_node = state.tree:get_node("-" .. discussion_id)
if not discussion_node:has_children() then
state.tree:remove_node("-" .. discussion_id)
end
state.tree:render()
end
end)
end,
on_stderr = u.print_error
}):start()
end
end,
})
@@ -179,32 +157,20 @@ M.edit_comment = function()
end
M.send_edits = function(text)
Job:new({
command = state.BIN,
args = {
"editComment",
state.PROJECT_ID,
state.ACTIVE_DISCUSSION,
state.ACTIVE_NOTE,
text,
},
on_stdout = function(_, line)
local note = vim.json.decode(line)
if note == nil then
notify("There was an issue editing the note", "error")
return
end
local escapedText = string.gsub(text, "\n", "\\n")
local json = string.format('{"discussion_id": "%s", "note_id": %s, "comment": "%s"}', state.ACTIVE_DISCUSSION,
state.ACTIVE_NOTE, escapedText)
job.run_job("comment", "PATCH", json, function()
vim.schedule(function()
local node = state.tree:get_node("-" .. state.ACTIVE_NOTE)
local childrenIds = node:get_child_ids()
for _, value in ipairs(childrenIds) do
state.tree:remove_node(value)
end
local newNoteTextNodes = {}
for bodyLine in note.body:gmatch("[^\n]+") do
for bodyLine in text:gmatch("[^\n]+") do
table.insert(newNoteTextNodes, NuiTree.Node({ text = bodyLine, is_body = true }, {}))
end
@@ -215,9 +181,7 @@ M.send_edits = function(text)
u.darken_metadata(buf, '')
notify("Edited comment!")
end)
end,
on_stderr = u.print_error
}):start()
end)
end
return M

View File

@@ -1,5 +1,6 @@
local u = require("gitlab.utils")
local NuiTree = require("nui.tree")
local job = require("gitlab.job")
local notify = require("notify")
local state = require("gitlab.state")
local Job = require("plenary.job")
@@ -17,22 +18,10 @@ M.reply = function()
end
M.send_reply = function(text)
Job:new({
command = state.BIN,
args = {
"reply",
state.PROJECT_ID,
state.ACTIVE_DISCUSSION,
text,
},
on_stdout = function(_, line)
local note = vim.json.decode(line)
if note == nil then
notify("There was an issue creating the note", "error")
return
end
local note_node = M.build_note(note)
local escapedText = string.gsub(text, "\n", "\\n")
local json = string.format('{"discussion_id": "%s", "reply": "%s"}', state.ACTIVE_DISCUSSION, escapedText)
job.run_job("reply", "POST", json, function(data)
local note_node = M.build_note(data.note)
note_node:expand()
state.tree:add_node(note_node, "-" .. state.ACTIVE_DISCUSSION)
@@ -42,24 +31,24 @@ M.send_reply = function(text)
u.darken_metadata(buf, '')
notify("Sent reply!")
end)
end,
on_stderr = u.print_error
}):start()
end)
end
-- Places all of the discussions into a readable list
M.list_discussions = function()
if u.base_invalid() then return end
Job:new({
command = state.BIN,
args = { "listDiscussions", state.PROJECT_ID },
on_stdout = function(_, line)
local discussions = vim.json.decode(line)
if (type(discussions) == 'userdata') then
notify("No discussions found for this MR", "warn")
command = "curl",
args = { "-s", "localhost:8081/" .. "discussions" },
on_stdout = function(_, output)
local data_ok, data = pcall(vim.json.decode, output)
if data_ok and data ~= nil then
local status = (data.status >= 200 and data.status < 300) and "success" or "error"
if status == "error" then
notify("Could not fetch discussions!", "error")
return
end
M.discussions = discussions
M.discussions = data.discussions
vim.schedule(function()
vim.cmd.tabnew()
local buf = vim.api.nvim_create_buf(false, true)
@@ -67,7 +56,7 @@ M.list_discussions = function()
vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown')
vim.api.nvim_set_current_buf(buf)
local allDiscussions = {}
for i, discussion in ipairs(discussions) do
for i, discussion in ipairs(data.discussions) do
local discussionChildren = {}
for _, note in ipairs(discussion.notes) do
local note_node = M.build_note(note)
@@ -96,8 +85,12 @@ M.list_discussions = function()
u.darken_metadata(buf, '')
M.jump_to_file()
end)
end
end,
on_stderr = function(_, output)
notify("Could not run approve command!", "error")
error(output)
end,
on_stderr = u.print_error,
}):start()
end

View File

@@ -1,19 +1,18 @@
local Job = require("plenary.job")
local curl = require("plenary.curl")
local state = require("gitlab.state")
local notify = require("notify")
local discussions = require("gitlab.discussions")
local summary = require("gitlab.summary")
local keymaps = require("gitlab.keymaps")
local comment = require("gitlab.comment")
local approve = require("gitlab.approve")
local revoke = require("gitlab.revoke")
local job = require("gitlab.job")
local u = require("gitlab.utils")
-- Root Module Scope
local M = {}
M.summary = summary.summary
M.approve = approve.approve
M.revoke = revoke.revoke
M.approve = job.approve
M.revoke = job.revoke
M.create_comment = comment.create_comment
M.list_discussions = discussions.list_discussions
M.edit_comment = comment.edit_comment
@@ -21,8 +20,6 @@ M.delete_comment = comment.delete_comment
M.reply = discussions.reply
-- Builds the Go binary, initializes the plugin, fetches MR info
local projectData = {}
M.build = function(args)
if args == nil then args = {} end
local command = string.format("cd %s && make", state.BIN_PATH)
@@ -42,6 +39,9 @@ M.setup = function(args, build_only)
state.BIN = parent_dir .. "/bin"
if args == nil then args = {} end
if args.dev == true then
M.build(args)
end
local binExists = io.open(state.BIN, "r")
if not binExists or args.dev == true then
@@ -78,29 +78,34 @@ M.setup = function(args, build_only)
state.BASE_BRANCH = args.base_branch
end
local error_message = "Failed to set up gitlab.nvim, could not get project information."
if u.is_gitlab_repo() then
Job:new({
command = state.BIN,
args = { "info", state.PROJECT_ID },
on_stdout = function(_, line)
table.insert(projectData, line)
end,
on_stderr = u.print_error,
on_exit = function()
if projectData[1] ~= nil then
local parsed_ok, data = pcall(vim.json.decode, projectData[1])
if parsed_ok ~= true then
notify("Failed calling setup. Could not get project data.", "error")
local port = args.port or 21036
vim.fn.jobstart(state.BIN .. " " .. state.PROJECT_ID .. " " .. port, {
on_stdout = function(job_id)
if job_id <= 0 then
notify(error_message, "error")
return
else
local response_ok, response = pcall(curl.get, "localhost:" .. port .. "/info",
{ timeout = 750 })
if response == nil or not response_ok then
notify(error_message, "error")
return
end
local body = response.body
local parsed_ok, data = pcall(vim.json.decode, body)
if parsed_ok ~= true then
notify(error_message, "error")
return
end
state.INFO = data
end
end
end,
}):start()
end
keymaps.set_keymap_keys(args.keymaps)
keymaps.set_keymaps()
end
end
})
end
end
M.current_file_path = function()

46
lua/gitlab/job.lua Normal file
View File

@@ -0,0 +1,46 @@
local notify = require("notify")
local Job = require("plenary.job")
local M = {}
M.run_job = function(endpoint, method, body, callback)
local args = { "-s", "-X", (method or "POST"), "localhost:8081/" .. endpoint }
if body ~= nil then
table.insert(args, 1, "-d")
table.insert(args, 2, body)
end
Job:new({
command = "curl",
args = args,
on_stdout = function(_, output)
local data_ok, data = pcall(vim.json.decode, output)
if data_ok and data ~= nil then
local status = (data.status >= 200 and data.status < 300) and "success" or "error"
if callback ~= nil then
callback(data)
else
notify(data.message, status)
end
else
notify("Could not parse command output!", "error")
end
end,
on_stderr = function(_, output)
notify("Could not run command!", "error")
error(output)
end
}):start()
end
-- Approves the current merge request
M.approve = function()
M.run_job("approve", "POST")
end
-- Revokes approval for the current merge request
M.revoke = function()
M.run_job("revoke", "POST")
end
return M

View File

@@ -1,17 +0,0 @@
local u = require("gitlab.utils")
local state = require("gitlab.state")
local M = {}
local Job = require("plenary.job")
-- Revokes approval for the current merge request
M.revoke = function()
if u.base_invalid() then return end
Job:new({
command = state.BIN,
args = { "revoke", state.PROJECT_ID },
on_stdout = u.print_success,
on_stderr = u.print_error
}):start()
end
return M

View File

@@ -239,7 +239,6 @@ local split_diff_view_filename = function(filename)
return hash, path
end
M.get_relative_file_path = get_relative_file_path
M.get_current_line_number = get_current_line_number
M.get_buffer_text = get_buffer_text