github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/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/kubernetes/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  // ClearPickAfterMerge will remove the the cherrypick-candidate label from
    36  // any PR that does not have a 'release' milestone set.
    37  type ClearPickAfterMerge struct {
    38  	features *features.Features
    39  }
    40  
    41  func init() {
    42  	RegisterMungerOrDie(&ClearPickAfterMerge{})
    43  }
    44  
    45  // Name is the name usable in --pr-mungers
    46  func (c *ClearPickAfterMerge) Name() string { return clearAfterMergeName }
    47  
    48  // RequiredFeatures is a slice of 'features' that must be provided
    49  func (c *ClearPickAfterMerge) RequiredFeatures() []string { return []string{features.RepoFeatureName} }
    50  
    51  // Initialize will initialize the munger
    52  func (c *ClearPickAfterMerge) Initialize(config *github.Config, features *features.Features) error {
    53  	c.features = features
    54  	return nil
    55  }
    56  
    57  // EachLoop is called at the start of every munge loop
    58  func (c *ClearPickAfterMerge) EachLoop() error { return nil }
    59  
    60  // RegisterOptions registers options for this munger; returns any that require a restart when changed.
    61  func (c *ClearPickAfterMerge) RegisterOptions(opts *options.Options) sets.String { return nil }
    62  
    63  func handleFound(obj *github.MungeObject, branch string) error {
    64  	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)
    65  	obj.WriteComment(msg)
    66  	obj.RemoveLabel(cpCandidateLabel)
    67  	return nil
    68  }
    69  
    70  // foundLog will return if the given `logString` exists on the branch in question.
    71  // it will also return the actual logs for further processing
    72  func (c *ClearPickAfterMerge) foundLog(branch string, logString string) (bool, string) {
    73  	args := []string{"merge-base", "origin/master", "origin/" + branch}
    74  	out, err := c.features.Repos.GitCommand(args)
    75  	base := string(out)
    76  	if err != nil {
    77  		glog.Errorf("Unable to find the fork point for branch %s. %s:%v", branch, base, err)
    78  		return false, ""
    79  	}
    80  	lines := strings.Split(base, "\n")
    81  	if len(lines) < 1 {
    82  		glog.Errorf("Found 0 lines splitting the results of git merge-base")
    83  	}
    84  	base = lines[0]
    85  
    86  	// if release-1.2 branched from master at abcdef123 this should result in:
    87  	// abcdef123..origin/release-1.2
    88  	logRefs := fmt.Sprintf("%s..origin/%s", base, branch)
    89  
    90  	args = []string{"log", "--pretty=tformat:%H%n%s%n%b", "-F", "--grep", logString, logRefs}
    91  	out, err = c.features.Repos.GitCommand(args)
    92  	logs := string(out)
    93  	if err != nil {
    94  		glog.Errorf("Error grepping logs out=%q: %v", logs, err)
    95  		return false, ""
    96  	}
    97  	glog.V(10).Infof("args:%v", args)
    98  
    99  	glog.V(10).Infof("Searching for %q in %q", logString, logs)
   100  	if !strings.Contains(logs, logString) {
   101  		return false, ""
   102  	}
   103  	return true, logs
   104  }
   105  
   106  // Can we find a commit in the changelog that looks like it was done using git cherry-pick -m1 -x ?
   107  func (c *ClearPickAfterMerge) foundByPickDashX(obj *github.MungeObject, branch string) bool {
   108  	sha, ok := obj.MergeCommit()
   109  	if !ok {
   110  		return false
   111  	}
   112  	if sha == nil {
   113  		glog.Errorf("Unable to get SHA of merged PR %d", *obj.Issue.Number)
   114  		return false
   115  	}
   116  
   117  	cherrypickMsg := fmt.Sprintf("(cherry picked from commit %s)", *sha)
   118  	found, logs := c.foundLog(branch, cherrypickMsg)
   119  	if !found {
   120  		return false
   121  	}
   122  
   123  	// double check for the 'non -x' message
   124  	logMsg := fmt.Sprintf("Merge pull request #%d from ", *obj.Issue.Number)
   125  	if !strings.Contains(logs, logMsg) {
   126  		return false
   127  	}
   128  	glog.Infof("Found cherry-pick for %d using -x information in branch %q", *obj.Issue.Number, branch)
   129  	return true
   130  }
   131  
   132  // Can we find a commit in the changelog that looks like it was done using git cherry-pick -m1 ?
   133  func (c *ClearPickAfterMerge) foundByPickWithoutDashX(obj *github.MungeObject, branch string) bool {
   134  	logMsg := fmt.Sprintf("Merge pull request #%d from ", *obj.Issue.Number)
   135  
   136  	found, _ := c.foundLog(branch, logMsg)
   137  	if found {
   138  		glog.Infof("Found cherry-pick for %d using log matching for `git cherry-pick` in branch %q", *obj.Issue.Number, branch)
   139  	}
   140  	return found
   141  }
   142  
   143  // Check that the commit messages for all commits in the PR are on the branch
   144  func (c *ClearPickAfterMerge) foundByAllCommits(obj *github.MungeObject, branch string) bool {
   145  	commits, ok := obj.GetCommits()
   146  	if !ok {
   147  		glog.Infof("unable to get commits")
   148  		return false
   149  	}
   150  	for _, commit := range commits {
   151  		if commit.Commit == nil {
   152  			return false
   153  		}
   154  		if commit.Commit.Message == nil {
   155  			return false
   156  		}
   157  		found, _ := c.foundLog(branch, *commit.Commit.Message)
   158  		if !found {
   159  			return false
   160  		}
   161  	}
   162  	return true
   163  }
   164  
   165  // Can we find a commit in the changelog that looks like it was done using the hack/cherry_pick_pull.sh script ?
   166  func (c *ClearPickAfterMerge) foundByScript(obj *github.MungeObject, branch string) bool {
   167  	logMsg := fmt.Sprintf("Automated cherry pick of #%d", *obj.Issue.Number)
   168  
   169  	found, _ := c.foundLog(branch, logMsg)
   170  	if found {
   171  		glog.Infof("Found cherry-pick for %d using log matching for `hack/cherry_pick_pull.sh` in branch %q", *obj.Issue.Number, branch)
   172  	}
   173  	return found
   174  }
   175  
   176  // Munge is the workhorse the will actually make updates to the PR
   177  func (c *ClearPickAfterMerge) Munge(obj *github.MungeObject) {
   178  	if !obj.IsPR() {
   179  		return
   180  	}
   181  	if !obj.HasLabel(cpCandidateLabel) {
   182  		return
   183  	}
   184  
   185  	if merged, ok := obj.IsMerged(); !ok || !merged {
   186  		return
   187  	}
   188  
   189  	releaseMilestone, ok := obj.ReleaseMilestone()
   190  	if !ok || releaseMilestone == "" || len(releaseMilestone) != 4 {
   191  		glog.Errorf("Found invalid milestone: %q", releaseMilestone)
   192  		return
   193  	}
   194  	rel := releaseMilestone[1:]
   195  	branch := "release-" + rel
   196  
   197  	if c.foundByPickDashX(obj, branch) {
   198  		handleFound(obj, branch)
   199  		return
   200  	}
   201  
   202  	if c.foundByAllCommits(obj, branch) {
   203  		handleFound(obj, branch)
   204  		return
   205  	}
   206  
   207  	if c.foundByPickWithoutDashX(obj, branch) {
   208  		handleFound(obj, branch)
   209  		return
   210  	}
   211  
   212  	if c.foundByScript(obj, branch) {
   213  		handleFound(obj, branch)
   214  		return
   215  	}
   216  
   217  	return
   218  }