blob: 3d186a1c3529d4bf43d098dd91c608c858e1257a [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 gitiles
import (
"context"
"fmt"
"net/http"
"go.chromium.org/luci/common/api/gitiles"
"go.chromium.org/luci/common/proto/git"
gitilespb "go.chromium.org/luci/common/proto/gitiles"
"go.fuchsia.dev/infra/rpcutil"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// noSuchCommittishError will be returned from a function that gets a NotFound
// response from Gitiles when trying to look up a specific ref or commit.
type noSuchCommittishError struct {
remote string
committish string
}
func (e noSuchCommittishError) Error() string {
return fmt.Sprintf("%q does not exist in repository %s", e.committish, e.remote)
}
func (e noSuchCommittishError) IsInfraFailure() bool {
return false
}
// Client provides utilities for interacting with a Gitiles project.
type Client struct {
client gitilespb.GitilesClient
project string
host string
}
// NewClient returns a gitilesClientWrapper for a host and project.
func NewClient(host, project string, httpClient *http.Client) (*Client, error) {
client, err := gitiles.NewRESTClient(httpClient, host, true)
if err != nil {
return nil, err
}
return &Client{client: client, project: project, host: host}, nil
}
// LatestCommit resolves a ref to a revision.
func (c *Client) LatestCommit(ctx context.Context, ref string) (string, error) {
log, err := c.Log(ctx, ref, 1)
if err != nil {
return "", err
}
return log[0].GetId(), nil
}
// Log returns a slice of commits of length `numCommits` starting from `committish`.
func (c *Client) Log(ctx context.Context, committish string, numCommits int32) ([]*git.Commit, error) {
url := fmt.Sprintf("%s/%s/+/%s", c.host, c.project, committish)
req := &gitilespb.LogRequest{
Project: c.project,
Committish: committish,
PageSize: numCommits,
}
var resp *gitilespb.LogResponse
if err := rpcutil.RequestWithRetries(ctx, func() error {
var err error
if resp, err = c.client.Log(ctx, req); err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
return noSuchCommittishError{
remote: fmt.Sprintf("%s/%s", c.host, c.project),
committish: committish,
}
}
return err
}
return nil
// Insufficient Gitiles quota may be a transient error.
// 404s may result from transient Gerrit backend inconsistencies.
}, codes.ResourceExhausted, codes.NotFound); err != nil {
return nil, fmt.Errorf("failed to get log for %s: %w", url, err)
}
if len(resp.Log) == 0 {
return nil, fmt.Errorf("empty log for %s", url)
}
return resp.Log, nil
}
// DownloadFile returns the contents of the file at `path` and `ref`.
func (c *Client) DownloadFile(ctx context.Context, path, ref string) (string, error) {
resp, err := c.client.DownloadFile(ctx, &gitilespb.DownloadFileRequest{
Project: c.project,
Committish: ref,
Path: path,
})
if err != nil {
return "", fmt.Errorf("failed to download %s: %w", path, err)
}
return resp.Contents, nil
}