From 3a95d2a96c3136169b6c51f492dd266e0a073a8a Mon Sep 17 00:00:00 2001 From: Gennady Telegin Date: Thu, 14 Jan 2016 01:34:31 +0300 Subject: [PATCH] Implement Merge Requests API --- merge_requests.go | 331 ++++++++++++++++++++++++++++++ merge_requests_test.go | 80 ++++++++ repositories.go | 1 + stubs/merge_requests/changes.json | 55 +++++ stubs/merge_requests/commits.json | 20 ++ stubs/merge_requests/index.json | 31 +++ stubs/merge_requests/show.json | 29 +++ 7 files changed, 547 insertions(+) create mode 100644 merge_requests.go create mode 100644 merge_requests_test.go create mode 100644 stubs/merge_requests/changes.json create mode 100644 stubs/merge_requests/commits.json create mode 100644 stubs/merge_requests/index.json create mode 100644 stubs/merge_requests/show.json diff --git a/merge_requests.go b/merge_requests.go new file mode 100644 index 0000000..c04dab3 --- /dev/null +++ b/merge_requests.go @@ -0,0 +1,331 @@ +package gogitlab + +import ( + "encoding/json" + "time" +) + +const ( + project_url_merge_requests = "/projects/:id/merge_requests" // Get project merge requests + project_url_merge_request = "/projects/:id/merge_requests/:merge_request_id" // Get information about a single merge request + project_url_merge_request_commits = "/projects/:id/merge_requests/:merge_request_id/commits" // Get a list of merge request commits + project_url_merge_request_changes = "/projects/:id/merge_requests/:merge_request_id/changes" // Shows information about the merge request including its files and changes + project_url_merge_request_merge = "/projects/:id/merge_requests/:merge_request_id/merge" // Merge changes submitted with MR + project_url_merge_request_cancel_merge = "/projects/:id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds" // Cancel Merge When Build Succeeds + project_url_merge_request_comments = "/projects/:id/merge_requests/:merge_request_id/comments" // Lists all comments associated with a merge request +) + +type MergeRequest struct { + Id int `json:"id,omitempty"` + Iid int `json:"iid,omitempty"` + TargetBranch string `json:"target_branch,omitempty"` + SourceBranch string `json:"source_branch,omitempty"` + ProjectId int `json:"project_id,omitempty"` + Title string `json:"title,omitempty"` + State string `json:"state,omitempty"` + Upvotes int `json:"upvotes,omitempty"` + Downvotes int `json:"downvotes,omitempty"` + Author *User `json:"author,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Description string `json:"description,omitempty"` + WorkInProgress bool `json:"work_in_progress,omitempty"` +} + +type ChangeItem struct { + OldPath string `json:"old_path,omitempty"` + NewPath string `json:"new_path,omitempty"` + AMode string `json:"a_mode,omitempty"` + BMode string `json:"b_mode,omitempty"` + Diff string `json:"diff,omitempty"` + NewFile bool `json:"new_file,omitempty"` + RenamedFile bool `json:"renamed_file,omitempty"` + DeletedFile bool `json:"deleted_file,omitempty"` +} + +type MergeRequestChanges struct { + *MergeRequest + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + SourceProjectId int `json:"source_project_id,omitempty"` + TargetProjectId int `json:"target_project_id,omitempty"` + Labels []string `json:"labels,omitempty"` + Milestone Milestone `json:"milestone,omitempty"` + Changes []ChangeItem `json:"changes,omitempty"` +} + +type AddMergeRequestRequest struct { + SourceBranch string `json:"source_branch"` + TargetBranch string `json:"target_branch"` + AssigneeId int `json:"assignee_id,omitempty"` + Title string `json:"title"` + Description string `json:"description,omitempty"` + TargetProjectId int `json:"target_project_id,omitempty"` + Lables []string `json:"lables,omitempty"` +} + +type AcceptMergeRequestRequest struct { + MergeCommitMessage string `json:"merge_commit_message,omitempty"` + ShouldRemoveSourceBranch bool `json:"should_remove_source_branch,omitempty"` + MergedWhenBuildSucceeds bool `json:"merged_when_build_succeeds,omitempty"` +} + +/* +Get list of project merge requests. + + GET /projects/:id/merge_requests + +Parameters: + + id The ID of a project + +Params: + iid (optional) - Return the request having the given iid + state (optional) - Return all requests or just those that are merged, opened or closed + order_by (optional) - Return requests ordered by created_at or updated_at fields. Default is created_at + sort (optional) - Return requests sorted in asc or desc order. Default is desc + +*/ +func (g *Gitlab) ProjectMergeRequests(id string, params map[string]string) ([]*MergeRequest, error) { + url, opaque := g.ResourceUrlRaw(project_url_merge_requests, map[string]string{":id": id}) + + for name, value := range params { + url = url + "&" + name + "=" + value + } + + var err error + var mergeRequests []*MergeRequest + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err != nil { + return mergeRequests, err + } + + err = json.Unmarshal(contents, &mergeRequests) + + return mergeRequests, err +} + +/* +Get single project merge request. + + GET /projects/:id/merge_requests/:merge_request_id + +Parameters: + + id The ID of a project + merge_request_id The ID of a merge request + +*/ +func (g *Gitlab) ProjectMergeRequest(id, merge_request_id string) (*MergeRequest, error) { + url, opaque := g.ResourceUrlRaw(project_url_merge_request, map[string]string{ + ":id": id, + ":merge_request_id": merge_request_id, + }) + + var err error + mr := new(MergeRequest) + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err != nil { + return mr, err + } + + err = json.Unmarshal(contents, &mr) + + return mr, err +} + +/* +Get a list of merge request commits. + + GET /projects/:id/merge_request/:merge_request_id/commits + +Parameters: + + id The ID of a project + merge_request_id The ID of a merge request + +*/ +func (g *Gitlab) ProjectMergeRequestCommits(id, merge_request_id string) ([]*Commit, error) { + url, opaque := g.ResourceUrlRaw(project_url_merge_request_commits, map[string]string{ + ":id": id, + ":merge_request_id": merge_request_id, + }) + + var err error + var commits []*Commit + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err == nil { + err = json.Unmarshal(contents, &commits) + if err == nil { + for _, commit := range commits { + t, _ := time.Parse(dateLayout, commit.Created_At) + commit.CreatedAt = t + } + } + } + + return commits, err +} + +/* +Get information about the merge request including its files and changes. + + GET /projects/:id/merge_request/:merge_request_id/changes + +Parameters: + + id The ID of a project + merge_request_id The ID of a merge request + +*/ +func (g *Gitlab) ProjectMergeRequestChanges(id, merge_request_id string) (*MergeRequestChanges, error) { + url, opaque := g.ResourceUrlRaw(project_url_merge_request_changes, map[string]string{ + ":id": id, + ":merge_request_id": merge_request_id, + }) + + var err error + changes := new(MergeRequestChanges) + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err != nil { + return changes, err + } + + err = json.Unmarshal(contents, &changes) + + return changes, err +} + +/* +Creates a new merge request. + + POST /projects/:id/merge_requests + +Parameters: + + id The ID of a project + +*/ +func (g *Gitlab) AddMergeRequest(req *AddMergeRequestRequest) (*MergeRequest, error) { + url, _ := g.ResourceUrlRaw(project_url_merge_requests, map[string]string{ + ":id": string(req.TargetProjectId), + }) + + encodedRequest, err := json.Marshal(req) + if err != nil { + return nil, err + } + + data, err := g.buildAndExecRequest("POST", url, encodedRequest) + if err != nil { + return nil, err + } + + mr := new(MergeRequest) + err = json.Unmarshal(data, mr) + if err != nil { + panic(err) + } + return mr, nil +} + +/* +Updates an existing merge request. + + PUT /projects/:id/merge_request/:merge_request_id + +Parameters: + + id The ID of a project + +*/ +func (g *Gitlab) EditMergeRequest(mr *MergeRequest) error { + url, _ := g.ResourceUrlRaw(project_url_merge_request, map[string]string{ + ":id": string(mr.ProjectId), + ":merge_request_id": string(mr.Id), + }) + + encodedRequest, err := json.Marshal(mr) + if err != nil { + return err + } + + data, err := g.buildAndExecRequest("PUT", url, encodedRequest) + if err != nil { + return err + } + + err = json.Unmarshal(data, mr) + if err != nil { + panic(err) + } + return nil +} + +/* +Merge changes submitted with MR. + + PUT /projects/:id/merge_request/:merge_request_id/merge + +Parameters: + + id The ID of a project + merge_request_id The ID of a merge request + +*/ +func (g *Gitlab) ProjectMergeRequestAccept(id, merge_request_id string, req *AcceptMergeRequestRequest) (*MergeRequest, error) { + url, _ := g.ResourceUrlRaw(project_url_merge_request_merge, map[string]string{ + ":id": id, + ":merge_request_id": merge_request_id, + }) + + encodedRequest, err := json.Marshal(req) + if err != nil { + return nil, err + } + + data, err := g.buildAndExecRequest("PUT", url, encodedRequest) + if err != nil { + return nil, err + } + + mr := new(MergeRequest) + err = json.Unmarshal(data, mr) + if err != nil { + panic(err) + } + return mr, nil +} + +/* +Cancel Merge When Build Succeeds. + + PUT /projects/:id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds + +Parameters: + + id The ID of a project + merge_request_id The ID of a merge request + +*/ +func (g *Gitlab) ProjectMergeRequestCancelMerge(id, merge_request_id string) (*MergeRequest, error) { + url, _ := g.ResourceUrlRaw(project_url_merge_request_cancel_merge, map[string]string{ + ":id": id, + ":merge_request_id": merge_request_id, + }) + + data, err := g.buildAndExecRequest("PUT", url, []byte{}) + if err != nil { + return nil, err + } + + mr := new(MergeRequest) + err = json.Unmarshal(data, mr) + if err != nil { + panic(err) + } + return mr, nil +} diff --git a/merge_requests_test.go b/merge_requests_test.go new file mode 100644 index 0000000..238fd97 --- /dev/null +++ b/merge_requests_test.go @@ -0,0 +1,80 @@ +package gogitlab + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestProjectMergeRequests(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/index.json") + mrs, err := gitlab.ProjectMergeRequests("3", nil) + + assert.Equal(t, err, nil) + assert.Equal(t, len(mrs), 1) + defer ts.Close() +} + +func TestProjectMergeRequest(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/show.json") + mr, err := gitlab.ProjectMergeRequest("3", "1") + + assert.Equal(t, err, nil) + assert.Equal(t, mr.TargetBranch, "master") + defer ts.Close() +} + +func TestProjectMergeRequestCommits(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/commits.json") + commits, err := gitlab.ProjectMergeRequestCommits("3", "1") + + assert.Equal(t, err, nil) + assert.Equal(t, len(commits), 2) + defer ts.Close() +} + +func TestProjectMergeRequestChanges(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/changes.json") + mr, err := gitlab.ProjectMergeRequestChanges("3", "1") + + assert.Equal(t, err, nil) + assert.Equal(t, len(mr.Changes), 1) + defer ts.Close() +} + +func TestAddMergeRequest(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/show.json") + req := AddMergeRequestRequest{ + TargetProjectId: 3, + } + _, err := gitlab.AddMergeRequest(&req) + + assert.Equal(t, err, nil) + defer ts.Close() +} + +func TestEditMergeRequest(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/show.json") + req := MergeRequest{ + ProjectId: 3, + Id: 1, + } + err := gitlab.EditMergeRequest(&req) + + assert.Equal(t, err, nil) + defer ts.Close() +} + +func TestProjectMergeRequestAccept(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/show.json") + req := AcceptMergeRequestRequest{} + _, err := gitlab.ProjectMergeRequestAccept("3", "1", &req) + assert.Equal(t, err, nil) + defer ts.Close() +} + +func TestProjectMergeRequestCancelMerge(t *testing.T) { + ts, gitlab := Stub("stubs/merge_requests/show.json") + _, err := gitlab.ProjectMergeRequestCancelMerge("3", "1") + assert.Equal(t, err, nil) + defer ts.Close() +} diff --git a/repositories.go b/repositories.go index 1c063ae..cf2217b 100644 --- a/repositories.go +++ b/repositories.go @@ -57,6 +57,7 @@ type Commit struct { Author_Email string Created_At string CreatedAt time.Time + Message string } /* diff --git a/stubs/merge_requests/changes.json b/stubs/merge_requests/changes.json new file mode 100644 index 0000000..d022600 --- /dev/null +++ b/stubs/merge_requests/changes.json @@ -0,0 +1,55 @@ +{ + "id": 21, + "iid": 1, + "project_id": 4, + "title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.", + "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.", + "work_in_progress": false, + "state": "reopened", + "created_at": "2015-02-02T19:49:39.159Z", + "updated_at": "2015-02-02T20:08:49.959Z", + "target_branch": "secret_token", + "source_branch": "version-1-9", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Chad Hamill", + "username": "jarrett", + "id": 5, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/b95567800f828948baf5f4160ebb2473?s=40&d=identicon" + }, + "assignee": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon" + }, + "source_project_id": 4, + "target_project_id": 4, + "labels": [ ], + "milestone": { + "id": 5, + "iid": 1, + "project_id": 4, + "title": "v2.0", + "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", + "state": "closed", + "created_at": "2015-02-02T19:49:26.013Z", + "updated_at": "2015-02-02T19:49:26.013Z", + "due_date": null + }, + "changes": [ + { + "old_path": "VERSION", + "new_path": "VERSION", + "a_mode": "100644", + "b_mode": "100644", + "diff": "--- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.7 +1.9.8", + "new_file": false, + "renamed_file": false, + "deleted_file": false + } + ] +} \ No newline at end of file diff --git a/stubs/merge_requests/commits.json b/stubs/merge_requests/commits.json new file mode 100644 index 0000000..cc27d4e --- /dev/null +++ b/stubs/merge_requests/commits.json @@ -0,0 +1,20 @@ +[ + { + "id": "ed899a2f4b50b4370feeea94676502b42383c746", + "short_id": "ed899a2f4b5", + "title": "Replace sanitize with escape once", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dzaporozhets@sphereconsultinginc.com", + "created_at": "2012-09-20T11:50:22+03:00", + "message": "Replace sanitize with escape once" + }, + { + "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", + "short_id": "6104942438c", + "title": "Sanitize for network graph", + "author_name": "randx", + "author_email": "dmitriy.zaporozhets@gmail.com", + "created_at": "2012-09-20T09:06:12+03:00", + "message": "Sanitize for network graph" + } +] \ No newline at end of file diff --git a/stubs/merge_requests/index.json b/stubs/merge_requests/index.json new file mode 100644 index 0000000..56ed3af --- /dev/null +++ b/stubs/merge_requests/index.json @@ -0,0 +1,31 @@ +[ + { + "id": 1, + "iid": 1, + "target_branch": "master", + "source_branch": "test1", + "project_id": 3, + "title": "test1", + "state": "opened", + "upvotes": 0, + "downvotes": 0, + "author": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "name": "Administrator", + "state": "active", + "created_at": "2012-04-29T08:46:00Z" + }, + "assignee": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "name": "Administrator", + "state": "active", + "created_at": "2012-04-29T08:46:00Z" + }, + "description":"fixed login page css paddings", + "work_in_progress": false + } +] \ No newline at end of file diff --git a/stubs/merge_requests/show.json b/stubs/merge_requests/show.json new file mode 100644 index 0000000..72e3e09 --- /dev/null +++ b/stubs/merge_requests/show.json @@ -0,0 +1,29 @@ +{ + "id": 1, + "iid": 1, + "target_branch": "master", + "source_branch": "test1", + "project_id": 3, + "title": "test1", + "state": "merged", + "upvotes": 0, + "downvotes": 0, + "author": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "name": "Administrator", + "state": "active", + "created_at": "2012-04-29T08:46:00Z" + }, + "assignee": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "name": "Administrator", + "state": "active", + "created_at": "2012-04-29T08:46:00Z" + }, + "description":"fixed login page css paddings", + "work_in_progress": false +} \ No newline at end of file