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
19 changes: 18 additions & 1 deletion gitlab/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,11 @@ func (s *gitLabIssueService) Create(ctx context.Context, owner, repo string, opt
Description: gitlab.Ptr(opts.Body),
}
if len(opts.Assignees) > 0 {
return nil, fmt.Errorf("GitLab requires assignee IDs, not usernames; assignees cannot be set by username on create")
assigneeIDs, err := resolveUserIDs(s.client, opts.Assignees)
if err != nil {
return nil, fmt.Errorf("resolving assignees: %w", err)
}
glOpts.AssigneeIDs = &assigneeIDs
}
if len(opts.Labels) > 0 {
lbls := gitlab.LabelOptions(opts.Labels)
Expand Down Expand Up @@ -218,6 +222,19 @@ func (s *gitLabIssueService) Update(ctx context.Context, owner, repo string, num
glOpts.Description = opts.Body
changed = true
}
if opts.Assignees != nil {
if len(opts.Assignees) == 0 {
empty := []int64{}
glOpts.AssigneeIDs = &empty
} else {
assigneeIDs, err := resolveUserIDs(s.client, opts.Assignees)
if err != nil {
return nil, fmt.Errorf("resolving assignees: %w", err)
}
glOpts.AssigneeIDs = &assigneeIDs
}
changed = true
}
if opts.Labels != nil {
lbls := gitlab.LabelOptions(opts.Labels)
glOpts.Labels = &lbls
Expand Down
47 changes: 38 additions & 9 deletions gitlab/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,57 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"

forge "github.com/git-pkgs/forge"
)

func TestGitLabCreateIssueRejectsAssignees(t *testing.T) {
srv := httptest.NewServer(nil)
func TestGitLabCreateIssueWithAssignees(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v4/users", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("username") == "alice" {
_ = json.NewEncoder(w).Encode([]map[string]any{{"id": 42, "username": "alice"}})
} else {
_ = json.NewEncoder(w).Encode([]map[string]any{})
}
})
var gotAssigneeIDs []int64
mux.HandleFunc("POST /api/v4/projects/mygroup%2Fmyrepo/issues", func(w http.ResponseWriter, r *http.Request) {
var req struct {
Title string `json:"title"`
AssigneeIDs []int64 `json:"assignee_ids"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
gotAssigneeIDs = req.AssigneeIDs
_ = json.NewEncoder(w).Encode(map[string]any{
"id": 1,
"iid": 1,
"title": req.Title,
"state": "opened",
"assignees": []map[string]any{
{"id": 42, "username": "alice"},
},
})
})

srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "test-token", nil)

_, err := f.Issues().Create(context.Background(), "mygroup", "myrepo", forge.CreateIssueOpts{
iss, err := f.Issues().Create(context.Background(), "mygroup", "myrepo", forge.CreateIssueOpts{
Title: "Test issue",
Body: "Body text",
Assignees: []string{"someuser"},
Assignees: []string{"alice"},
})
if err == nil {
t.Fatal("expected error when assignees are set, got nil")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(gotAssigneeIDs) != 1 || gotAssigneeIDs[0] != 42 {
t.Fatalf("expected assignee_ids [42], got %v", gotAssigneeIDs)
}
if !strings.Contains(err.Error(), "assignee IDs") {
t.Fatalf("expected error to mention 'assignee IDs', got: %v", err)
if len(iss.Assignees) != 1 || iss.Assignees[0].Login != "alice" {
t.Fatalf("expected 1 assignee alice, got %v", iss.Assignees)
}
}

Expand Down
42 changes: 42 additions & 0 deletions gitlab/prs.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,22 @@ func (s *gitLabPRService) Create(ctx context.Context, owner, repo string, opts f
glOpts.Title = gitlab.Ptr("Draft: " + opts.Title)
}

if len(opts.Assignees) > 0 {
assigneeIDs, err := resolveUserIDs(s.client, opts.Assignees)
if err != nil {
return nil, fmt.Errorf("resolving assignees: %w", err)
}
glOpts.AssigneeIDs = &assigneeIDs
}

if len(opts.Reviewers) > 0 {
reviewerIDs, err := resolveUserIDs(s.client, opts.Reviewers)
if err != nil {
return nil, fmt.Errorf("resolving reviewers: %w", err)
}
glOpts.ReviewerIDs = &reviewerIDs
}

if len(opts.Labels) > 0 {
lbls := gitlab.LabelOptions(opts.Labels)
glOpts.Labels = &lbls
Expand Down Expand Up @@ -310,6 +326,32 @@ func (s *gitLabPRService) Update(ctx context.Context, owner, repo string, number
glOpts.TargetBranch = opts.Base
changed = true
}
if opts.Assignees != nil {
if len(opts.Assignees) == 0 {
empty := []int64{}
glOpts.AssigneeIDs = &empty
} else {
assigneeIDs, err := resolveUserIDs(s.client, opts.Assignees)
if err != nil {
return nil, fmt.Errorf("resolving assignees: %w", err)
}
glOpts.AssigneeIDs = &assigneeIDs
}
changed = true
}
if opts.Reviewers != nil {
if len(opts.Reviewers) == 0 {
empty := []int64{}
glOpts.ReviewerIDs = &empty
} else {
reviewerIDs, err := resolveUserIDs(s.client, opts.Reviewers)
if err != nil {
return nil, fmt.Errorf("resolving reviewers: %w", err)
}
glOpts.ReviewerIDs = &reviewerIDs
}
changed = true
}
if opts.Labels != nil {
lbls := gitlab.LabelOptions(opts.Labels)
glOpts.Labels = &lbls
Expand Down
142 changes: 142 additions & 0 deletions gitlab/prs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package gitlab

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

forge "github.com/git-pkgs/forge"
)

func TestGitLabCreatePRWithAssigneesAndReviewers(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v4/users", func(w http.ResponseWriter, r *http.Request) {
uname := r.URL.Query().Get("username")
switch uname {
case "alice":
_ = json.NewEncoder(w).Encode([]map[string]any{{"id": 101, "username": "alice"}})
case "bob":
_ = json.NewEncoder(w).Encode([]map[string]any{{"id": 202, "username": "bob"}})
default:
_ = json.NewEncoder(w).Encode([]map[string]any{})
}
})

var gotAssigneeIDs, gotReviewerIDs []int64
mux.HandleFunc("POST /api/v4/projects/mygroup%2Fmyrepo/merge_requests", func(w http.ResponseWriter, r *http.Request) {
var req struct {
Title string `json:"title"`
AssigneeIDs []int64 `json:"assignee_ids"`
ReviewerIDs []int64 `json:"reviewer_ids"`
SourceBranch string `json:"source_branch"`
TargetBranch string `json:"target_branch"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
gotAssigneeIDs = req.AssigneeIDs
gotReviewerIDs = req.ReviewerIDs
_ = json.NewEncoder(w).Encode(map[string]any{
"id": 1,
"iid": 1,
"title": req.Title,
"state": "opened",
"source_branch": req.SourceBranch,
"target_branch": req.TargetBranch,
"assignees": []map[string]any{
{"id": 101, "username": "alice"},
},
"reviewers": []map[string]any{
{"id": 202, "username": "bob"},
},
})
})

srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "test-token", nil)

