Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ If you are making changes to the Go codebase, don't forget to run `make compile`

3. Apply formatters and linters to your changes

For changes to the Go codebase: We use <a href="https://pkg.go.dev/cmd/gofmt">gofmt</a> to check formatting and <a href="https://github.com/golangci/golangci-lint">golangci-lint</a> to check linting. Run these commands in the root of the repository:
For changes to the Go codebase: We use <a href="https://pkg.go.dev/cmd/gofmt">gofmt</a> to check formatting and <a href="https://github.com/golangci/golangci-lint">golangci-lint</a> to check linting, and <a href="https://staticcheck.dev/">staticcheck</a>. Run these commands in the root of the repository:

```bash
$ go fmt ./...
$ golangci-lint run
$ golangci-lint run ./...
$ staticcheck ./...
```

If you are writing tests and have added something to the Go client, you can test with:
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.19'
go-version: '1.23.1'
cache: false
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.54
version: v1.61.0
only-new-issues: true
skip-cache: true
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@2024.1.1
- name: Run staticcheck
run: staticcheck ./...
go_test:
name: Test Go 🧪
needs: [go_lint]
Expand Down
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
run:
tests: true
timeout: 30s
2 changes: 1 addition & 1 deletion cmd/app/assignee.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (a assigneesService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
assigneeUpdateRequest, ok := r.Context().Value(payload("payload")).(*AssigneeUpdateRequest)

if !ok {
handleError(w, errors.New("Could not get payload from context"), "Bad payload", http.StatusInternalServerError)
handleError(w, errors.New("could not get payload from context"), "Bad payload", http.StatusInternalServerError)
return
}

Expand Down
22 changes: 11 additions & 11 deletions cmd/app/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ type Client struct {
}

/* NewClient parses and validates the project settings and initializes the Gitlab client. */
func NewClient() (error, *Client) {
func NewClient() (*Client, error) {

if pluginOptions.GitlabUrl == "" {
return errors.New("GitLab instance URL cannot be empty"), nil
return nil, errors.New("GitLab instance URL cannot be empty")
}

var apiCustUrl = fmt.Sprintf(pluginOptions.GitlabUrl + "/api/v4")
var apiCustUrl = fmt.Sprintf("%s/api/v4", pluginOptions.GitlabUrl)

gitlabOptions := []gitlab.ClientOptionFunc{
gitlab.WithBaseURL(apiCustUrl),
Expand Down Expand Up @@ -73,10 +73,10 @@ func NewClient() (error, *Client) {
client, err := gitlab.NewClient(pluginOptions.AuthToken, gitlabOptions...)

if err != nil {
return fmt.Errorf("Failed to create client: %v", err), nil
return nil, fmt.Errorf("failed to create client: %v", err)
}

return nil, &Client{
return &Client{
MergeRequestsService: client.MergeRequests,
MergeRequestApprovalsService: client.MergeRequestApprovals,
DiscussionsService: client.Discussions,
Expand All @@ -88,28 +88,28 @@ func NewClient() (error, *Client) {
AwardEmojiService: client.AwardEmoji,
UsersService: client.Users,
DraftNotesService: client.DraftNotes,
}
}, nil
}

/* InitProjectSettings fetch the project ID using the client */
func InitProjectSettings(c *Client, gitInfo git.GitData) (error, *ProjectInfo) {
func InitProjectSettings(c *Client, gitInfo git.GitData) (*ProjectInfo, error) {

opt := gitlab.GetProjectOptions{}
project, _, err := c.GetProject(gitInfo.ProjectPath(), &opt)

if err != nil {
return fmt.Errorf(fmt.Sprintf("Error getting project at %s", gitInfo.RemoteUrl), err), nil
return nil, fmt.Errorf(fmt.Sprintf("Error getting project at %s", gitInfo.RemoteUrl), err)
}

if project == nil {
return fmt.Errorf(fmt.Sprintf("Could not find project at %s", gitInfo.RemoteUrl), err), nil
return nil, fmt.Errorf(fmt.Sprintf("Could not find project at %s", gitInfo.RemoteUrl), err)
}

projectId := fmt.Sprint(project.ID)

return nil, &ProjectInfo{
return &ProjectInfo{
ProjectId: projectId,
}
}, nil
}

/* handleError is a utililty handler that returns errors to the client along with their statuses and messages */
Expand Down
2 changes: 1 addition & 1 deletion cmd/app/draft_notes.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (a draftNoteService) updateDraftNote(w http.ResponseWriter, r *http.Request
payload := r.Context().Value(payload("payload")).(*UpdateDraftNoteRequest)

if payload.Note == "" {
handleError(w, errors.New("Draft note text missing"), "Must provide draft note text", http.StatusBadRequest)
handleError(w, errors.New("draft note text missing"), "Must provide draft note text", http.StatusBadRequest)
return
}

Expand Down
11 changes: 8 additions & 3 deletions cmd/app/emoji.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func (a emojiService) deleteEmojiFromNote(w http.ResponseWriter, r *http.Request
suffix := strings.TrimPrefix(r.URL.Path, "/mr/awardable/note/")
ids := strings.Split(suffix, "/")

if len(ids) < 2 {
handleError(w, errors.New("missing IDs"), "Must provide note ID and awardable ID", http.StatusBadRequest)
return
}

noteId, err := strconv.Atoi(ids[0])
if err != nil {
handleError(w, err, "Could not convert note ID to integer", http.StatusBadRequest)
Expand Down Expand Up @@ -158,18 +163,18 @@ func attachEmojis(a *data, fr FileReader) error {
reader, err := fr.ReadFile(filePath)

if err != nil {
return fmt.Errorf("Could not find emojis at %s", filePath)
return fmt.Errorf("could not find emojis at %s", filePath)
}

bytes, err := io.ReadAll(reader)
if err != nil {
return errors.New("Could not read emoji file")
return errors.New("could not read emoji file")
}

var emojiMap EmojiMap
err = json.Unmarshal(bytes, &emojiMap)
if err != nil {
return errors.New("Could not unmarshal emojis")
return errors.New("could not unmarshal emojis")
}

a.emojiMap = emojiMap
Expand Down
16 changes: 8 additions & 8 deletions cmd/app/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ Gitlab project and the branch must be a feature branch
func NewGitData(remote string, g GitManager) (GitData, error) {
err := g.RefreshProjectInfo(remote)
if err != nil {
return GitData{}, fmt.Errorf("Could not get latest information from remote: %v", err)
return GitData{}, fmt.Errorf("could not get latest information from remote: %v", err)
}

url, err := g.GetProjectUrlFromNativeGitCmd(remote)
if err != nil {
return GitData{}, fmt.Errorf("Could not get project Url: %v", err)
return GitData{}, fmt.Errorf("could not get project Url: %v", err)
}

/*
Expand All @@ -62,15 +62,15 @@ func NewGitData(remote string, g GitManager) (GitData, error) {
re := regexp.MustCompile(`^(?:git@[^\/:]*|https?:\/\/[^\/]+|ssh:\/\/[^\/:]+)(?::\d+)?[\/:](.*)\/([^\/]+?)(?:\.git)?\/?$`)
matches := re.FindStringSubmatch(url)
if len(matches) != 3 {
return GitData{}, fmt.Errorf("Invalid Git URL format: %s", url)
return GitData{}, fmt.Errorf("invalid git URL format: %s", url)
}

namespace := matches[1]
projectName := matches[2]

branchName, err := g.GetCurrentBranchNameFromNativeGitCmd()
if err != nil {
return GitData{}, fmt.Errorf("Failed to get current branch: %v", err)
return GitData{}, fmt.Errorf("failed to get current branch: %v", err)
}

return GitData{
Expand All @@ -88,7 +88,7 @@ func (g Git) GetCurrentBranchNameFromNativeGitCmd() (res string, e error) {

output, err := gitCmd.Output()
if err != nil {
return "", fmt.Errorf("Error running git rev-parse: %w", err)
return "", fmt.Errorf("error running git rev-parse: %w", err)
}

branchName := strings.TrimSpace(string(output))
Expand All @@ -101,7 +101,7 @@ func (g Git) GetProjectUrlFromNativeGitCmd(remote string) (string, error) {
cmd := exec.Command("git", "remote", "get-url", remote)
url, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("Could not get remote")
return "", fmt.Errorf("could not get remote")
}

return strings.TrimSpace(string(url)), nil
Expand All @@ -112,7 +112,7 @@ func (g Git) RefreshProjectInfo(remote string) error {
cmd := exec.Command("git", "fetch", remote)
_, err := cmd.Output()
if err != nil {
return fmt.Errorf("Failed to run `git fetch %s`: %v", remote, err)
return fmt.Errorf("failed to run `git fetch %s`: %v", remote, err)
}

return nil
Expand All @@ -123,7 +123,7 @@ func (g Git) GetLatestCommitOnRemote(remote string, branchName string) (string,

out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("Failed to run `git log -1 --format=%%H " + fmt.Sprintf("%s/%s", remote, branchName))
return "", fmt.Errorf("failed to run `git log -1 --format=%%H %s/%s`", remote, branchName)
}

commit := strings.TrimSpace(string(out))
Expand Down
2 changes: 1 addition & 1 deletion cmd/app/list_discussions.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (a discussionsListerService) ServeHTTP(w http.ResponseWriter, r *http.Reque
var linkedDiscussions []*gitlab.Discussion

for _, discussion := range discussions {
if discussion.Notes == nil || len(discussion.Notes) == 0 || Contains(request.Blacklist, discussion.Notes[0].Author.Username) {
if len(discussion.Notes) == 0 || Contains(request.Blacklist, discussion.Notes[0].Author.Username) {
continue
}
for _, note := range discussion.Notes {
Expand Down
6 changes: 3 additions & 3 deletions cmd/app/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (l LoggingServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Request: r,
}
if pluginOptions.Debug.Response {
logResponse("RESPONSE FROM GO SERVER", resp)
logResponse("RESPONSE FROM GO SERVER", resp) //nolint:errcheck
}
}

Expand All @@ -62,7 +62,7 @@ func logRequest(prefix string, r *http.Request) {
os.Exit(1)
}
r.Header.Set("Private-Token", token)
_, err = file.Write([]byte(fmt.Sprintf("\n-- %s --\n%s\n", prefix, res))) //nolint:all
fmt.Fprintf(file, "\n-- %s --\n%s\n", prefix, res) //nolint:errcheck
}

func logResponse(prefix string, r *http.Response) {
Expand All @@ -75,7 +75,7 @@ func logResponse(prefix string, r *http.Response) {
os.Exit(1)
}

_, err = file.Write([]byte(fmt.Sprintf("\n-- %s --\n%s\n", prefix, res))) //nolint:all
fmt.Fprintf(file, "\n-- %s --\n%s\n", prefix, res) //nolint:errcheck
}

func openLogFile() *os.File {
Expand Down
4 changes: 2 additions & 2 deletions cmd/app/merge_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (a mergeRequestListerService) ServeHTTP(w http.ResponseWriter, r *http.Requ
}

if len(mergeRequests) == 0 {
handleError(w, errors.New("No merge requests found"), "No merge requests found", http.StatusNotFound)
handleError(w, errors.New("no merge requests found"), "No merge requests found", http.StatusNotFound)
return
}

Expand All @@ -60,6 +60,6 @@ func (a mergeRequestListerService) ServeHTTP(w http.ResponseWriter, r *http.Requ

err = json.NewEncoder(w).Encode(response)
if err != nil {
handleError(w, err, "Could not encode response", http.StatusInternalServerError)
handleError(w, err, "could not encode response", http.StatusInternalServerError)
}
}
2 changes: 1 addition & 1 deletion cmd/app/merge_requests_by_username_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestListMergeRequestByUsername(t *testing.T) {
)
data, status := getFailData(t, svc, request)
assert(t, data.Message, "An error occurred")
assert(t, data.Details, strings.Repeat("Some error from Gitlab; ", 3))
assert(t, data.Details, strings.Repeat("some error from Gitlab; ", 3))
assert(t, status, http.StatusInternalServerError)
})

Expand Down
12 changes: 6 additions & 6 deletions cmd/app/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,18 @@ func (m withMrMiddleware) handle(next http.Handler) http.Handler {

mergeRequests, _, err := m.client.ListProjectMergeRequests(m.data.projectInfo.ProjectId, &options)
if err != nil {
handleError(w, fmt.Errorf("Failed to list merge requests: %w", err), "Failed to list merge requests", http.StatusInternalServerError)
handleError(w, fmt.Errorf("failed to list merge requests: %w", err), "Failed to list merge requests", http.StatusInternalServerError)
return
}

if len(mergeRequests) == 0 {
err := fmt.Errorf("Branch '%s' does not have any merge requests", m.data.gitInfo.BranchName)
err := fmt.Errorf("branch '%s' does not have any merge requests", m.data.gitInfo.BranchName)
handleError(w, err, "No MRs Found", http.StatusNotFound)
return
}

if len(mergeRequests) > 1 {
err := errors.New("Please call gitlab.choose_merge_request()")
err := errors.New("please call gitlab.choose_merge_request()")
handleError(w, err, "Multiple MRs found", http.StatusBadRequest)
return
}
Expand Down Expand Up @@ -155,9 +155,9 @@ func withMethodCheck(methods ...string) mw {
}

// Helper function to format validation errors into more readable strings
func formatValidationErrors(errors validator.ValidationErrors) error {
func formatValidationErrors(errs validator.ValidationErrors) error {
var s strings.Builder
for i, e := range errors {
for i, e := range errs {
if i > 0 {
s.WriteString("; ")
}
Expand All @@ -169,5 +169,5 @@ func formatValidationErrors(errors validator.ValidationErrors) error {
}
}

return fmt.Errorf(s.String())
return errors.New(s.String())
}
4 changes: 2 additions & 2 deletions cmd/app/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestWithMrMiddleware(t *testing.T) {
data, status := getFailData(t, handler, request)
assert(t, status, http.StatusNotFound)
assert(t, data.Message, "No MRs Found")
assert(t, data.Details, "Branch 'foo' does not have any merge requests")
assert(t, data.Details, "branch 'foo' does not have any merge requests")
})
t.Run("Handles when there are too many MRs", func(t *testing.T) {
request := makeRequest(t, http.MethodGet, "/foo", nil)
Expand All @@ -88,7 +88,7 @@ func TestWithMrMiddleware(t *testing.T) {
data, status := getFailData(t, handler, request)
assert(t, status, http.StatusBadRequest)
assert(t, data.Message, "Multiple MRs found")
assert(t, data.Details, "Please call gitlab.choose_merge_request()")
assert(t, data.Details, "please call gitlab.choose_merge_request()")
})
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/app/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (a pipelineService) GetLastPipeline(commit string) (*gitlab.PipelineInfo, e
}

if res.StatusCode >= 300 {
return nil, errors.New("Could not get pipelines")
return nil, errors.New("could not get pipelines")
}

if len(pipes) == 0 {
Expand Down
2 changes: 1 addition & 1 deletion cmd/app/resolve_discussion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestResolveDiscussion(t *testing.T) {
request := makeRequest(t, http.MethodPut, "/mr/discussions/resolve", testResolveMergeRequestPayload)
data, status := getFailData(t, svc, request)
assert(t, data.Message, "Could not resolve discussion")
assert(t, data.Details, "Some error from Gitlab")
assert(t, data.Details, "some error from Gitlab")
assert(t, status, http.StatusInternalServerError)
})
}
12 changes: 9 additions & 3 deletions cmd/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ func CreateRouter(gitlabClient *Client, projectInfo *ProjectInfo, s *shutdownSer
for _, optFunc := range optFuncs {
err := optFunc(&d)
if err != nil {
panic(err)
if os.Getenv("DEBUG") != "" {
// TODO: We have some JSON files (emojis.json) we import relative to the binary in production and
// expect to break during debugging, do not throw when that occurs.
fmt.Fprintf(os.Stdout, "Issue occured setting up router: %s\n", err)
} else {
panic(err)
}
}
}

Expand Down Expand Up @@ -245,13 +251,13 @@ func CreateRouter(gitlabClient *Client, projectInfo *ProjectInfo, s *shutdownSer
func checkServer(port int) error {
for i := 0; i < 10; i++ {
resp, err := http.Get("http://localhost:" + fmt.Sprintf("%d", port) + "/ping")
if resp.StatusCode == 200 && err == nil {
if resp != nil && resp.StatusCode == 200 && err == nil {
return nil
}
time.Sleep(100 * time.Microsecond)
}

return errors.New("Could not start server!")
return errors.New("could not start server")
}

/* Creates a TCP listener on the port specified by the user or a random port */
Expand Down
Loading