go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/util/changelogutil/changelogutil.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package changelogutil contains utility functions for changelogs.
    16  package changelogutil
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  
    23  	"go.chromium.org/luci/bisection/internal/gitiles"
    24  	"go.chromium.org/luci/bisection/model"
    25  	pb "go.chromium.org/luci/bisection/proto/v1"
    26  	bbpb "go.chromium.org/luci/buildbucket/proto"
    27  	"go.chromium.org/luci/common/logging"
    28  )
    29  
    30  // GetChangeLogs queries Gitiles for changelogs in the regression range.
    31  // If shouldIncludeLastPass is true, the result should also include the last pass revision.
    32  // The result will be in descending order of recency (i.e. first failed revision at index 0).
    33  func GetChangeLogs(c context.Context, rr *pb.RegressionRange, shouldIncludeLastPass bool) ([]*model.ChangeLog, error) {
    34  	if rr.LastPassed.Host != rr.FirstFailed.Host || rr.LastPassed.Project != rr.FirstFailed.Project {
    35  		return nil, fmt.Errorf("RepoURL for last pass and first failed commits must be same, but aren't: %v and %v", rr.LastPassed, rr.FirstFailed)
    36  	}
    37  	repoURL := gitiles.GetRepoUrl(c, rr.FirstFailed)
    38  	lastPassID := rr.LastPassed.Id
    39  	if shouldIncludeLastPass {
    40  		lastPassID = fmt.Sprintf("%s^1", lastPassID)
    41  	}
    42  	return gitiles.GetChangeLogs(c, repoURL, lastPassID, rr.FirstFailed.Id)
    43  }
    44  
    45  func ChangeLogsToBlamelist(ctx context.Context, changeLogs []*model.ChangeLog) *pb.BlameList {
    46  	if len(changeLogs) == 0 {
    47  		return &pb.BlameList{}
    48  	}
    49  	commits := []*pb.BlameListSingleCommit{}
    50  	for i := 0; i < len(changeLogs)-1; i++ {
    51  		cl := changeLogs[i]
    52  		commits = append(commits, changelogToCommit(ctx, cl))
    53  	}
    54  	return &pb.BlameList{
    55  		Commits:        commits,
    56  		LastPassCommit: changelogToCommit(ctx, changeLogs[len(changeLogs)-1]),
    57  	}
    58  }
    59  
    60  func changelogToCommit(ctx context.Context, cl *model.ChangeLog) *pb.BlameListSingleCommit {
    61  	reviewURL, err := cl.GetReviewUrl()
    62  	if err != nil {
    63  		// Just log, this is not important for nth-section analysis
    64  		logging.Errorf(ctx, "Error getting review URL: %s", err)
    65  	}
    66  
    67  	reviewTitle, err := cl.GetReviewTitle()
    68  	if err != nil {
    69  		// Just log, this is not important for nth-section analysis
    70  		logging.Errorf(ctx, "Error getting review title: %s", err)
    71  	}
    72  
    73  	commitTime, err := cl.GetCommitTime()
    74  	if err != nil {
    75  		// Just log, this is informational.
    76  		logging.Errorf(ctx, "Error getting commit time: %s", err)
    77  	}
    78  
    79  	return &pb.BlameListSingleCommit{
    80  		Commit:      cl.Commit,
    81  		ReviewUrl:   reviewURL,
    82  		ReviewTitle: reviewTitle,
    83  		CommitTime:  commitTime,
    84  	}
    85  }
    86  
    87  // SetCommitPositionInBlamelist sets the position field in BlameListSingleCommits of this blamelist.
    88  // Commits in the blamelist are ordered by commit position in descending order.
    89  // Index 0 refers to the highest-position commit in the regression range, with has the commit position same as regression end position.
    90  // Index n-1 refers to the lowest-position commit in the regression range.
    91  // We can find the commit position of all commits in between.
    92  func SetCommitPositionInBlamelist(blamelist *pb.BlameList, regressionStartPosition, regressionEndPosition int64) error {
    93  	if int(regressionEndPosition-regressionStartPosition) != len(blamelist.Commits) {
    94  		msg := fmt.Sprintf("Number of changelog in the regression range (%d) doesn't match the regression commit position (%d)-(%d)",
    95  			len(blamelist.Commits), regressionStartPosition, regressionEndPosition)
    96  		return errors.New(msg)
    97  	}
    98  	for i, c := range blamelist.Commits {
    99  		c.Position = regressionEndPosition - int64(i)
   100  	}
   101  	blamelist.LastPassCommit.Position = regressionStartPosition
   102  	return nil
   103  }
   104  
   105  // FindCommitIndexInBlameList find the index of the gitiles commit in blamelist.
   106  // It returns -1 if it couldn't find.
   107  func FindCommitIndexInBlameList(gitilesCommit *bbpb.GitilesCommit, blamelist *pb.BlameList) int {
   108  	for i, commit := range blamelist.Commits {
   109  		if commit.Commit == gitilesCommit.Id {
   110  			return i
   111  		}
   112  	}
   113  	return -1
   114  }