Skip to content

Commit

Permalink
2.1.0 (#241)
Browse files Browse the repository at this point in the history
• removes the <esc> keybinding for popups which was causing folks to lose their changes
• deprecates the backup register.
• updates go-gitlab to latest in order to get "drafts" functionality
• fixes issues with labels not deleting correctly
• creates a new data() function to get data from the plugin directly, see :h gitlab.nvim.data
• fixes issues with line values not being computed directly, blocking jumps to/from discussion tree

This is a #MINOR release.
  • Loading branch information
harrisoncramer authored Apr 8, 2024
1 parent 12c4acb commit 36f512c
Show file tree
Hide file tree
Showing 19 changed files with 298 additions and 176 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ require("gitlab").setup({
},
help = "g?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
popup = { -- The popup for comment creation, editing, and replying
exit = "<Esc>",
perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc)
perform_linewise_action = "<leader>l", -- Once in normal mode, does the linewise action (see logs for this job, etc)
width = "40%",
Expand All @@ -128,7 +127,6 @@ require("gitlab").setup({
pipeline = nil,
reply = nil,
squash_message = nil,
backup_register = nil,
},
discussion_tree = { -- The discussion tree that holds all comments
auto_open = true, -- Automatically open when the reviewer is opened
Expand Down
4 changes: 0 additions & 4 deletions cmd/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ func GetCurrentBranchNameFromNativeGitCmd() (res string, e error) {

branchName := strings.TrimSpace(string(output))

if branchName == "main" || branchName == "master" {
return "", fmt.Errorf("Cannot run on %s branch", branchName)
}

return branchName, nil
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (a *api) updateLabels(w http.ResponseWriter, r *http.Request) {
return
}

var labels = gitlab.Labels(labelUpdateRequest.Labels)
var labels = gitlab.LabelOptions(labelUpdateRequest.Labels)
mr, res, err := a.client.UpdateMergeRequest(a.projectInfo.ProjectId, a.projectInfo.MergeId, &gitlab.UpdateMergeRequestOptions{
Labels: &labels,
})
Expand Down
47 changes: 33 additions & 14 deletions cmd/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ import (

type RetriggerPipelineResponse struct {
SuccessResponse
Pipeline *gitlab.Pipeline
LatestPipeline *gitlab.Pipeline `json:"latest_pipeline"`
}

type GetJobsResponse struct {
type PipelineWithJobs struct {
Jobs []*gitlab.Job `json:"jobs"`
LatestPipeline *gitlab.Pipeline `json:"latest_pipeline"`
}

type GetPipelineAndJobsResponse struct {
SuccessResponse
Jobs []*gitlab.Job
Pipeline PipelineWithJobs `json:"latest_pipeline"`
}

/*
Expand All @@ -27,7 +32,7 @@ about a given job in a pipeline, see the jobHandler function
func (a *api) pipelineHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
a.GetJobs(w, r)
a.GetPipelineAndJobs(w, r)
case http.MethodPost:
a.RetriggerPipeline(w, r)
default:
Expand All @@ -37,18 +42,29 @@ func (a *api) pipelineHandler(w http.ResponseWriter, r *http.Request) {
}
}

func (a *api) GetJobs(w http.ResponseWriter, r *http.Request) {
func (a *api) GetPipelineAndJobs(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

id := strings.TrimPrefix(r.URL.Path, "/pipeline/")
idInt, err := strconv.Atoi(id)
pipeline, res, err := a.client.GetLatestPipeline(a.projectInfo.ProjectId, &gitlab.GetLatestPipelineOptions{
Ref: &a.gitInfo.BranchName,
})

if err != nil {
handleError(w, err, "Could not convert pipeline ID to integer", http.StatusBadRequest)
handleError(w, err, fmt.Sprintf("Gitlab failed to get latest pipeline for %s branch", a.gitInfo.BranchName), http.StatusInternalServerError)
return
}

if res.StatusCode >= 300 {
handleError(w, GenericError{endpoint: "/pipeline"}, fmt.Sprintf("Could not get latest pipeline for %s branch", a.gitInfo.BranchName), res.StatusCode)
return
}

if pipeline == nil {
handleError(w, GenericError{endpoint: "/pipeline"}, fmt.Sprintf("No pipeline found for %s branch", a.gitInfo.BranchName), res.StatusCode)
return
}

jobs, res, err := a.client.ListPipelineJobs(a.projectInfo.ProjectId, idInt, &gitlab.ListJobsOptions{})
jobs, res, err := a.client.ListPipelineJobs(a.projectInfo.ProjectId, pipeline.ID, &gitlab.ListJobsOptions{})

if err != nil {
handleError(w, err, "Could not get pipeline jobs", http.StatusInternalServerError)
Expand All @@ -61,12 +77,15 @@ func (a *api) GetJobs(w http.ResponseWriter, r *http.Request) {
}

w.WriteHeader(http.StatusOK)
response := GetJobsResponse{
response := GetPipelineAndJobsResponse{
SuccessResponse: SuccessResponse{
Status: http.StatusOK,
Message: "Pipeline jobs retrieved",
Message: "Pipeline retrieved",
},
Pipeline: PipelineWithJobs{
LatestPipeline: pipeline,
Jobs: jobs,
},
Jobs: jobs,
}

err = json.NewEncoder(w).Encode(response)
Expand All @@ -78,7 +97,7 @@ func (a *api) GetJobs(w http.ResponseWriter, r *http.Request) {
func (a *api) RetriggerPipeline(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

id := strings.TrimPrefix(r.URL.Path, "/pipeline/")
id := strings.TrimPrefix(r.URL.Path, "/pipeline/trigger/")

idInt, err := strconv.Atoi(id)
if err != nil {
Expand All @@ -104,7 +123,7 @@ func (a *api) RetriggerPipeline(w http.ResponseWriter, r *http.Request) {
Message: "Pipeline retriggered",
Status: http.StatusOK,
},
Pipeline: pipeline,
LatestPipeline: pipeline,
}

err = json.NewEncoder(w).Encode(response)
Expand Down
69 changes: 47 additions & 22 deletions cmd/pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,54 +32,79 @@ func retryPipelineBuildNon200(pid interface{}, pipeline int, options ...gitlab.R
return nil, makeResponse(http.StatusSeeOther), nil
}

func getLatestPipeline200(pid interface{}, opts *gitlab.GetLatestPipelineOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error) {
return &gitlab.Pipeline{ID: 1}, makeResponse(http.StatusOK), nil
}

func TestPipelineHandler(t *testing.T) {
t.Run("Gets all pipeline jobs", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/pipeline/1", nil)
server, _ := createRouterAndApi(fakeClient{listPipelineJobs: listPipelineJobs})
data := serveRequest(t, server, request, GetJobsResponse{})
assert(t, data.SuccessResponse.Message, "Pipeline jobs retrieved")
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
server, _ := createRouterAndApi(fakeClient{
listPipelineJobs: listPipelineJobs,
getLatestPipeline: getLatestPipeline200,
})
data := serveRequest(t, server, request, GetPipelineAndJobsResponse{})
assert(t, data.SuccessResponse.Message, "Pipeline retrieved")
assert(t, data.SuccessResponse.Status, http.StatusOK)
})

t.Run("Disallows non-GET, non-POST methods", func(t *testing.T) {
request := makeRequest(t, http.MethodPatch, "/pipeline/1", nil)
server, _ := createRouterAndApi(fakeClient{listPipelineJobs: listPipelineJobs})
request := makeRequest(t, http.MethodPatch, "/pipeline", nil)
server, _ := createRouterAndApi(fakeClient{
listPipelineJobs: listPipelineJobs,
getLatestPipeline: getLatestPipeline200,
})
data := serveRequest(t, server, request, ErrorResponse{})
checkBadMethod(t, *data, http.MethodGet, http.MethodPost)
})

t.Run("Handles errors from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/pipeline/1", nil)
server, _ := createRouterAndApi(fakeClient{listPipelineJobs: listPipelineJobsErr})
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
server, _ := createRouterAndApi(fakeClient{
listPipelineJobs: listPipelineJobsErr,
getLatestPipeline: getLatestPipeline200,
})
data := serveRequest(t, server, request, ErrorResponse{})
checkErrorFromGitlab(t, *data, "Could not get pipeline jobs")
})

t.Run("Handles non-200s from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/pipeline/1", nil)
server, _ := createRouterAndApi(fakeClient{listPipelineJobs: listPipelineJobsNon200})
request := makeRequest(t, http.MethodGet, "/pipeline", nil)
server, _ := createRouterAndApi(fakeClient{
listPipelineJobs: listPipelineJobsNon200,
getLatestPipeline: getLatestPipeline200,
})
data := serveRequest(t, server, request, ErrorResponse{})
checkNon200(t, *data, "Could not get pipeline jobs", "/pipeline")
})

t.Run("Retriggers pipeline", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/pipeline/1", nil)
server, _ := createRouterAndApi(fakeClient{retryPipelineBuild: retryPipelineBuild})
data := serveRequest(t, server, request, GetJobsResponse{})
assert(t, data.SuccessResponse.Message, "Pipeline retriggered")
assert(t, data.SuccessResponse.Status, http.StatusOK)
})

t.Run("Handles errors from Gitlab client", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/pipeline/1", nil)
server, _ := createRouterAndApi(fakeClient{retryPipelineBuild: retryPipelineBuildErr})
request := makeRequest(t, http.MethodPost, "/pipeline/trigger/1", nil)
server, _ := createRouterAndApi(fakeClient{
retryPipelineBuild: retryPipelineBuildErr,
getLatestPipeline: getLatestPipeline200,
})
data := serveRequest(t, server, request, ErrorResponse{})
checkErrorFromGitlab(t, *data, "Could not retrigger pipeline")
})

t.Run("Retriggers pipeline", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/pipeline/trigger/1", nil)
server, _ := createRouterAndApi(fakeClient{
retryPipelineBuild: retryPipelineBuild,
getLatestPipeline: getLatestPipeline200,
})
data := serveRequest(t, server, request, GetPipelineAndJobsResponse{})
assert(t, data.SuccessResponse.Message, "Pipeline retriggered")
assert(t, data.SuccessResponse.Status, http.StatusOK)
})

t.Run("Handles non-200s from Gitlab client on retrigger", func(t *testing.T) {
request := makeRequest(t, http.MethodPost, "/pipeline/1", nil)
server, _ := createRouterAndApi(fakeClient{retryPipelineBuild: retryPipelineBuildNon200})
request := makeRequest(t, http.MethodPost, "/pipeline/trigger/1", nil)
server, _ := createRouterAndApi(fakeClient{
retryPipelineBuild: retryPipelineBuildNon200,
getLatestPipeline: getLatestPipeline200,
})
data := serveRequest(t, server, request, ErrorResponse{})
checkNon200(t, *data, "Could not retrigger pipeline", "/pipeline")
})
Expand Down
3 changes: 2 additions & 1 deletion cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,12 @@ func createRouterAndApi(client ClientInterface, optFuncs ...optFunc) (*http.Serv
m.HandleFunc("/mr/revoke", a.withMr(a.revokeHandler))
m.HandleFunc("/mr/awardable/note/", a.withMr(a.emojiNoteHandler))

m.HandleFunc("/pipeline", a.pipelineHandler)
m.HandleFunc("/pipeline/trigger/", a.pipelineHandler)
m.HandleFunc("/users/me", a.meHandler)
m.HandleFunc("/attachment", a.attachmentHandler)
m.HandleFunc("/create_mr", a.createMr)
m.HandleFunc("/job", a.jobHandler)
m.HandleFunc("/pipeline/", a.pipelineHandler)
m.HandleFunc("/project/members", a.projectMembersHandler)
m.HandleFunc("/shutdown", a.shutdownHandler)

Expand Down
5 changes: 5 additions & 0 deletions cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type fakeClient struct {
listAllProjectMembers func(pid interface{}, opt *gitlab.ListProjectMembersOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectMember, *gitlab.Response, error)
retryPipelineBuild func(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)
listPipelineJobs func(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error)
getLatestPipeline func(pid interface{}, opt *gitlab.GetLatestPipelineOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)
getTraceFile func(pid interface{}, jobID int, options ...gitlab.RequestOptionFunc) (*bytes.Reader, *gitlab.Response, error)
listLabels func(pid interface{}, opt *gitlab.ListLabelsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Label, *gitlab.Response, error)
listMergeRequestAwardEmojiOnNote func(pid interface{}, mergeRequestIID, noteID int, opt *gitlab.ListAwardEmojiOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.AwardEmoji, *gitlab.Response, error)
Expand Down Expand Up @@ -120,6 +121,10 @@ func (f fakeClient) ListPipelineJobs(pid interface{}, pipelineID int, opts *gitl
return f.listPipelineJobs(pid, pipelineID, opts, options...)
}

func (f fakeClient) GetLatestPipeline(pid interface{}, opts *gitlab.GetLatestPipelineOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error) {
return f.getLatestPipeline(pid, opts, options...)
}

func (f fakeClient) GetTraceFile(pid interface{}, jobID int, options ...gitlab.RequestOptionFunc) (*bytes.Reader, *gitlab.Response, error) {
return f.getTraceFile(pid, jobID, options...)
}
Expand Down
1 change: 1 addition & 0 deletions cmd/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type ClientInterface interface {
ListAllProjectMembers(pid interface{}, opt *gitlab.ListProjectMembersOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.ProjectMember, *gitlab.Response, error)
RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)
ListPipelineJobs(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error)
GetLatestPipeline(pid interface{}, opt *gitlab.GetLatestPipelineOptions, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)
GetTraceFile(pid interface{}, jobID int, options ...gitlab.RequestOptionFunc) (*bytes.Reader, *gitlab.Response, error)
ListLabels(pid interface{}, opt *gitlab.ListLabelsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Label, *gitlab.Response, error)
ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID int, noteID int, opt *gitlab.ListAwardEmojiOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.AwardEmoji, *gitlab.Response, error)
Expand Down
52 changes: 34 additions & 18 deletions doc/gitlab.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ you call this function with no values the defaults will be used:
},
help = "g?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
popup = { -- The popup for comment creation, editing, and replying
exit = "<Esc>",
perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc)
perform_linewise_action = "<leader>l", -- Once in normal mode, does the linewise action (see logs for this job, etc)
width = "40%",
Expand All @@ -159,7 +158,6 @@ you call this function with no values the defaults will be used:
pipeline = nil,
reply = nil,
squash_message = nil,
backup_register = nil,
},
discussion_tree = { -- The discussion tree that holds all comments
auto_open = true, -- Automatically open when the reviewer is opened
Expand Down Expand Up @@ -305,22 +303,6 @@ code block with prefilled code from the visual selection.
Just like the summary, all the different kinds of comments are saved via the
`settings.popup.perform_action` keybinding.

BACKUP REGISTER *gitlab.nvim.backup-register*

Sometimes, the action triggered by `settings.popup.perform_action` can fail.
To prevent losing your carefully crafted note/comment/suggestion you can set
`settings.popup.backup_register` to a writable register (see |registers|) to
which the contents of the popup window will be saved just before the action is
performed. A practical setting is `settings.popup.backup_register = "+"` which
saves to the system clipboard (see |quoteplus|). This lets you easily apply
the action on Gitlab in a browser, if it keeps failing in `gitlab.nvim`.

If you experience such problems, please first read the
|gitlab.nvim.troubleshooting| section. If it does not help, see if there are
any relevant known <https://github.com/harrisoncramer/gitlab.nvim/issues>. If
there are none, please open one and provide any error messages that
`gitlab.nvim` may be showing.

DISCUSSIONS AND NOTES *gitlab.nvim.discussions-and-notes*

Gitlab groups threads of comments together into "discussions."
Expand Down Expand Up @@ -751,4 +733,38 @@ Merges the merge request into the target branch
>lua
require("gitlab").merge()

gitlab.data({ opts }, cb) *gitlab.nvim.data*

The data function can be used to integrate `gitlab.nvim` with other plugins and tooling, by fetching
raw data about the current MR, including the summary information (title, description, etc);
reviewers, assignees, pipeline status.
>lua
require("gitlab").data({
{ type = "info", refresh = false },
{ type = "user", refresh = false } }, function (data)
vim.print("The info data is: ", data.info)
vim.print("The user data is: ", data.user)
end)

If the resources have not yet been fetched from Gitlab, this function will
perform API calls for them. Once the data has been fetched, the callback will
execute and passed the data as an argument.

Parameters: ~
{resources} (table) A list of resource blocks to fetch.
{resource} (table) A resource to fetch, such as job information, etc.
{resource.type}: (string) The type of resource, either: "user"
"labels", "project_members", "pipeline," or "revisions"." The types are:
{user}: Information about the currently authenticated user
{labels}: The labels available in the current project
• {project_members}: The list of current project members
{revisions}: Revision information about the MR
{pipeline}: Information about the current branch's pipeline. Returns
and object with `latest_pipeline` and `jobs` as fields.
{resource.refresh}: (bool) Whether to re-fetch the data from Gitlab
or use the cached data locally, if available.
{cb} (function) The callback function that runs after all of the
resources have been fetched. Will be passed a table with the data,
with each resource as a key-value pair, with the key being it's type.

vim:tw=78:ts=8:noet:ft=help:norl:
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ module gitlab.com/harrisoncramer/gitlab.nvim

go 1.19

require github.com/xanzy/go-gitlab v0.93.2
require (
github.com/hashicorp/go-retryablehttp v0.7.2
github.com/xanzy/go-gitlab v0.102.0
)

require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/time v0.3.0 // indirect
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/xanzy/go-gitlab v0.83.0 h1:37p0MpTPNbsTMKX/JnmJtY8Ch1sFiJzVF342+RvZEGw=
github.com/xanzy/go-gitlab v0.83.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
github.com/xanzy/go-gitlab v0.93.2 h1:kNNf3BYNYn/Zkig0B89fma12l36VLcYSGu7OnaRlRDg=
github.com/xanzy/go-gitlab v0.93.2/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
Expand Down
Loading

0 comments on commit 36f512c

Please sign in to comment.