go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/gerrit/equi.go (about)

     1  // Copyright 2020 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 gerrit
    16  
    17  import (
    18  	"sort"
    19  
    20  	"go.chromium.org/luci/common/errors"
    21  	gerritpb "go.chromium.org/luci/common/proto/gerrit"
    22  )
    23  
    24  // EquivalentPatchsetRange computes range of patchsets code-wise equivalent to
    25  // the current patchset.
    26  //
    27  // Gerrit categorises each new patchset (aka Revision) according to difference
    28  // from prior one. The rebases are counted as equivalent, even though
    29  // dependencies may have changed. Thus, only REWORK changes code.
    30  //
    31  // Generally, all patchsets are numbered 1,2,3,...n without gaps. But this
    32  // function doesn't assume this, thus Gerrit might potentially support wiping
    33  // out individual patchsets, creating gaps without affecting CV.
    34  func EquivalentPatchsetRange(info *gerritpb.ChangeInfo) (minEquiPatchset, currentPatchset int, err error) {
    35  	if len(info.Revisions) == 0 {
    36  		err = errors.Reason("ChangeInfo must have all revisions populated").Err()
    37  		return
    38  	}
    39  	revs := make([]*gerritpb.RevisionInfo, 0, len(info.Revisions))
    40  	for _, rev := range info.Revisions {
    41  		revs = append(revs, rev)
    42  	}
    43  	sort.Slice(revs, func(i, j int) bool {
    44  		return revs[i].Number > revs[j].Number // largest patchset first.
    45  	})
    46  
    47  	// Validate ChangeInfo to avoid problems later.
    48  	switch rev, ok := info.Revisions[info.CurrentRevision]; {
    49  	case !ok:
    50  		err = errors.Reason("ChangeInfo must have current_revision populated").Err()
    51  		return
    52  	case rev != revs[0]:
    53  		err = errors.Reason("ChangeInfo.currentPatchset %v doesn't have largest patchset %v", rev, revs[0]).Err()
    54  		return
    55  	}
    56  
    57  	currentPatchset = int(revs[0].Number)
    58  	minEquiPatchset = currentPatchset
    59  	for i, rev := range revs[:len(revs)-1] {
    60  		switch rev.Kind {
    61  		case gerritpb.RevisionInfo_REWORK:
    62  			return
    63  		case gerritpb.RevisionInfo_NO_CHANGE,
    64  			gerritpb.RevisionInfo_NO_CODE_CHANGE,
    65  			gerritpb.RevisionInfo_MERGE_FIRST_PARENT_UPDATE,
    66  			gerritpb.RevisionInfo_TRIVIAL_REBASE:
    67  			minEquiPatchset = int(revs[i+1].Number)
    68  		default:
    69  			err = errors.Reason("Unknown revision kind %d %s ps#%d",
    70  				rev.Kind, rev.Kind, rev.GetNumber()).Err()
    71  			return
    72  		}
    73  	}
    74  	return
    75  }