blob: fbce91e4845d9df74d74cc75fa91ac238578093b [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package git
import (
"context"
"fmt"
"go.chromium.org/luci/common/api/gerrit"
gerritpb "go.chromium.org/luci/common/proto/gerrit"
"net/http"
"testplans/internal/shared"
"time"
)
// ChangeRevKey is the necessary set of data for looking up a single Gerrit revision.
type ChangeRevKey struct {
Host string
ChangeNum int64
Revision int32
}
func (cik ChangeRevKey) String() string {
return fmt.Sprintf("%s:%d:%d", cik.Host, cik.ChangeNum, cik.Revision)
}
// ChangeRev contains data about a Gerrit change,revision pair.
type ChangeRev struct {
ChangeRevKey
Project string
Files []string
}
var (
// Override this to use a mock GerritClient rather than the real one.
mockGerrit gerritpb.GerritClient
)
// GetChangeRev gets details from Gerrit about a change,revision pair.
func GetChangeRev(authedClient *http.Client, ctx context.Context, changeNum int64, revision int32, host string) (*ChangeRev, error) {
var g gerritpb.GerritClient
var err error
if mockGerrit != nil {
g = mockGerrit
} else {
if g, err = gerrit.NewRESTClient(authedClient, host, true); err != nil {
return nil, err
}
}
ctx, _ = context.WithTimeout(ctx, 5*time.Minute)
ch := make(chan *gerritpb.ChangeInfo, 1)
err = shared.DoWithRetry(ctx, shared.DefaultOpts, func() error {
// This sets the deadline for the individual API call, while the outer context sets
// an overall timeout for all attempts.
innerCtx, _ := context.WithTimeout(ctx, 30*time.Second)
change, err := g.GetChange(innerCtx, &gerritpb.GetChangeRequest{
Number: changeNum,
Options: []gerritpb.QueryOption{
gerritpb.QueryOption_ALL_REVISIONS,
gerritpb.QueryOption_ALL_FILES,
}})
if err != nil {
return err
}
ch <- change
return nil
})
if err != nil {
return nil, err
}
change := <-ch
for _, v := range change.GetRevisions() {
if v.Number == revision {
return &ChangeRev{
ChangeRevKey: ChangeRevKey{
Host: host,
ChangeNum: change.Number,
Revision: v.Number,
},
Project: change.Project,
Files: getKeys(v.Files),
}, nil
}
}
return nil, fmt.Errorf("found no revision %d for change %d on host %s", revision, changeNum, host)
}
// ChangeRevData encapsulates a bunch of Gerrit change revisions.
type ChangeRevData struct {
m map[string]*ChangeRev
}
// GetChangeRev extracts a single Gerrit change revision from the ChangeRevData.
func (crv ChangeRevData) GetChangeRev(host string, changeNum int64, revision int32) (*ChangeRev, error) {
key := ChangeRevKey{Host: host, ChangeNum: changeNum, Revision: revision}.String()
val, found := crv.m[key]
if !found {
return nil, fmt.Errorf("No ChangeRev found for key %s", key)
}
return val, nil
}
// GetChangeRevData fetches the Gerrit changes for the provided ChangeIdKeys, and bundles the result
// into a ChangeRevData.
func GetChangeRevData(authedClient *http.Client, ctx context.Context, changeIds []ChangeRevKey) (*ChangeRevData, error) {
output := &ChangeRevData{m: make(map[string]*ChangeRev)}
for _, c := range changeIds {
if _, exists := output.m[c.String()]; !exists {
cr, err := GetChangeRev(authedClient, ctx, c.ChangeNum, c.Revision, c.Host)
if err != nil {
return output, err
}
output.m[c.String()] = cr
}
}
return output, nil
}
// GetChangeRevForTest is intended for testing only, and it allows creation of a ChangeRevData
// through the supplied ChangeRevs.
func GetChangeRevsForTest(cr []*ChangeRev) *ChangeRevData {
output := &ChangeRevData{m: make(map[string]*ChangeRev)}
for _, c := range cr {
output.m[c.String()] = c
}
return output
}
// getKeys extracts the keyset from the provided map.
func getKeys(m map[string]*gerritpb.FileInfo) []string {
keys := make([]string, 0)
for k := range m {
keys = append(keys, k)
}
return keys
}