@@ -3,7 +3,7 @@ package dotty.tools.bot
3
3
import org .http4s .{ Status => _ , _ }
4
4
import org .http4s .client .blaze ._
5
5
import org .http4s .client .Client
6
- import org .http4s .headers .Authorization
6
+ import org .http4s .headers .{ Accept , Authorization }
7
7
8
8
import cats .syntax .applicative ._
9
9
import scalaz .concurrent .Task
@@ -47,6 +47,10 @@ trait PullRequestService {
47
47
new Authorization (creds)
48
48
}
49
49
50
+ private [this ] lazy val previewAcceptHeader =
51
+ Accept .parse(" application/vnd.github.black-cat-preview+json" )
52
+ .getOrElse(throw new Exception (" Couldn't initialize accept header" ))
53
+
50
54
private final case class CLASignature (
51
55
user : String ,
52
56
signed : Boolean ,
@@ -68,14 +72,20 @@ trait PullRequestService {
68
72
def issueCommentsUrl (issueNbr : Int ): String =
69
73
s " $githubUrl/repos/lampepfl/dotty/issues/ $issueNbr/comments "
70
74
75
+ def reviewUrl (issueNbr : Int ): String =
76
+ s " $githubUrl/repos/lampepfl/dotty/pulls/ $issueNbr/reviews "
77
+
71
78
def toUri (url : String ): Task [Uri ] =
72
79
Uri .fromString(url).fold(Task .fail, Task .now)
73
80
74
81
def getRequest (endpoint : Uri ): Task [Request ] =
75
- Request (uri = endpoint, method = Method .GET ).putHeaders(authHeader).pure[Task ]
82
+ Request (uri = endpoint, method = Method .GET ).putHeaders(authHeader)
83
+ .pure[Task ]
76
84
77
85
def postRequest (endpoint : Uri ): Task [Request ] =
78
- Request (uri = endpoint, method = Method .POST ).putHeaders(authHeader).pure[Task ]
86
+ Request (uri = endpoint, method = Method .POST )
87
+ .putHeaders(authHeader, previewAcceptHeader)
88
+ .pure[Task ]
79
89
80
90
def shutdownClient (client : Client ): Task [Unit ] =
81
91
client.shutdownNow().pure[Task ]
@@ -191,11 +201,87 @@ trait PullRequestService {
191
201
private def usersFromInvalid (xs : List [CommitStatus ]) =
192
202
xs.collect { case Invalid (user, _) => user }
193
203
194
- def sendInitialComment (invalidUsers : List [String ]): Task [Unit ] = ???
204
+ def hasBadCommitMessages (commits : List [Commit ]): Boolean =
205
+ commits.exists { cm =>
206
+ val firstLine = cm.commit.message.takeWhile(_ != '\n ' ).trim.toLowerCase
207
+ val firstWord = firstLine.takeWhile(x => x != ':' && x != ' ' )
208
+ val containsColon = firstLine.contains(':' )
195
209
196
- def checkFreshPR (issue : Issue ): Task [Response ] = {
210
+ val wrongTense = firstWord match {
211
+ case " added" | " fixed" | " removed" | " moved" | " changed" |
212
+ " finalized" | " re-added"
213
+ => true
214
+
215
+ case " adds" | " fixes" | " removes" | " moves" | " changes" |
216
+ " finalizes" | " re-adds"
217
+ => true
218
+
219
+ case _
220
+ => false
221
+ }
222
+
223
+ wrongTense || firstLine.last == '.' || firstLine.length > 100
224
+ }
197
225
226
+ def sendInitialComment (issueNbr : Int , invalidUsers : List [String ], commits : List [Commit ], client : Client ): Task [ReviewResponse ] = {
227
+
228
+ val cla = if (invalidUsers.nonEmpty) {
229
+ s """ |## CLA ##
230
+ |In order for us to be able to accept your contribution, all users
231
+ |must sign the Scala CLA.
232
+ |
233
+ |Users:
234
+ | ${ invalidUsers.map(" @" + _).mkString(" - " , " \n - " , " " ) }
235
+ |
236
+ |Could you folks please sign the CLA? :pray:
237
+ |
238
+ |Please do this here: https://www.lightbend.com/contribute/cla/scala
239
+ | """ .stripMargin
240
+ } else " All contributors have signed the CLA, thank you! :heart:"
241
+
242
+ val commitRules = if (hasBadCommitMessages(commits)) {
243
+ """ |## Commit Messages ##
244
+ |We want to keep history, but for that to actually be useful we have
245
+ |some rules on how to format our commit messages ([relevant xkcd](https://xkcd.com/1296/)).
246
+ |
247
+ |Please stick to these guidelines for commit messages:
248
+ |
249
+ |> 1. Separate subject from body with a blank line
250
+ |> 1. When fixing an issue, start your commit message with `Fix #<ISSUE-NBR>: `
251
+ |> 1. Limit the subject line to 80 characters
252
+ |> 1. Capitalize the subject line
253
+ |> 1. Do not end the subject line with a period
254
+ |> 1. Use the imperative mood in the subject line ("Added" instead of "Add")
255
+ |> 1. Wrap the body at 80 characters
256
+ |> 1. Use the body to explain what and why vs. how
257
+ |>
258
+ |> adapted from https://chris.beams.io/posts/git-commit""" .stripMargin
259
+ } else " "
260
+
261
+ val body =
262
+ s """ |Hello, and thank you for opening this PR! :tada:
263
+ |
264
+ |If you haven't already, please request a review from one of our
265
+ |collaborators (have no fear, we don't bite)!
266
+ |
267
+ | $cla
268
+ |
269
+ | $commitRules
270
+ |
271
+ |Have an awesome day! :sunny: """ .stripMargin
272
+
273
+ val review = Review .comment(body)
274
+
275
+ for {
276
+ endpoint <- toUri(reviewUrl(issueNbr))
277
+ req <- postRequest(endpoint).withBody(review.asJson)
278
+ res <- client.expect(req)(jsonOf[ReviewResponse ])
279
+ } yield res
280
+ }
281
+
282
+ def checkFreshPR (issue : Issue ): Task [Response ] = {
198
283
val httpClient = PooledHttp1Client ()
284
+
199
285
for {
200
286
commits <- getCommits(issue.number, httpClient)
201
287
statuses <- checkCLA(commits, httpClient)
@@ -215,7 +301,7 @@ trait PullRequestService {
215
301
}
216
302
217
303
// Send positive comment:
218
- _ <- sendInitialComment(invalidUsers)
304
+ _ <- sendInitialComment(issue.number, invalidUsers, commits, httpClient )
219
305
_ <- shutdownClient(httpClient)
220
306
resp <- Ok (" Fresh PR checked" )
221
307
} yield resp
0 commit comments