From dbc2e1e4983e600e57d1e992ab1a6076565bb18a Mon Sep 17 00:00:00 2001 From: Assen Kolov Date: Tue, 11 Jul 2017 21:54:37 +0200 Subject: [PATCH] pushes notes --- README.md | 7 ++-- build.sbt | 2 +- src/it/scala/GithubIT.scala | 6 +-- src/main/scala/Domain.scala | 23 +++++++++--- src/main/scala/Github.scala | 28 ++++++++++++-- src/main/scala/NotesMaker.scala | 65 ++++++++++++++++++++++++--------- 6 files changed, 97 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index f812d3c..f4251ff 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,14 @@ This plugin is under development, the functionality is very basic but usable. In addSbtPlugin("com.akolov" % "sbt-pantarhei" % "0.1.0") -This makes two sbt tasks available: `printNotesForLatest` and `printNotesAfterLatest`: +This makes the following sbt tasks available: | Command | Description | | --------------------------|-------------| -| `printNotesForLatest` | Prints release notes usable for the latest remote tag - that is, form all pull requests _after_ the _previous_ tag, if any, and _before_ the _last_ tag | -| `printNotesAfterLatest` | Prints release notes from the pull requests _after_ the _latest_ tag. These notes will be usable for the _next tag_ | +| `printNotesForLatestTag` | Prints release notes usable for the latest remote tag - that is, form all pull requests _after_ the _previous_ tag, if any, and _before_ the _last_ tag | +| `pushNotesForLatestTag` | Creates or updates release notes for the latest remote tag. | +| `printNotesForNextTag` | Prints release notes from the pull requests _after_ the _latest_ tag. These notes will be usable for the _next tag_ | The output is in markdown, ready to be copy/pasted as github release notes. Example: diff --git a/build.sbt b/build.sbt index 851e8b1..ac9ea55 100644 --- a/build.sbt +++ b/build.sbt @@ -27,7 +27,7 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.8.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.3") scalacOptions := Seq("-deprecation", "-unchecked") -credentials += Credentials(Path.userHome / ".github" / "token") +credentials += Credentials(Path.userHome / ".github" / "credentials") resolvers += "Maven.org" at "http://repo1.maven.org/maven2" pomIncludeRepository := { _ => false } publishMavenStyle := true diff --git a/src/it/scala/GithubIT.scala b/src/it/scala/GithubIT.scala index 2c7571a..8b09998 100644 --- a/src/it/scala/GithubIT.scala +++ b/src/it/scala/GithubIT.scala @@ -1,6 +1,6 @@ import java.io.File -import com.akolov.pantarhei.{FutureCommit, Github, NotesMaker} +import com.akolov.pantarhei._ import org.scalatest.WordSpecLike import scala.io.Source @@ -9,7 +9,7 @@ class GithubIT extends WordSpecLike { val PW = "password=" - val token = Source.fromFile(s"/Users/assen/.github/token") + val token = Source.fromFile(s"/Users/assen/.github/credentials") .getLines() .toList .find { @@ -23,7 +23,7 @@ class GithubIT extends WordSpecLike { "The Notes Plugin" should { "make RN" in { - new NotesMaker(new File("/Users/assen/projects/sbt-pantarhei"), token).makeNotes(FutureCommit) + new NotesMaker(new File("/Users/assen/projects/sbt-pantarhei"), token).makeNotes(LatestComit, PushNotes) } } "The class Github" should { diff --git a/src/main/scala/Domain.scala b/src/main/scala/Domain.scala index 3ba902e..9e0661f 100644 --- a/src/main/scala/Domain.scala +++ b/src/main/scala/Domain.scala @@ -7,18 +7,21 @@ case class PullRequest(title: String, htmlUrl: String, number: Int, createdAt: S case class Commit(sha: String, url: String) -case class CommitInfo(message: String, author: CommitPerson) +case class CommitInfo( message: String, author: CommitPerson) case class CommitPerson(name: String, email: String, date: String) -case class CommitRecord(htmlUrl: String, commit: CommitInfo) +case class CommitRecord(sha: String, htmlUrl: String, commit: CommitInfo) -case class Release(tagName: String, body: String) +case class ReleaseRequest(tagName: String, name: String, body: String) + +case class ReleaseResponse(id: Int, tagName: String, name: String, body: String) case class Tag(tagName: String, commit: Commit) case class CommitterInfo(name: String, email: String, date: String) + object MyJsonProtocol extends DefaultJsonProtocol { implicit val pullRequestFormat: RootJsonFormat[PullRequest] = jsonFormat( @@ -47,12 +50,22 @@ object MyJsonProtocol extends DefaultJsonProtocol { implicit val commitRecordFormat: RootJsonFormat[CommitRecord] = jsonFormat( CommitRecord, + "sha", "html_url", "commit") - implicit val releaseFormat: RootJsonFormat[Release] = jsonFormat( - Release, + implicit val releaseRequestFormat: RootJsonFormat[ReleaseRequest] = jsonFormat( + ReleaseRequest, "tag_name", + "name", + "body" + ) + + implicit val releaseResponseFormat: RootJsonFormat[ReleaseResponse] = jsonFormat( + ReleaseResponse, + "id", + "tag_name", + "name", "body" ) implicit val tagFormat: RootJsonFormat[Tag] = jsonFormat( diff --git a/src/main/scala/Github.scala b/src/main/scala/Github.scala index 8755cfc..ba0d2a4 100644 --- a/src/main/scala/Github.scala +++ b/src/main/scala/Github.scala @@ -7,7 +7,6 @@ import scalaj.http._ class Github(remoteUrl: String, val token: String) { - val (host, owner, repository) = Github.parseUrl(remoteUrl) val apiUrl = s"https://api.github.com/repos/$owner/$repository" val home = System.getProperty("user.home") @@ -16,7 +15,7 @@ class Github(remoteUrl: String, val token: String) { val response: HttpResponse[String] = Http(s"$apiUrl/pulls") .param("state", "all") .params(since match { - case None => Map[String,String]() + case None => Map[String, String]() case Some(date) => Map("since" -> date) }) .header("Accept", "application/vnd.github.v3+json") @@ -37,7 +36,7 @@ class Github(remoteUrl: String, val token: String) { .elements.map(e => commitRecordFormat.read(e)) } - def getLatestRelease(): Option[Release] = { + def getLatestRelease(): Option[ReleaseResponse] = { val response: HttpResponse[String] = Http(s"$apiUrl/releases/latest") .header("Accept", "application/vnd.github.v3+json") .header("Authorization", s"token $token") @@ -46,7 +45,7 @@ class Github(remoteUrl: String, val token: String) { if (response.code == 404) return None else if (response.code == 200) - return Some(response.body.parseJson.convertTo[Release]) + return Some(response.body.parseJson.convertTo[ReleaseResponse]) else throw new Exception(s"Error: $response.body") } @@ -69,6 +68,27 @@ class Github(remoteUrl: String, val token: String) { response.body.parseJson.convertTo[CommitRecord] } + def pushReleaseNotes(tag: Tag, body: String) = { + val response: HttpResponse[String] = Http(s"$apiUrl/releases") + .postData(releaseRequestFormat.write(ReleaseRequest(tag.tagName, "release name", body)).toString) + .header("Accept", "application/vnd.github.v3+json") + .header("Authorization", s"token $token") + .asString + + response.body.parseJson.convertTo[ReleaseResponse] + } + + def editReleaseNotes(release: ReleaseResponse, body: String) = { + val response: HttpResponse[String] = Http(s"$apiUrl/releases/${release.id}") + .postData(releaseRequestFormat.write(ReleaseRequest(release.tagName, "release name", body)).toString) + .method("PATCH") + .header("Accept", "application/vnd.github.v3+json") + .header("Authorization", s"token $token") + .asString + + response.body.parseJson.convertTo[ReleaseResponse] + } + } diff --git a/src/main/scala/NotesMaker.scala b/src/main/scala/NotesMaker.scala index 69a2f06..2d25fa1 100644 --- a/src/main/scala/NotesMaker.scala +++ b/src/main/scala/NotesMaker.scala @@ -7,26 +7,33 @@ import sbt._ object NotesMaker extends sbt.AutoPlugin { - lazy val printNotesAfterLatest = taskKey[Unit]("Create release notes af pull requests after the latest tag") - lazy val printNotesForLatest = taskKey[Unit]("Create release notes af pull requests for the latest tag") + lazy val printNotesForNextTag = taskKey[Unit]("Create and print release notes af pull requests after the latest tag") + lazy val printNotesForLatestTag = taskKey[Unit]("Create and print release notes af pull requests for the latest tag") + lazy val pushNotesForLatestTag = taskKey[Unit]("Create and push release notes af pull requests for the latest tag") override def trigger: PluginTrigger = AllRequirements override def projectSettings = Seq( - printNotesAfterLatest := { + printNotesForNextTag := { val baseDirectory = Keys.baseDirectory.value val credentials = Keys.credentials.value - processNotes(baseDirectory, credentials, FutureCommit) + processNotes(baseDirectory, credentials, FutureCommit, PrintNotes) }, - printNotesForLatest := { + printNotesForLatestTag := { val baseDirectory = Keys.baseDirectory.value val credentials = Keys.credentials.value - processNotes(baseDirectory, credentials, LatestComit) + processNotes(baseDirectory, credentials, LatestComit, PrintNotes) + }, + pushNotesForLatestTag := { + val baseDirectory = Keys.baseDirectory.value + val credentials = Keys.credentials.value + processNotes(baseDirectory, credentials, LatestComit, PushNotes) } ) - def processNotes(baseDirectory: java.io.File, credentials: Seq[Credentials], target: Target): Unit = { + def processNotes(baseDirectory: java.io.File, credentials: Seq[Credentials], + target: Target, action: Action): Unit = { val githubCredentials = credentials.map(Credentials.toDirect).find(c => c.realm.toLowerCase == "github") .orElse( Credentials.loadCredentials(new File(System.getProperty("user.home") + "/.github/credentials")) match { @@ -39,7 +46,8 @@ object NotesMaker extends sbt.AutoPlugin { return () } - new NotesMaker(baseDirectory, githubCredentials.get.passwd).makeNotes(target) + new NotesMaker(baseDirectory, githubCredentials.get.passwd) + .makeNotes(target, action) } } @@ -59,7 +67,7 @@ class NotesMaker(baseDir: java.io.File, token: String) { def this(lowerBound: Option[Tag], upperBound: Option[Tag]) = this(lowerBound, upperBound, None) } - def makeNotes(target: Target): Unit = { + def makeNotes(target: Target, action: Action): Unit = { val tags = github.tags val parameters = target match { @@ -84,7 +92,7 @@ class NotesMaker(baseDir: java.io.File, token: String) { return () } case Parameters(lower, upper, _) => { - printNotes(lower, upper) + printNotes(lower, upper, action) } } } @@ -93,7 +101,7 @@ class NotesMaker(baseDir: java.io.File, token: String) { def dateStrToTicks(dateStr: String) = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").parse(dateStr).getTime() - def printNotes(lowerBound: Option[Tag], upperBound: Option[Tag]): Unit = { + def printNotes(lowerBound: Option[Tag], upperBound: Option[Tag], action: Action): Unit = { (lowerBound, upperBound) match { case (Some(l), Some(u)) => println(s"Preparing release notes from pull requests between tags ${l.tagName} and ${u.tagName}\n") case (None, Some(u)) => println(s"Preparing release notes from pull requests before tag ${u.tagName}\n") @@ -116,22 +124,43 @@ class NotesMaker(baseDir: java.io.File, token: String) { println("No pull requests were found since last tag") } - pullRequests.foreach { pr => - println(s"[PR #${pr.number}](${pr.htmlUrl}) ${pr.title}") + val body = pullRequests.foldLeft("") { (txt, pr) => val commits = github.getPRCommits(pr.number) - commits.foreach { record => - println(s"* [${record.commit.message}](${record.htmlUrl})") - } - println + txt + s"[PR #${pr.number}](${pr.htmlUrl}) ${pr.title}\n" + + commits.foldLeft("") { (txt, record) => + txt + s"* ${record.commit.message} _[${record.sha.substring(0, 7)}](${record.htmlUrl})_\n" + } + "\n" } + + println(body) + + if (action == PushNotes) { + github.getLatestRelease().find(_.tagName == upperBound.get.tagName) match { + case Some(release) => { + println(s"Patching release ${release.id} for tag ${upperBound.get.tagName}") + github.editReleaseNotes(release, body) + } + case None => { + println(s"Ceealting release for tag ${upperBound.get.tagName}") + github.pushReleaseNotes(upperBound.get, body) + } + } + } + } } -class Target +sealed trait Target case object LatestComit extends Target case object FutureCommit extends Target +sealed trait Action + +case object PushNotes extends Action + +case object PrintNotes extends Action +