| // Copyright 2020 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package gerrit |
| |
| import ( |
| "fmt" |
| "net/url" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "google.golang.org/protobuf/types/known/timestamppb" |
| |
| "go.chromium.org/luci/common/errors" |
| gerritpb "go.chromium.org/luci/common/proto/gerrit" |
| ) |
| |
| // This file contains code related to JSON representations of messages that are |
| // used for requests to the Gerrit REST API, and unmarshalling code to convert |
| // from the JSON representations to protos defined in `gerritpb`. |
| // |
| // Each of these structs corresponds to an entity described at |
| // https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#json-entities |
| // and also to a message in `gerritpb`, and each has a unmarshalling method |
| // called ToProto. |
| |
| // timestamp implements customized JSON marshal/unmarshal behavior that matches |
| // the timestamp format used in Gerrit. |
| |
| type accountInfo struct { |
| Name string `json:"name,omitempty"` |
| Email string `json:"email,omitempty"` |
| SecondaryEmails []string `json:"secondary_emails,omitempty"` |
| Username string `json:"username,omitempty"` |
| AccountID int64 `json:"_account_id,omitempty"` |
| } |
| |
| func (a *accountInfo) ToProto() *gerritpb.AccountInfo { |
| if a == nil { |
| return nil |
| } |
| return &gerritpb.AccountInfo{ |
| Name: a.Name, |
| Email: a.Email, |
| SecondaryEmails: a.SecondaryEmails, |
| Username: a.Username, |
| AccountId: a.AccountID, |
| } |
| } |
| |
| type ownerInfo struct { |
| Account accountInfo `json:"account,omitempty"` |
| } |
| |
| // changeInfo represents JSON for a gerritpb.ChangeInfo on the wire. |
| type changeInfo struct { |
| Number int64 `json:"_number"` |
| Owner *accountInfo `json:"owner"` |
| Project string `json:"project"` |
| Branch string `json:"branch"` |
| ChangeID string `json:"change_id"` |
| Reviewers map[string][]*accountInfo `json:"reviewers"` |
| Hashtags []string `json:"hashtags"` |
| Subject string `json:"subject"` |
| |
| // json.Unmarshal cannot convert enum string to value, |
| // so this field is handled specially in ToProto. |
| Status string `json:"status"` |
| |
| CurrentRevision string `json:"current_revision"` |
| Revisions map[string]*revisionInfo `json:"revisions"` |
| Labels map[string]*labelInfo `json:"labels"` |
| Messages []changeMessageInfo `json:"messages"` |
| Requirements []requirement `json:"requirements"` |
| SubmitRequirements []*submitRequirementResultInfo `json:"submit_requirements"` |
| |
| Created Timestamp `json:"created"` |
| Updated Timestamp `json:"updated"` |
| Submitted Timestamp `json:"submitted"` |
| Submittable bool `json:"submittable,omitempty"` |
| IsPrivate bool `json:"is_private,omitempty"` |
| MetaRevID string `json:"meta_rev_id,omitempty"` |
| |
| RevertOf int64 `json:"revert_of,omitempty"` |
| CherryPickOfChange int64 `json:"cherry_pick_of_change,omitempty"` |
| |
| // MoreChanges may be set on the last change in a response to a query for |
| // changes, but this is not a property of the change itself and is not |
| // needed in gerritpb.ChangeInfo. |
| MoreChanges bool `json:"_more_changes"` |
| } |
| |
| func (ci *changeInfo) ToProto() (*gerritpb.ChangeInfo, error) { |
| ret := &gerritpb.ChangeInfo{ |
| Number: ci.Number, |
| Owner: ci.Owner.ToProto(), |
| Project: ci.Project, |
| Ref: branchToRef(ci.Branch), |
| Subject: ci.Subject, |
| Status: gerritpb.ChangeStatus(gerritpb.ChangeStatus_value[ci.Status]), |
| Hashtags: ci.Hashtags, |
| CurrentRevision: ci.CurrentRevision, |
| Submittable: ci.Submittable, |
| IsPrivate: ci.IsPrivate, |
| MetaRevId: ci.MetaRevID, |
| Created: timestamppb.New(ci.Created.Time), |
| Updated: timestamppb.New(ci.Updated.Time), |
| Submitted: timestamppb.New(ci.Submitted.Time), |
| RevertOf: ci.RevertOf, |
| CherryPickOfChange: ci.CherryPickOfChange, |
| Branch: ci.Branch, |
| } |
| if ci.Revisions != nil { |
| ret.Revisions = make(map[string]*gerritpb.RevisionInfo, len(ci.Revisions)) |
| for rev, info := range ci.Revisions { |
| ret.Revisions[rev] = info.ToProto() |
| } |
| } |
| if ci.Labels != nil { |
| ret.Labels = make(map[string]*gerritpb.LabelInfo, len(ci.Labels)) |
| for label, info := range ci.Labels { |
| ret.Labels[label] = info.ToProto() |
| } |
| } |
| if ci.Messages != nil { |
| ret.Messages = make([]*gerritpb.ChangeMessageInfo, len(ci.Messages)) |
| for i, msg := range ci.Messages { |
| ret.Messages[i] = msg.ToProto() |
| } |
| } |
| var err error |
| if ci.Requirements != nil { |
| ret.Requirements = make([]*gerritpb.Requirement, len(ci.Requirements)) |
| for i, r := range ci.Requirements { |
| if ret.Requirements[i], err = r.ToProto(); err != nil { |
| return nil, err |
| } |
| } |
| } |
| if ci.SubmitRequirements != nil { |
| ret.SubmitRequirements = make([]*gerritpb.SubmitRequirementResultInfo, |
| len(ci.SubmitRequirements)) |
| for i, r := range ci.SubmitRequirements { |
| if ret.SubmitRequirements[i], err = r.ToProto(); err != nil { |
| return nil, err |
| } |
| } |
| } |
| if ci.Reviewers != nil { |
| ret.Reviewers = &gerritpb.ReviewerStatusMap{} |
| if accs, exist := ci.Reviewers["REVIEWER"]; exist { |
| ret.Reviewers.Reviewers = make([]*gerritpb.AccountInfo, len(accs)) |
| for i, acc := range accs { |
| ret.Reviewers.Reviewers[i] = acc.ToProto() |
| } |
| } |
| if accs, exist := ci.Reviewers["CC"]; exist { |
| ret.Reviewers.Ccs = make([]*gerritpb.AccountInfo, len(accs)) |
| for i, acc := range accs { |
| ret.Reviewers.Ccs[i] = acc.ToProto() |
| } |
| } |
| if accs, exist := ci.Reviewers["REMOVED"]; exist { |
| ret.Reviewers.Removed = make([]*gerritpb.AccountInfo, len(accs)) |
| for i, acc := range accs { |
| ret.Reviewers.Ccs[i] = acc.ToProto() |
| } |
| } |
| } |
| |
| return ret, nil |
| } |
| |
| type labelInfo struct { |
| Optional bool `json:"optional"` |
| Approved *accountInfo `json:"approved"` |
| Rejected *accountInfo `json:"rejected"` |
| Recommended *accountInfo `json:"recommended"` |
| Disliked *accountInfo `json:"disliked"` |
| Blocking bool `json:"blocking"` |
| Value int32 `json:"value"` |
| DefaultValue int32 `json:"default_value"` |
| All []*approvalInfo `json:"all"` |
| Values map[string]string `json:"values"` |
| } |
| |
| func (li *labelInfo) ToProto() *gerritpb.LabelInfo { |
| ret := &gerritpb.LabelInfo{ |
| Optional: li.Optional, |
| Approved: li.Approved.ToProto(), |
| Rejected: li.Rejected.ToProto(), |
| Recommended: li.Recommended.ToProto(), |
| Disliked: li.Disliked.ToProto(), |
| Blocking: li.Blocking, |
| Value: li.Value, |
| DefaultValue: li.DefaultValue, |
| } |
| if len(li.All) > 0 { |
| ret.All = make([]*gerritpb.ApprovalInfo, len(li.All)) |
| for i, a := range li.All { |
| ret.All[i] = a.ToProto() |
| } |
| } |
| if li.Values != nil { |
| ret.Values = make(map[int32]string, len(li.Values)) |
| for value, description := range li.Values { |
| i, err := strconv.ParseInt(strings.TrimSpace(value), 10, 32) |
| // Error is silently ignored for consistency with other parts of code. |
| if err == nil { |
| ret.Values[int32(i)] = description |
| } |
| } |
| } |
| return ret |
| } |
| |
| type approvalInfo struct { |
| accountInfo |
| Value int32 `json:"value"` |
| PermittedVotingRange *gerritpb.VotingRangeInfo `json:"permitted_voting_range"` |
| Date Timestamp `json:"date"` |
| Tag string `json:"tag"` |
| PostSubmit bool `json:"post_submit"` |
| } |
| |
| func (ai *approvalInfo) ToProto() *gerritpb.ApprovalInfo { |
| ret := &gerritpb.ApprovalInfo{ |
| User: ai.accountInfo.ToProto(), |
| Value: ai.Value, |
| PermittedVotingRange: ai.PermittedVotingRange, |
| Date: timestamppb.New(ai.Date.Time), |
| Tag: ai.Tag, |
| PostSubmit: ai.PostSubmit, |
| } |
| return ret |
| } |
| |
| type changeMessageInfo struct { |
| ID string `json:"id"` |
| Author *accountInfo `json:"author"` |
| RealAuthor *accountInfo `json:"real_author"` |
| Date Timestamp `json:"date"` |
| Message string `json:"message"` |
| Tag string `json:"tag"` |
| } |
| |
| func (cmi *changeMessageInfo) ToProto() *gerritpb.ChangeMessageInfo { |
| if cmi == nil { |
| return nil |
| } |
| return &gerritpb.ChangeMessageInfo{ |
| Id: cmi.ID, |
| Author: cmi.Author.ToProto(), |
| RealAuthor: cmi.RealAuthor.ToProto(), |
| Date: timestamppb.New(cmi.Date.Time), |
| Message: cmi.Message, |
| Tag: cmi.Tag, |
| } |
| } |
| |
| type requirement struct { |
| Status string `json:"status"` |
| FallbackText string `json:"fallback_text"` |
| Type string `json:"type"` |
| } |
| |
| func (r *requirement) ToProto() (*gerritpb.Requirement, error) { |
| stringVal := "REQUIREMENT_STATUS_" + r.Status |
| numVal, found := gerritpb.Requirement_Status_value[stringVal] |
| if !found { |
| return nil, errors.Reason("no Status enum value for %q", r.Status).Err() |
| } |
| return &gerritpb.Requirement{ |
| Status: gerritpb.Requirement_Status(numVal), |
| FallbackText: r.FallbackText, |
| Type: r.Type, |
| }, nil |
| } |
| |
| type fileInfo struct { |
| LinesInserted int32 `json:"lines_inserted"` |
| LinesDeleted int32 `json:"lines_deleted"` |
| SizeDelta int64 `json:"size_delta"` |
| Size int64 `json:"size"` |
| } |
| |
| func (fi *fileInfo) ToProto() *gerritpb.FileInfo { |
| return &gerritpb.FileInfo{ |
| LinesInserted: fi.LinesInserted, |
| LinesDeleted: fi.LinesDeleted, |
| SizeDelta: fi.SizeDelta, |
| Size: fi.Size, |
| } |
| } |
| |
| type revisionInfo struct { |
| Kind string `json:"kind"` |
| Number int `json:"_number"` |
| Uploader *accountInfo `json:"uploader"` |
| Ref string `json:"ref"` |
| Created Timestamp `json:"created"` |
| Description string `json:"description"` |
| Files map[string]*fileInfo `json:"files"` |
| Commit *commitInfo `json:"commit"` |
| } |
| |
| func (ri *revisionInfo) ToProto() *gerritpb.RevisionInfo { |
| ret := &gerritpb.RevisionInfo{ |
| Number: int32(ri.Number), |
| Uploader: ri.Uploader.ToProto(), |
| Ref: ri.Ref, |
| Created: timestamppb.New(ri.Created.Time), |
| Description: ri.Description, |
| } |
| if v, ok := gerritpb.RevisionInfo_Kind_value[ri.Kind]; ok { |
| ret.Kind = gerritpb.RevisionInfo_Kind(v) |
| } |
| if ri.Files != nil { |
| ret.Files = make(map[string]*gerritpb.FileInfo, len(ri.Files)) |
| for i, fi := range ri.Files { |
| ret.Files[i] = fi.ToProto() |
| } |
| } |
| if ri.Commit != nil { |
| ret.Commit = ri.Commit.ToProto() |
| } |
| return ret |
| } |
| |
| // https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#git-person-info |
| type gitPersonInfo struct { |
| Name string `json:"name"` |
| Email string `json:"email"` |
| } |
| |
| func (g *gitPersonInfo) ToProto() *gerritpb.GitPersonInfo { |
| return &gerritpb.GitPersonInfo{ |
| Name: g.Name, |
| Email: g.Email, |
| } |
| } |
| |
| // https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#commit-info |
| type commitInfo struct { |
| Commit string `json:"commit"` |
| Parents []*commitInfo `json:"parents"` |
| Author *gitPersonInfo `json:"author"` |
| Committer *gitPersonInfo `json:"committer"` |
| Subject string `json:"subject"` |
| Message string `json:"message"` |
| } |
| |
| func (c *commitInfo) ToProto() *gerritpb.CommitInfo { |
| parents := make([]*gerritpb.CommitInfo_Parent, len(c.Parents)) |
| for i, p := range c.Parents { |
| parents[i] = &gerritpb.CommitInfo_Parent{Id: p.Commit} |
| } |
| return &gerritpb.CommitInfo{ |
| Id: c.Commit, |
| Parents: parents, |
| Message: c.Message, |
| Author: c.Author.ToProto(), |
| // TODO(tandrii): support other fields once added. |
| } |
| } |
| |
| // https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#related-change-and-commit-info |
| type relatedChangeAndCommitInfo struct { |
| Project string `json:"project"` |
| ChangeID string `json:"change_id"` |
| Commit commitInfo `json:"commit"` |
| Number int64 `json:"_change_number"` |
| Patchset int64 `json:"_revision_number"` |
| CurrentPatchset int64 `json:"_current_revision_number"` |
| |
| // json.Unmarshal cannot convert enum string to value, |
| // so this field is handled specially in ToProto. |
| Status string `json:"status"` |
| } |
| |
| func (r *relatedChangeAndCommitInfo) ToProto() *gerritpb.GetRelatedChangesResponse_ChangeAndCommit { |
| return &gerritpb.GetRelatedChangesResponse_ChangeAndCommit{ |
| Project: r.Project, |
| Number: r.Number, |
| Patchset: r.Patchset, |
| CurrentPatchset: r.CurrentPatchset, |
| Commit: r.Commit.ToProto(), |
| Status: gerritpb.ChangeStatus(gerritpb.ChangeStatus_value[r.Status]), |
| } |
| } |
| |
| type mergeableInfo struct { |
| SubmitType string `json:"submit_type"` |
| Strategy string `json:"strategy"` |
| Mergeable bool `json:"mergeable"` |
| CommitMerged bool `json:"commit_merged"` |
| ContentMerged bool `json:"content_merged"` |
| Conflicts []string `json:"conflicts"` |
| MergeableInto []string `json:"mergeable_into"` |
| } |
| |
| func (mi *mergeableInfo) ToProto() (*gerritpb.MergeableInfo, error) { |
| // Convert something like 'simple-two-way-in-core' to 'SIMPLE_TWO_WAY_IN_CORE'. |
| strategyEnumName := strings.Replace(strings.ToUpper(mi.Strategy), "-", "_", -1) |
| strategyEnumNum, found := gerritpb.MergeableStrategy_value[strategyEnumName] |
| if !found { |
| return nil, errors.Reason("no MergeableStrategy enum value for %q", strategyEnumName).Err() |
| } |
| submitTypeEnumNum, found := gerritpb.MergeableInfo_SubmitType_value[mi.SubmitType] |
| if !found { |
| return nil, errors.Reason("no SubmitType enum value for %q", mi.SubmitType).Err() |
| } |
| return &gerritpb.MergeableInfo{ |
| SubmitType: gerritpb.MergeableInfo_SubmitType(submitTypeEnumNum), |
| Strategy: gerritpb.MergeableStrategy(strategyEnumNum), |
| Mergeable: mi.Mergeable, |
| CommitMerged: mi.CommitMerged, |
| ContentMerged: mi.ContentMerged, |
| Conflicts: mi.Conflicts, |
| MergeableInto: mi.MergeableInto, |
| }, nil |
| } |
| |
| type addReviewerRequest struct { |
| Reviewer string `json:"reviewer"` |
| State string `json:"state,omitempty"` |
| Confirmed bool `json:"confirmed,omitempty"` |
| Notify string `json:"notify,omitempty"` |
| } |
| |
| type reviewerInfo struct { |
| Name string `json:"name,omitempty"` |
| Email string `json:"email,omitempty"` |
| SecondaryEmails []string `json:"secondary_emails,omitempty"` |
| Username string `json:"username,omitempty"` |
| Approvals map[string]string `json:"approvals,omitempty"` |
| AccountID int64 `json:"_account_id,omitempty"` |
| } |
| |
| func (ri *reviewerInfo) ToProtoReviewerInfo() (*gerritpb.ReviewerInfo, error) { |
| approvals := make(map[string]int32, 0) |
| for label, score := range ri.Approvals { |
| score = strings.TrimLeft(score, " ") |
| scoreInt, err := strconv.ParseInt(score, 10, 32) |
| if err != nil { |
| return nil, errors.Annotate(err, "parsing approvals").Err() |
| } |
| approvals[label] = int32(scoreInt) |
| } |
| return &gerritpb.ReviewerInfo{ |
| Account: &gerritpb.AccountInfo{ |
| Name: ri.Name, |
| Email: ri.Email, |
| SecondaryEmails: ri.SecondaryEmails, |
| Username: ri.Username, |
| AccountId: ri.AccountID, |
| }, |
| Approvals: approvals, |
| }, nil |
| } |
| |
| type addReviewerResult struct { |
| Input string `json:"input"` |
| Reviewers []reviewerInfo `json:"reviewers,omitempty"` |
| Ccs []reviewerInfo `json:"ccs,omitempty"` |
| Error string `json:"error,omitempty"` |
| Confirm bool `json:"confirm,omitempty"` |
| } |
| |
| func (rr *addReviewerResult) ToProto() (*gerritpb.AddReviewerResult, error) { |
| reviewers := make([]*gerritpb.ReviewerInfo, 0) |
| for _, r := range rr.Reviewers { |
| rInfo, err := r.ToProtoReviewerInfo() |
| if err != nil { |
| return nil, errors.Annotate(err, "converting reviewerInfo").Err() |
| } |
| reviewers = append(reviewers, rInfo) |
| } |
| ccs := make([]*gerritpb.ReviewerInfo, 0) |
| for _, r := range rr.Ccs { |
| rInfo, err := r.ToProtoReviewerInfo() |
| if err != nil { |
| return nil, errors.Annotate(err, "converting reviewerInfo").Err() |
| } |
| ccs = append(ccs, rInfo) |
| } |
| return &gerritpb.AddReviewerResult{ |
| Input: rr.Input, |
| Reviewers: reviewers, |
| Ccs: ccs, |
| Error: rr.Error, |
| Confirm: rr.Confirm, |
| }, nil |
| } |
| |
| func enumToString(v int32, m map[int32]string) string { |
| if v == 0 { |
| return "" |
| } |
| prefixLen := strings.LastIndex(m[0], "UNSPECIFIED") |
| return m[v][prefixLen:] |
| } |
| |
| type reviewInput struct { |
| Message string `json:"message,omitempty"` |
| Labels map[string]int32 `json:"labels,omitempty"` |
| Tag string `json:"tag,omitempty"` |
| Notify string `json:"notify,omitempty"` |
| NotifyDetails notifyDetails `json:"notify_details,omitempty"` |
| OnBehalfOf int64 `json:"on_behalf_of,omitempty"` |
| Ready bool `json:"ready,omitempty"` |
| WorkInProgress bool `json:"work_in_progress,omitempty"` |
| AddToAttentionSet []*attentionSetInput `json:"add_to_attention_set,omitempty"` |
| RemoveFromAttentionSet []*attentionSetInput `json:"remove_from_attention_set,omitempty"` |
| IgnoreAutomaticAttentionSetRules bool `json:"ignore_automatic_attention_set_rules,omitempty"` |
| Reviewers []*reviewerInput `json:"reviewers,omitempty"` |
| } |
| |
| type notifyInfo struct { |
| Accounts []int64 `json:"accounts,omitempty"` |
| } |
| |
| type notifyDetails map[string]*notifyInfo |
| |
| func toNotifyDetails(in *gerritpb.NotifyDetails) notifyDetails { |
| recipients := in.GetRecipients() |
| if len(recipients) == 0 { |
| return nil |
| } |
| res := make(map[string]*notifyInfo, len(recipients)) |
| for _, recipient := range recipients { |
| if len(recipient.Info.GetAccounts()) == 0 { |
| continue |
| } |
| rt := recipient.RecipientType |
| if rt == gerritpb.NotifyDetails_RECIPIENT_TYPE_UNSPECIFIED { |
| // Must have been caught in validation. |
| panic(fmt.Errorf("must specify recipient type")) |
| } |
| rts := enumToString(int32(rt.Number()), gerritpb.NotifyDetails_RecipientType_name) |
| if ni, ok := res[rts]; !ok { |
| ni = ¬ifyInfo{ |
| Accounts: make([]int64, len(recipient.Info.GetAccounts())), |
| } |
| for i, aid := range recipient.Info.GetAccounts() { |
| ni.Accounts[i] = aid |
| } |
| res[rts] = ni |
| } else { |
| ni.Accounts = append(ni.Accounts, recipient.Info.GetAccounts()...) |
| } |
| } |
| |
| for _, ni := range res { |
| // Sort & dedup accounts in each notification bucket. |
| sort.Slice(ni.Accounts, func(i, j int) bool { return ni.Accounts[i] < ni.Accounts[j] }) |
| n := 0 |
| for i := 1; i < len(ni.Accounts); i++ { |
| if ni.Accounts[n] == ni.Accounts[i] { |
| continue |
| } |
| n++ |
| ni.Accounts[n] = ni.Accounts[i] |
| } |
| ni.Accounts = ni.Accounts[:n+1] |
| } |
| return res |
| } |
| |
| type attentionSetInput struct { |
| User string `json:"user"` |
| Reason string `json:"reason"` |
| Notify string `json:"string,omitempty"` |
| NotifyDetails notifyDetails `json:"notify_details,omitempty"` |
| } |
| |
| func toAttentionSetInput(in *gerritpb.AttentionSetInput) *attentionSetInput { |
| return &attentionSetInput{ |
| User: in.User, |
| Reason: in.Reason, |
| Notify: enumToString(int32(in.Notify.Number()), gerritpb.Notify_name), |
| NotifyDetails: toNotifyDetails(in.NotifyDetails), |
| } |
| } |
| |
| func toAttentionSetInputs(in []*gerritpb.AttentionSetInput) []*attentionSetInput { |
| if len(in) == 0 { |
| return nil |
| } |
| out := make([]*attentionSetInput, len(in)) |
| for i, x := range in { |
| out[i] = toAttentionSetInput(x) |
| } |
| return out |
| } |
| |
| type reviewerInput struct { |
| Reviewer string `json:"reviewer"` |
| State string `json:"state,omitempty"` |
| } |
| |
| func toReviewerInputs(in []*gerritpb.ReviewerInput) []*reviewerInput { |
| if len(in) == 0 { |
| return nil |
| } |
| |
| out := make([]*reviewerInput, len(in)) |
| for i, x := range in { |
| out[i] = &reviewerInput{ |
| Reviewer: x.Reviewer, |
| State: enumToString(int32(x.State.Number()), gerritpb.ReviewerInput_State_name), |
| } |
| } |
| return out |
| } |
| |
| type reviewResult struct { |
| Labels map[string]int32 `json:"labels,omitempty"` |
| Reviewers map[string]*addReviewerResult `json:"reviewers,omitempty"` |
| } |
| |
| func (rr *reviewResult) ToProto() (*gerritpb.ReviewResult, error) { |
| result := &gerritpb.ReviewResult{ |
| Labels: rr.Labels, |
| } |
| if len(rr.Reviewers) == 0 { |
| return result, nil |
| } |
| |
| reviewers := make(map[string]*gerritpb.AddReviewerResult, len(rr.Reviewers)) |
| for i, x := range rr.Reviewers { |
| reviewerDetails, err := x.ToProto() |
| if err != nil { |
| return nil, err |
| } |
| reviewers[i] = reviewerDetails |
| } |
| result.Reviewers = reviewers |
| |
| return result, nil |
| } |
| |
| type projectInfo struct { |
| ID string `json:"id,omitempty"` |
| Parent string `json:"parent,omitempty"` |
| Description string `json:"description,omitempty"` |
| State string `json:"state,omitempty"` |
| Branches map[string]string `json:"branches,omitempty"` |
| WebLinks []*gerritpb.WebLinkInfo `json:"web_links,omitempty"` |
| } |
| |
| func (pi *projectInfo) ToProto() (*gerritpb.ProjectInfo, error) { |
| stateEnumVal := "PROJECT_STATE_" + pi.State |
| stateEnumNum, found := gerritpb.ProjectInfo_State_value[stateEnumVal] |
| if !found { |
| return nil, errors.Reason("no State enum value for %q", pi.State).Err() |
| } |
| projectName, err := url.QueryUnescape(pi.ID) |
| if err != nil { |
| return nil, errors.Annotate(err, "decoding name").Err() |
| } |
| absoluteRefs := make(map[string]string, len(pi.Branches)) |
| for ref, sha1 := range pi.Branches { |
| absoluteRefs[branchToRef(ref)] = sha1 |
| } |
| return &gerritpb.ProjectInfo{ |
| Name: projectName, |
| Parent: pi.Parent, |
| Description: pi.Description, |
| State: gerritpb.ProjectInfo_State(stateEnumNum), |
| Refs: absoluteRefs, |
| WebLinks: pi.WebLinks, |
| }, nil |
| } |
| |
| // https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#submit-info |
| type submitInfo struct { |
| Status string `json:"status"` |
| } |
| |
| func (si *submitInfo) ToProto() *gerritpb.SubmitInfo { |
| return &gerritpb.SubmitInfo{ |
| Status: gerritpb.ChangeStatus(gerritpb.ChangeStatus_value[si.Status]), |
| } |
| } |
| |
| type submitRequirementResultInfo struct { |
| Name string `json:"name"` |
| Description string `json:"description"` |
| Status string `json:"status"` |
| IsLegacy bool `json:"is_legacy"` |
| ApplicabilityExpressionResult *submitRequirementExpressionInfo `json:"applicability_expression_result"` |
| SubmittabilityExpressionResult *submitRequirementExpressionInfo `json:"submittability_expression_result"` |
| OverrideExpressionResult *submitRequirementExpressionInfo `json:"override_expression_result"` |
| } |
| |
| func (ri *submitRequirementResultInfo) ToProto() (*gerritpb.SubmitRequirementResultInfo, error) { |
| numVal, found := gerritpb.SubmitRequirementResultInfo_Status_value[ri.Status] |
| if !found { |
| return nil, errors.Reason("no Status enum value for %q", ri.Status).Err() |
| } |
| return &gerritpb.SubmitRequirementResultInfo{ |
| Name: ri.Name, |
| Description: ri.Description, |
| Status: gerritpb.SubmitRequirementResultInfo_Status(numVal), |
| IsLegacy: ri.IsLegacy, |
| ApplicabilityExpressionResult: ri.ApplicabilityExpressionResult.ToProto(), |
| SubmittabilityExpressionResult: ri.SubmittabilityExpressionResult.ToProto(), |
| OverrideExpressionResult: ri.OverrideExpressionResult.ToProto(), |
| }, nil |
| } |
| |
| type submitRequirementExpressionInfo struct { |
| Expression string `json:"expression"` |
| Fulfilled bool `json:"fulfilled"` |
| PassingAtoms []string `json:"passing_atoms"` |
| FailingAtoms []string `json:"failing_atoms"` |
| ErrorMessage string `json:"error_message"` |
| } |
| |
| func (ei *submitRequirementExpressionInfo) ToProto() *gerritpb.SubmitRequirementExpressionInfo { |
| if ei == nil { |
| return nil |
| } |
| return &gerritpb.SubmitRequirementExpressionInfo{ |
| Expression: ei.Expression, |
| Fulfilled: ei.Fulfilled, |
| PassingAtoms: ei.PassingAtoms, |
| FailingAtoms: ei.FailingAtoms, |
| ErrorMessage: ei.ErrorMessage, |
| } |
| } |
| |
| type metaDiff struct { |
| Added *changeInfo `json:"added"` |
| Removed *changeInfo `json:"removed"` |
| OldChangeInfo *changeInfo `json:"old_change_info"` |
| NewChangeInfo *changeInfo `json:"new_change_info"` |
| } |
| |
| func (md *metaDiff) ToProto() (*gerritpb.MetaDiff, error) { |
| var resp gerritpb.MetaDiff |
| var ci *gerritpb.ChangeInfo |
| var err error |
| |
| if ci, err = md.Added.ToProto(); err != nil { |
| return nil, err |
| } |
| resp.Added = ci |
| if ci, err = md.Removed.ToProto(); err != nil { |
| return nil, err |
| } |
| resp.Removed = ci |
| if ci, err = md.OldChangeInfo.ToProto(); err != nil { |
| return nil, err |
| } |
| resp.OldChangeInfo = ci |
| if ci, err = md.NewChangeInfo.ToProto(); err != nil { |
| return nil, err |
| } |
| resp.NewChangeInfo = ci |
| return &resp, nil |
| } |