Welcome to mirror list, hosted at ThFree Co, Russian Federation.

PullRequestEventHandler.scala « server « savage « getbootstrap « com « scala « main « src - github.com/twbs/savage.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: c09d59651e8a37b8429f95ae08a9af992fdf10d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.getbootstrap.savage.server

import java.nio.file.Path
import java.util.regex.Pattern
import scala.collection.JavaConverters._
import scala.util.{Try,Success,Failure}
import akka.actor.ActorRef
import org.eclipse.egit.github.core._
import org.eclipse.egit.github.core.service.{CommitService, OrganizationService, PullRequestService}
import com.getbootstrap.savage.github._
import com.getbootstrap.savage.github.util._
import com.getbootstrap.savage.github.commit_status.StatusForCommit
import com.getbootstrap.savage.util.UnixFileSystemString

class PullRequestEventHandler(
  protected val pusher: ActorRef,
  protected val statusSetter: ActorRef
) extends GitHubActorWithLogging {
  private def affectedFilesFor(repoId: RepositoryId, base: CommitSha, head: CommitSha): Try[Set[Path]] = {
    val commitService = new CommitService(gitHubClient)
    Try { commitService.compare(repoId, base.sha, head.sha) }.map { comparison =>
      val affectedFiles = comparison.getFiles.asScala.map{ "/" + _.getFilename }.toSet[String].map{ _.asUnixPath }
      affectedFiles
    }
  }

  private val NormalPathRegex = "^[a-zA-Z0-9_./-]+$".r
  private def isNormal(path: Path): Boolean = {
    path.toString match {
      case NormalPathRegex(_*) => true
      case _ => {
        log.info(s"Abnormal path: ${path}")
        false
      }
    }
  }
  private def areSafe(paths: Set[Path]): Boolean = {
    implicit val logger = log
    paths.forall{ path => isNormal(path) && settings.Whitelist.isAllowed(path) }
  }
  private def areInteresting(paths: Set[Path]): Boolean = {
    implicit val logger = log
    settings.Watchlist.anyInterestingIn(paths)
  }

  private def isTrusted(user: GitHubUser): Boolean = {
    val orgService = new OrganizationService(gitHubClient)
    settings.TrustedOrganizations.exists{ org => Try{ orgService.isPublicMember(org, user.username) }.toOption.getOrElse(false) }
  }

  private def logPrInfo(msg: String)(implicit prNum: PullRequestNumber) {
    log.info(s"PR #${prNum.number} : ${msg}")
  }

  private val RetryCommentRegex = ("(?i)^" + Pattern.quote(s"@${settings.BotUsername}") + "\\s+retry\\b.*").r

  override def receive = {
    case commentEvent: IssueOrCommentEvent => {
      commentEvent.repository.id match {
        case settings.MainRepoId => {
          commentEvent.prNumber.foreach{ prNum => {
            commentEvent.comment.foreach{ comment => {
              if (isTrusted(comment.user)) {
                comment.body match {
                  case RetryCommentRegex(_*) => {
                    val prService = new PullRequestService(gitHubClient)
                    Try{ prService.getPullRequest(settings.MainRepoId, prNum.number) } match {
                      case Failure(exc) => log.error(exc, s"Error getting ${prNum} for repo ${settings.MainRepoId}!")
                      case Success(pullReq) => {
                        log.info(s"Initiating retry of ${prNum} due to request from trusted ${comment.user}")
                        self ! pullReq
                      }
                    }
                  }
                  case _ => {}
                }
              }
            }}
          }}
        }
        case otherRepo => log.error(s"Received event from GitHub about irrelevant repository: ${otherRepo}")
      }
    }
    case pr: PullRequest => {
      implicit val prNum = pr.number
      val bsBase = pr.getBase
      val prHead = pr.getHead
      val prUser = pr.getUser.username
      val destinationRepo = bsBase.getRepo.repositoryId
      destinationRepo match {
        case None => log.error(s"Received event from GitHub about irrelevant repository with unsafe name")
        case Some(settings.MainRepoId) => {
          bsBase.branch match {
            case None => logPrInfo(s"Ignoring since PR targets the unsafely-named ${bsBase.getRef} branch")
            case Some(destBranch) => {
              if (settings.AllowedBaseBranches.contains(destBranch)) {
                prHead.getRepo.repositoryId match {
                  case None => log.error(s"Received event from GitHub about repository with unsafe name")
                  case Some(foreignRepo) => {
                    val baseSha = bsBase.commitSha
                    val headSha = prHead.commitSha

                    affectedFilesFor(foreignRepo, baseSha, headSha) match {
                      case Failure(exc) => {
                        log.error(exc, s"Could not get affected files for commits ${baseSha}...${headSha} for ${foreignRepo}")
                      }
                      case Success(affectedFiles) => {
                        log.debug("Files affected by {}: {}", prNum, affectedFiles)
                        if (isTrusted(prUser) || areSafe(affectedFiles)) {
                          if (areInteresting(affectedFiles)) {
                            logPrInfo(s"Requesting build for safe & interesting PR")
                            pusher ! PullRequestPushRequest(
                              origin = foreignRepo,
                              number = pr.number,
                              commitSha = headSha
                            )
                            statusSetter ! StatusForCommit(
                              status = commit_status.Pending("Savage has initiated its special separate Travis CI build"),
                              commit = headSha
                            )
                          }
                          else {
                            logPrInfo(s"Ignoring PR with no interesting file changes")
                          }
                        }
                        else {
                          logPrInfo(s"Ignoring PR with unsafe file changes")
                        }
                      }
                    }
                  }
                }
              }
              else {
                logPrInfo(s"Ignoring since PR targets the ${destBranch} branch")
              }
            }
          }
        }
        case Some(otherRepo) => log.error(s"Received event from GitHub about irrelevant repository: ${otherRepo}")
      }
    }
  }
}