Skip to content

Commit 1a10c8d

Browse files
author
Ganeshwara Hananda
authored
Collect release notes from commits (#322)
## What is the goal of this PR? We've updated the release notes script to collect from commits rather than milestones. It will collect the commits between the last release up to the specified commit being released. Additionally, it's been rewritten in Kotlin. ## What are the changes implemented in this PR? 1. Update the script for collecting release note from commits, which works as follows: - given C1, the commit that we want to release, collect L, the list of commits that we want to add to the release note: - if this is the first release ever, L would contain all the commits from C1 down to and including the first commit in the repo - if this is not the first release: - find C2, the commit of the release that immediately precedes this release - L would contain all the commits from C1 down to but excluding C2 - write PR description or commit message for each commit in L into the release note 2. Rewrite in Kotlin
1 parent 8d72394 commit 1a10c8d

File tree

10 files changed

+418
-128
lines changed

10 files changed

+418
-128
lines changed

RELEASE_TEMPLATE.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ release notes }
1+
{ release notes }

tool/release/BUILD

-14
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,6 @@ py_binary(
2929
main = "docs.py"
3030
)
3131

32-
py_binary(
33-
name = "create-notes",
34-
srcs = ["create-notes.py"],
35-
main = "create-notes.py",
36-
deps = [
37-
requirement("PyGithub"),
38-
requirement("urllib3"),
39-
requirement("chardet"),
40-
requirement("idna"),
41-
requirement("wrapt"),
42-
requirement("certifi"),
43-
]
44-
)
45-
4632
kt_jvm_binary(
4733
name = "bump",
4834
srcs = ["Bump.kt"],

tool/release/create-notes.py

-113
This file was deleted.

tool/release/createnotes/BUILD

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#
2+
# Copyright (C) 2021 Vaticle
3+
#
4+
# Licensed to the Apache Software Foundation (ASF) under one
5+
# or more contributor license agreements. See the NOTICE file
6+
# distributed with this work for additional information
7+
# regarding copyright ownership. The ASF licenses this file
8+
# to you under the Apache License, Version 2.0 (the
9+
# "License"); you may not use this file except in compliance
10+
# with the License. You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing,
15+
# software distributed under the License is distributed on an
16+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
# KIND, either express or implied. See the License for the
18+
# specific language governing permissions and limitations
19+
# under the License.
20+
#
21+
22+
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_binary")
23+
24+
kt_jvm_binary(
25+
name = "bin",
26+
srcs = glob(["*.kt"]),
27+
main_class = "com.vaticle.dependencies.tool.release.createnotes.MainKt",
28+
deps = [
29+
"@maven//:com_eclipsesource_minimal_json_minimal_json",
30+
"@maven//:com_google_http_client_google_http_client",
31+
"@maven//:org_zeroturnaround_zt_exec",
32+
],
33+
)

tool/release/createnotes/Commit.kt

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (C) 2021 Vaticle
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.vaticle.dependencies.tool.release.createnotes
23+
24+
import com.eclipsesource.json.Json
25+
import com.vaticle.dependencies.tool.release.createnotes.Constant.github
26+
import java.nio.file.Path
27+
28+
fun getCommits(org: String, repo: String, current: Version, to: String, baseDir: Path, githubToken: String): List<String> {
29+
val preceding = getPrecedingVersion(org, repo, current, githubToken)
30+
if (preceding != null) {
31+
val response = httpGet("$github/repos/$org/$repo/compare/$preceding...$to", githubToken)
32+
val body = Json.parse(String(response.content.readBytes()))
33+
return body.asObject().get("commits").asArray().map { e -> e.asObject().get("sha").asString() }
34+
}
35+
else {
36+
val gitRevList = bash("git rev-list --max-parents=0 HEAD", baseDir)
37+
val firstCommit = gitRevList.outputString().trim()
38+
val response = httpGet("$github/repos/$org/$repo/compare/$firstCommit...$to", githubToken)
39+
val body = Json.parse(String(response.content.readBytes()))
40+
return listOf(firstCommit) + body.asObject().get("commits").asArray().map { e -> e.asObject().get("sha").asString() }.toList()
41+
}
42+
}
43+
44+
private fun getPrecedingVersion(org: String, repo: String, current: Version, githubToken: String): Version? {
45+
val response = httpGet("$github/repos/$org/$repo/releases", githubToken)
46+
val body = Json.parse(String(response.content.readBytes()))
47+
val releases = mutableListOf<Version>()
48+
releases.add(current)
49+
releases.addAll(body.asArray().map { e -> Version.parse(e.asObject().get("tag_name").asString()) })
50+
releases.sort()
51+
val currentIdx = releases.indexOf(current)
52+
val preceding =
53+
if (currentIdx >= 1) releases[currentIdx - 1]
54+
else if (currentIdx == 0) null
55+
else throw IllegalStateException("")
56+
println("preceding version: $preceding")
57+
return preceding
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (C) 2021 Vaticle
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.vaticle.dependencies.tool.release.createnotes
23+
24+
import com.eclipsesource.json.Json
25+
import com.vaticle.dependencies.tool.release.createnotes.Constant.labelBug
26+
import com.vaticle.dependencies.tool.release.createnotes.Constant.labelFeature
27+
import com.vaticle.dependencies.tool.release.createnotes.Constant.github
28+
import com.vaticle.dependencies.tool.release.createnotes.Constant.labelPrefix
29+
import com.vaticle.dependencies.tool.release.createnotes.Constant.labelRefactor
30+
31+
data class CommitDescription(val title: String, val desc: String, val type: Type) {
32+
enum class Type { FEATURE, BUG, REFACTOR, OTHER }
33+
}
34+
35+
fun getCommitDescriptions(org: String, repo: String, commits: List<String>, githubToken: String): List<CommitDescription> {
36+
return commits.flatMap { commit ->
37+
38+
val response = httpGet("$github/repos/$org/$repo/commits/$commit/pulls", githubToken)
39+
val body = Json.parse(String(response.content.readBytes()))
40+
val prs = body.asArray()
41+
if (prs.size() > 0) {
42+
val notes = prs.map { pr ->
43+
val prNumber = pr.asObject().get("number").asInt()
44+
println("collecting commit '$commit' from PR #$prNumber...")
45+
val types = pr.asObject().get("labels").asArray().map { e -> e.asObject().get("name").asString() }
46+
.filter { e -> e.startsWith(labelPrefix) }
47+
val type =
48+
if (types.contains(labelFeature)) CommitDescription.Type.FEATURE
49+
else if (types.contains(labelBug)) CommitDescription.Type.BUG
50+
else if (types.contains(labelRefactor)) CommitDescription.Type.REFACTOR
51+
else CommitDescription.Type.OTHER
52+
CommitDescription(
53+
title = pr.asObject().get("title").asString(),
54+
desc = pr.asObject().get("body").asString(),
55+
type = type
56+
)
57+
}
58+
notes
59+
} else {
60+
println("collecting commit '$commit'...")
61+
val response = httpGet("$github/repos/$org/$repo/commits/$commit", githubToken)
62+
val body = Json.parse(String(response.content.readBytes()))
63+
// only take the first line of the commit message, since the second line onwards are most likely implementation detail
64+
val title = body.asObject().get("commit").asObject().get("message").asString().lines().first()
65+
val notes = listOf(
66+
CommitDescription(title = title, desc = "", type = CommitDescription.Type.OTHER)
67+
)
68+
notes
69+
}
70+
}
71+
}

tool/release/createnotes/Common.kt

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.vaticle.dependencies.tool.release.createnotes
2+
3+
import com.google.api.client.http.GenericUrl
4+
import com.google.api.client.http.HttpHeaders
5+
import com.google.api.client.http.HttpResponse
6+
import com.google.api.client.http.javanet.NetHttpTransport
7+
import org.zeroturnaround.exec.ProcessExecutor
8+
import org.zeroturnaround.exec.ProcessResult
9+
import java.nio.file.Path
10+
11+
object Constant {
12+
val releaseTemplateRegex = "\\{\\s*release notes\\s*}".toRegex()
13+
const val installInstruction = "http://docs.vaticle.com/docs/running-typedb/install-and-run"
14+
const val github = "https://api.github.com"
15+
const val headerAccept = "\"application/vnd.github.v3+json"
16+
const val headerAuthPrefix = "Token"
17+
const val labelPrefix = "type"
18+
const val labelFeature = "$labelPrefix: feature"
19+
const val labelBug = "$labelPrefix: bug"
20+
const val labelRefactor = "$labelPrefix: refactor"
21+
}
22+
23+
fun httpGet(url: String, githubToken: String): HttpResponse {
24+
return NetHttpTransport()
25+
.createRequestFactory()
26+
.buildGetRequest(GenericUrl(url))
27+
.setHeaders(
28+
HttpHeaders().setAuthorization("${Constant.headerAuthPrefix} $githubToken").setAccept(Constant.headerAccept)
29+
)
30+
.execute()
31+
}
32+
33+
fun bash(script: String, baseDir: Path): ProcessResult {
34+
val builder = ProcessExecutor(script.split(" "))
35+
.readOutput(true)
36+
.redirectOutput(System.out)
37+
.redirectError(System.err)
38+
.directory(baseDir.toFile())
39+
.exitValueNormal()
40+
return builder.execute()
41+
}

tool/release/createnotes/Main.kt

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (C) 2021 Vaticle
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.vaticle.dependencies.tool.release.createnotes
23+
24+
import java.nio.file.Paths
25+
import kotlin.io.path.notExists
26+
27+
fun main(args: Array<String>) {
28+
val bazelWorkspaceDir = Paths.get(getEnv("BUILD_WORKSPACE_DIRECTORY"))
29+
val githubToken = getEnv("CREATE_NOTES_TOKEN")
30+
if (args.size != 5) throw RuntimeException("org, repo, version, commit, and template must be supplied")
31+
val (org, repo, version, commit, templateFileLocation) = args
32+
val templateFile = bazelWorkspaceDir.resolve(templateFileLocation)
33+
if (templateFile.notExists()) throw RuntimeException("Template file '$templateFile' does not exist.")
34+
35+
println("Repository: $org/$repo")
36+
println("Commit: $commit")
37+
println("Version: $version")
38+
39+
val commits = getCommits(org, repo, Version.parse(version), commit, bazelWorkspaceDir, githubToken)
40+
println("There are ${commits.size} commits to be collected.")
41+
val commitDescriptions = getCommitDescriptions(org, repo, commits, githubToken)
42+
writeReleaseNoteMd(commitDescriptions, templateFile)
43+
}
44+
45+
private fun getEnv(env: String): String {
46+
return System.getenv(env) ?: throw RuntimeException("'$env' environment variable must be set.")
47+
}

0 commit comments

Comments
 (0)