github.com/abayer/test-infra@v0.0.5/mungegithub/mungers/cherrypick-clear-after-merge.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mungers
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	"k8s.io/apimachinery/pkg/util/sets"
    24  	"k8s.io/test-infra/mungegithub/features"
    25  	"k8s.io/test-infra/mungegithub/github"
    26  	"k8s.io/test-infra/mungegithub/options"
    27  
    28  	"github.com/golang/glog"
    29  )
    30  
    31  const (
    32  	clearAfterMergeName = "cherrypick-clear-after-merge"
    33  )
    34  
    35  type LogFinder interface {
    36  	FoundLog(branch, logString string, regexSearch bool) (bool, string)
    37  }
    38  
    39  // ClearPickAfterMerge will remove the cherrypick-candidate label from
    40  // any PR that does not have a 'release' milestone set.
    41  type ClearPickAfterMerge struct {
    42  	features *features.Features
    43  	logs     LogFinder
    44  }
    45  
    46  func init() {
    47  	RegisterMungerOrDie(&ClearPickAfterMerge{})
    48  }
    49  
    50  // Name is the name usable in --pr-mungers
    51  func (c *ClearPickAfterMerge) Name() string { return clearAfterMergeName }
    52  
    53  // RequiredFeatures is a slice of 'features' that must be provided
    54  func (c *ClearPickAfterMerge) RequiredFeatures() []string { return []string{features.RepoFeatureName} }
    55  
    56  // Initialize will initialize the munger
    57  func (c *ClearPickAfterMerge) Initialize(config *github.Config, features *features.Features) error {
    58  	c.features = features
    59  	c.logs = c
    60  	return nil
    61  }
    62  
    63  // EachLoop is called at the start of every munge loop
    64  func (c *ClearPickAfterMerge) EachLoop() error { return nil }
    65  
    66  // RegisterOptions registers options for this munger; returns any that require a restart when changed.
    67  func (c *ClearPickAfterMerge) RegisterOptions(opts *options.Options) sets.String { return nil }
    68  
    69  func handleFound(obj *github.MungeObject, branch string) error {
    70  	msg := fmt.Sprintf("Commit found in the %q branch appears to be this PR. Removing the %q label. If this is an error find help to get your PR picked.", branch, cpCandidateLabel)
    71  	obj.WriteComment(msg)
    72  	obj.RemoveLabel(cpCandidateLabel)
    73  	return nil
    74  }
    75  
    76  // FoundLog will return if the given `logString` exists on the branch in question.
    77  // it will also return the actual logs for further processing
    78  func (c *ClearPickAfterMerge) FoundLog(branch, logString string, regexSearch bool) (bool, string) {
    79  	args := []string{"merge-base", "origin/master", "origin/" + branch}
    80  	out, err := c.features.Repos.GitCommand(args)
    81  	base := string(out)
    82  	if err != nil {
    83  		glog.Errorf("Unable to find the fork point for branch %s. %s:%v", branch, base, err)
    84  		return false, ""
    85  	}
    86  	lines := strings.Split(base, "\n")
    87  	if len(lines) < 1 {
    88  		glog.Errorf("Found 0 lines splitting the results of git merge-base")
    89  	}
    90  	base = lines[0]
    91  
    92  	// if release-1.2 branched from master at abcdef123 this should result in:
    93  	// abcdef123..origin/release-1.2
    94  	logRefs := fmt.Sprintf("%s..origin/%s", base, branch)
    95  
    96  	var regexFlag string
    97  	if regexSearch {
    98  		regexFlag = "-E"
    99  	} else {
   100  		regexFlag = "-F"
   101  	}
   102  	args = []string{"log", "--pretty=tformat:%H%n%s%n%b", regexFlag, "--grep", logString, logRefs}
   103  	out, err = c.features.Repos.GitCommand(args)
   104  	logs := string(out)
   105  	if err != nil {
   106  		glog.Errorf("Error grepping logs out=%q: %v", logs, err)
   107  		return false, ""
   108  	}
   109  	glog.V(10).Infof("args:%v", args)
   110  	return true, logs
   111  }
   112  
   113  // Can we find a commit in the changelog that looks like it was done using git cherry-pick -m1 -x ?
   114  func (c *ClearPickAfterMerge) foundByPickDashX(obj *github.MungeObject, branch string) bool {
   115  	sha, ok := obj.MergeCommit()
   116  	if !ok {
   117  		return false
   118  	}
   119  	if sha == nil {
   120  		glog.Errorf("Unable to get SHA of merged PR %d", *obj.Issue.Number)
   121  		return false
   122  	}
   123  
   124  	cherrypickMsg := fmt.Sprintf("(cherry picked from commit %s)", *sha)
   125  	found, logs := c.logs.FoundLog(branch, cherrypickMsg, false)
   126  	if !found {
   127  		return false
   128  	}
   129  
   130  	// double check for the 'non -x' message
   131  	logMsg := fmt.Sprintf("Merge pull request #%d from ", *obj.Issue.Number)
   132  	if !strings.Contains(logs, logMsg) {
   133  		return false
   134  	}
   135  	glog.Infof("Found cherry-pick for %d using -x information in branch %q", *obj.Issue.Number, branch)
   136  	return true
   137  }
   138  
   139  // Can we find a commit in the changelog that looks like it was done using git cherry-pick -m1 ?
   140  func (c *ClearPickAfterMerge) foundByPickWithoutDashX(obj *github.MungeObject, branch string) bool {
   141  	logMsg := fmt.Sprintf("Merge pull request #%d from ", *obj.Issue.Number)
   142  
   143  	found, _ := c.logs.FoundLog(branch, logMsg, false)
   144  	if found {
   145  		glog.Infof("Found cherry-pick for %d using log matching for `git cherry-pick` in branch %q", *obj.Issue.Number, branch)
   146  	}
   147  	return found
   148  }
   149  
   150  // Check that the commit messages for all commits in the PR are on the branch
   151  func (c *ClearPickAfterMerge) foundByAllCommits(obj *github.MungeObject, branch string) bool {
   152  	commits, ok := obj.GetCommits()
   153  	if !ok {
   154  		glog.Infof("unable to get commits")
   155  		return false
   156  	}
   157  	for _, commit := range commits {
   158  		if commit.Commit == nil {
   159  			return false
   160  		}
   161  		if commit.Commit.Message == nil {
   162  			return false
   163  		}
   164  		found, _ := c.logs.FoundLog(branch, *commit.Commit.Message, false)
   165  		if !found {
   166  			return false
   167  		}
   168  	}
   169  	return true
   170  }
   171  
   172  // Can we find a commit in the changelog that looks like it was done using the hack/cherry_pick_pull.sh script ?
   173  func (c *ClearPickAfterMerge) foundByScript(obj *github.MungeObject, branch string) bool {
   174  	logMsg := fmt.Sprintf(`^Automated cherry pick of( #[0-9]+)* #%d( #[0-9]+)*$`, *obj.Issue.Number)
   175  
   176  	found, _ := c.logs.FoundLog(branch, logMsg, true)
   177  	if found {
   178  		glog.Infof("Found cherry-pick for %d using log matching for `hack/cherry_pick_pull.sh` in branch %q", *obj.Issue.Number, branch)
   179  	}
   180  	return found
   181  }
   182  
   183  // Munge is the workhorse the will actually make updates to the PR
   184  func (c *ClearPickAfterMerge) Munge(obj *github.MungeObject) {
   185  	if !obj.IsPR() {
   186  		return
   187  	}
   188  	if !obj.HasLabel(cpCandidateLabel) {
   189  		return
   190  	}
   191  
   192  	if merged, ok := obj.IsMerged(); !ok || !merged {
   193  		return
   194  	}
   195  
   196  	releaseMilestone, ok := obj.ReleaseMilestone()
   197  	if !ok || releaseMilestone == "" || len(releaseMilestone) != 4 {
   198  		glog.Errorf("Found invalid milestone: %q", releaseMilestone)
   199  		return
   200  	}
   201  	rel := releaseMilestone[1:]
   202  	branch := "release-" + rel
   203  
   204  	if c.foundByPickDashX(obj, branch) {
   205  		handleFound(obj, branch)
   206  		return
   207  	}
   208  
   209  	if c.foundByAllCommits(obj, branch) {
   210  		handleFound(obj, branch)
   211  		return
   212  	}
   213  
   214  	if c.foundByPickWithoutDashX(obj, branch) {
   215  		handleFound(obj, branch)
   216  		return
   217  	}
   218  
   219  	if c.foundByScript(obj, branch) {
   220  		handleFound(obj, branch)
   221  		return
   222  	}
   223  
   224  	return
   225  }