pr, err := f.PullRequests().Create(context.Background(), "mygroup", "myrepo", forge.CreatePROpts{
Title: "New Feature",
Head: "feature",
Base: "main",
Assignees: []string{"alice"},
Reviewers: []string{"bob"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(gotAssigneeIDs) != 1 || gotAssigneeIDs[0] != 101 {
t.Fatalf("expected assignee_ids [101], got %v", gotAssigneeIDs)
}
if len(gotReviewerIDs) != 1 || gotReviewerIDs[0] != 202 {
t.Fatalf("expected reviewer_ids [202], got %v", gotReviewerIDs)
}
if len(pr.Assignees) != 1 || pr.Assignees[0].Login != "alice" {
t.Fatalf("expected 1 assignee alice, got %v", pr.Assignees)
}
if len(pr.Reviewers) != 1 || pr.Reviewers[0].Login != "bob" {
t.Fatalf("expected 1 reviewer bob, got %v", pr.Reviewers)
}
}

func TestGitLabUpdatePRWithAssigneesAndReviewers(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v4/users", func(w http.ResponseWriter, r *http.Request) {
uname := r.URL.Query().Get("username")
switch uname {
case "alice":
_ = json.NewEncoder(w).Encode([]map[string]any{{"id": 101, "username": "alice"}})
case "bob":
_ = json.NewEncoder(w).Encode([]map[string]any{{"id": 202, "username": "bob"}})
default:
_ = json.NewEncoder(w).Encode([]map[string]any{})
}
})

var gotAssigneeIDs, gotReviewerIDs []int64
mux.HandleFunc("PUT /api/v4/projects/mygroup%2Fmyrepo/merge_requests/1", func(w http.ResponseWriter, r *http.Request) {
var req struct {
AssigneeIDs []int64 `json:"assignee_ids"`
ReviewerIDs []int64 `json:"reviewer_ids"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
gotAssigneeIDs = req.AssigneeIDs
gotReviewerIDs = req.ReviewerIDs
_ = json.NewEncoder(w).Encode(map[string]any{
"id": 1,
"iid": 1,
"title": "Updated MR",
"state": "opened",
"assignees": []map[string]any{
{"id": 101, "username": "alice"},
},
"reviewers": []map[string]any{
{"id": 202, "username": "bob"},
},
})
})

srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "test-token", nil)

pr, err := f.PullRequests().Update(context.Background(), "mygroup", "myrepo", 1, forge.UpdatePROpts{
Assignees: []string{"alice"},
Reviewers: []string{"bob"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(gotAssigneeIDs) != 1 || gotAssigneeIDs[0] != 101 {
t.Fatalf("expected assignee_ids [101], got %v", gotAssigneeIDs)
}
if len(gotReviewerIDs) != 1 || gotReviewerIDs[0] != 202 {
t.Fatalf("expected reviewer_ids [202], got %v", gotReviewerIDs)
}
if len(pr.Assignees) != 1 || pr.Assignees[0].Login != "alice" {
t.Fatalf("expected 1 assignee alice, got %v", pr.Assignees)
}
}
19 changes: 1 addition & 18 deletions gitlab/reviews.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (s *gitLabReviewService) RequestReviewers(ctx context.Context, owner, repo
pid := owner + "/" + repo

// GitLab requires user IDs, not usernames. Resolve them.
ids, err := s.resolveUserIDs(users)
ids, err := resolveUserIDs(s.client, users)
if err != nil {
return err
}
Expand Down Expand Up @@ -150,20 +150,3 @@ func (s *gitLabReviewService) RemoveReviewers(ctx context.Context, owner, repo s
}
return nil
}

func (s *gitLabReviewService) resolveUserIDs(usernames []string) ([]int64, error) {
ids := make([]int64, 0, len(usernames))
for _, username := range usernames {
users, _, err := s.client.Users.ListUsers(&gitlab.ListUsersOptions{
Username: gitlab.Ptr(username),
})
if err != nil {
return nil, fmt.Errorf("looking up user %q: %w", username, err)
}
if len(users) == 0 {
return nil, fmt.Errorf("user %q not found", username)
}
ids = append(ids, int64(users[0].ID))
}
return ids, nil
}
24 changes: 24 additions & 0 deletions gitlab/users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gitlab

import (
"fmt"

gitlab "gitlab.com/gitlab-org/api/client-go"
)

func resolveUserIDs(client *gitlab.Client, usernames []string) ([]int64, error) {
ids := make([]int64, 0, len(usernames))
for _, username := range usernames {
users, _, err := client.Users.ListUsers(&gitlab.ListUsersOptions{
Username: gitlab.Ptr(username),
})
if err != nil {
return nil, fmt.Errorf("looking up user %q: %w", username, err)
}
if len(users) == 0 {
return nil, fmt.Errorf("user %q not found", username)
}
ids = append(ids, int64(users[0].ID))
}
return ids, nil
}