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
|
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 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) => {
val destBranch = bsBase.getRef
destBranch match {
case "master" => {
prHead.getRepo.repositoryId match {
case None => log.error(s"Received event from GitHub about repository with unsafe name")
case Some(settings.MainRepoId) if settings.IgnoreBranchesFromMainRepo => log.info("Ignoring PR whose branch is from the main repo, per settings.")
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 (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")
}
}
}
}
}
}
case _ => logPrInfo(s"Ignoring since PR targets the ${destBranch} branch")
}
}
case Some(otherRepo) => log.error(s"Received event from GitHub about irrelevant repository: ${otherRepo}")
}
}
}
}
|