blob: f3b28bf7b4c19712447e0da6631c6aab1fe96277 [file] [log] [blame]
// Copyright 2020 Google Inc. All Rights Reserved.
//
// 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.
// This package contains the error interface returned by changelog and
// findbuild packages. It includes functions to retrieve HTTP status codes
// from Gerrit and Gitiles errors, and functions to create ChangelogErrors
// relevant to the changelog and findbuild features.
package utils
import (
"context"
"fmt"
"regexp"
"time"
"go.chromium.org/luci/common/proto/git"
log "github.com/sirupsen/logrus"
gitilesProto "go.chromium.org/luci/common/proto/gitiles"
)
const (
manifestFileName string = "snapshot.xml"
// These constants are used for exponential increase in Gitiles request size.
defaultPageSize = 100
pageSizeGrowthMultiplier = 5
maxPageSize = 10000
// Maximum time to wait for a response from a Gitiles request
requestMaxAge = 2 * time.Minute
)
// limitPageSize will restrict a request page size to min of pageSize (which grows exponentially)
// or remaining request size
func limitPageSize(pageSize, querySize int, noLimit bool) int {
if pageSize > maxPageSize {
pageSize = maxPageSize
}
if noLimit || pageSize <= querySize {
return pageSize
}
return querySize
}
// DownloadManifest retrieves a manifest file from Git on Borg for a specific
// build number
func DownloadManifest(client gitilesProto.GitilesClient, manifestRepo, buildNum string) (*gitilesProto.DownloadFileResponse, error) {
log.Debugf("Downloading manifest file for build %s", buildNum)
request := gitilesProto.DownloadFileRequest{
Project: manifestRepo,
Committish: "refs/tags/" + buildNum,
Path: manifestFileName,
Format: 1,
}
ctx, cancel := context.WithTimeout(context.Background(), requestMaxAge)
defer cancel()
response, err := client.DownloadFile(ctx, &request)
return response, err
}
func nextCommits(client gitilesProto.GitilesClient, repo string, committish string, ancestor string, nextToken string, pageSize int) (*gitilesProto.LogResponse, error) {
request := gitilesProto.LogRequest{
Project: repo,
Committish: committish,
ExcludeAncestorsOf: ancestor,
PageToken: nextToken,
PageSize: int32(pageSize),
}
ctx, cancel := context.WithTimeout(context.Background(), requestMaxAge)
defer cancel()
return client.Log(ctx, &request)
}
// Commits retrieves querySize commits that occur between a committish and an ancestor
// for a given repository. Returns a list of commits and a bool that is set to true
// if there are more than querySize commits between the two provided committishs.
func Commits(client gitilesProto.GitilesClient, repo string, committish string, ancestor string, querySize int) ([]*git.Commit, bool, error) {
log.Debugf("Fetching changelog for repo: %s from: %s to: %s\n", repo, ancestor, committish)
if querySize < -1 {
return nil, false, fmt.Errorf("commits: %d is not a valid querySize. Please specify a positive querySize, or -1 for all commits", querySize)
}
start := time.Now()
noLimit := querySize == -1
pageSize := limitPageSize(defaultPageSize, querySize, noLimit)
querySize -= pageSize
response, err := nextCommits(client, repo, committish, ancestor, "", pageSize)
if err != nil {
return nil, false, fmt.Errorf("commits: Error retrieving commits for repo %s with committish %s and ancestor %s:\n%w", repo, committish, ancestor, err)
}
// No nextPageToken means there were less than <defaultPageSize> commits total.
// We can immediately return.
if response.NextPageToken == "" {
log.Debugf("Retrieved %d commits from %s in %s\n", len(response.Log), repo, time.Since(start))
return response.Log, false, nil
}
// Retrieve remaining commits using exponential increase in pageSize.
allCommits := response.Log
for (noLimit || querySize > 0) && response.NextPageToken != "" {
if pageSize < maxPageSize {
pageSize *= pageSizeGrowthMultiplier
}
pageSize = limitPageSize(pageSize, querySize, noLimit)
log.Debugf("More commits remaining, expanding page size to %d commits", pageSize)
querySize -= pageSize
response, err = nextCommits(client, repo, committish, ancestor, response.NextPageToken, pageSize)
if err != nil {
return nil, false, fmt.Errorf("commits: Error retrieving next page commits for repo %s with committish %s and ancestor %s:\n%w", repo, committish, ancestor, err)
}
allCommits = append(allCommits, response.Log...)
}
log.Debugf("Retrieved %d commits from %s in %s\n", len(allCommits), repo, time.Since(start))
return allCommits, response.NextPageToken != "", nil
}
// CreateGerritURL creates a Gerrit URL from a given
// Gitiles Host URL. For example: If the given Gitiles
// Host URL is: https://cos.googlesource.com, then it will
// return https://cos-review.googlesource.com. In case the
// Gitiles Host URL is in incorrect format, error is returned.
func CreateGerritURL(gitilesHostURL string) (string, error) {
re := regexp.MustCompile("(.+://)?(.+?).googlesource.com")
ss := re.FindStringSubmatch(gitilesHostURL)
if len(ss) > 0 {
return fmt.Sprintf("https://%s-review.googlesource.com", ss[len(ss)-1]), nil
}
return "", fmt.Errorf("failed to created Gerrit Host URL from invalid Gitiles Host URL: %q", gitilesHostURL)
}