Browse Source

Script for generating changelogs, new template for PR's

Igor Demin 7 months ago
parent
commit
cb6aea44b5
  1. 16
      .github/PULL_REQUEST_TEMPLATE.md
  2. 256
      tools/changelog.main.kts
  3. 1
      tools/commit-to-pr-mapping.txt

16
.github/PULL_REQUEST_TEMPLATE.md

@ -0,0 +1,16 @@
_[Optional]_ Fixes $linkToIssue
Properly describe the issue fixed.
Describe it from the user perspective if it affects functionality (adds a feature, fixes a bug, changes behavior, improves performance).
Add labels for the changelog:
- no labels - don't add to the changelog
- _changelog: normal_ - add
- _changelog: highlight_ - add and highlight
- _changelog: prerelease fix_ - add _(prerelease fix)_ to the title in a prerelease changelog, don’t include in a stable changelog
- _ios, desktop, web, android, common_ - affected platforms
## Testing
Describe how you tested your changes.

256
tools/changelog.main.kts

@ -0,0 +1,256 @@
/**
* Script for creating a changelog. Call
* ```
* kotlin changelog.main.kts v1.6.0-rc02 release/1.6.0
* ```
* where v1.6.0-rc02 - the first commit
* where release/1.6.0 - the last commit
*/
/**
* Run from command line:
* 1. Download https://github.com/JetBrains/kotlin/releases/tag/v1.9.22 and add `bin` to PATH
* 2. Call `kotlin <fileName>`
*
* Run from IntelliJ:
* 1. Right click on the script
* 2. More Run/Debug
* 3. Modify Run Configuration...
* 4. Clear all "Before launch" tasks (you can edit the system-wide template as well)
* 5. OK
*/
@file:Repository("https://repo1.maven.org/maven2/")
@file:DependsOn("com.google.code.gson:gson:2.10.1")
import com.google.gson.Gson
import java.io.File
import java.io.IOException
import java.net.URL
import java.util.concurrent.TimeUnit
val firstCommit = args.getOrNull(0) ?: error("Please call this way: kotlin changelog.main.kts <firstCommit> <lastCommit>")
val lastCommit = args.getOrNull(1) ?: error("Please call this way: kotlin changelog.main.kts <firstCommit> <lastCommit>")
// commits that don't have a link to a PR (a link should be something like " (#454)")
val commitToPRLinkMapping = File("commit-to-pr-mapping.txt").readLines().associate {
val splits = it.split(" ")
val commit = splits[0]
val prLink = splits[1]
commit to prLink
}
val entries = entriesForRepo("JetBrains/compose-multiplatform-core") +
entriesForRepo("JetBrains/compose-multiplatform")
fun List<ChangelogEntry>.ofType(type: Type) =
filter { it.type == type }.sortedByDescending { it.platforms.sumOf { it.sorting } }
val highlighted = entries.ofType(Type.Highlighted)
val normal = entries.ofType(Type.Normal)
val prereleaseFixes = entries.ofType(Type.PrereleaseFix)
val unknown = entries.ofType(Type.Unknown)
println(
buildString {
append("_Changes since ${commitToVersion(firstCommit)}_\n")
append("\n")
if (highlighted.isNotEmpty())
append(highlighted.joinToString("\n") { it.format() }).append("\n")
if (normal.isNotEmpty())
append(normal.joinToString("\n") { it.format() }).append("\n")
if (prereleaseFixes.isNotEmpty())
append(prereleaseFixes.joinToString("\n") { it.format() }).append("\n")
if (unknown.isNotEmpty()) {
append("\nUnknown changes for review:\n")
append(unknown.joinToString("\n") { it.format() }).append("\n")
}
}
)
/**
* Transforms v1.6.0-beta01 to 1.6.0-beta01
*/
fun commitToVersion(commit: String) =
if (commit.startsWith("v") && commit.contains(".")) {
commit.removePrefix("v")
} else {
commit
}
fun ChangelogEntry.format() = buildString {
append("- ")
if (type == Type.Highlighted) append("**")
if (type == Type.PrereleaseFix) append("_(prerelease fix)_ ")
append("[$title]($link)")
if (type == Type.Highlighted) append("**")
platforms.forEach {
append(" <sub>$it</sub>")
}
}
/**
* @param repo Example:
* JetBrains/compose-multiplatform-core
*/
fun entriesForRepo(repo: String): List<ChangelogEntry> {
val pullNumberToPull = (1..5)
.flatMap {
request<Array<GitHubPullEntry>>("https://api.github.com/repos/$repo/pulls?state=closed&per_page=100&page=$it").toList()
}
.associateBy { it.number }
fun prForCommit(commit: GitHubCompareResponse.CommitEntry): GitHubPullEntry? {
val repoNumber = repoNumberForCommit(commit)
return pullNumberToPull[repoNumber]
}
fun changelogEntryFor(
commit: GitHubCompareResponse.CommitEntry,
pullRequest: GitHubPullEntry?
): ChangelogEntry {
return if (pullRequest != null) {
val prTitle = pullRequest.title
val prNumber = pullRequest.number
ChangelogEntry(
prTitle,
"https://github.com/$repo/pull/$prNumber",
typeOf(pullRequest),
platformsOf(pullRequest)
)
} else {
val commitSha = commit.sha
val commitTitle = commit.commit.message.substringBefore("\n")
ChangelogEntry(commitTitle, "https://github.com/$repo/commit/$commitSha", Type.Unknown, emptyList())
}
}
return request<GitHubCompareResponse>("https://api.github.com/repos/$repo/compare/$firstCommit...$lastCommit")
.commits
.map { changelogEntryFor(it, prForCommit(it)) }
}
/**
* Extract the PR number from the commit.
*/
fun repoNumberForCommit(commit: GitHubCompareResponse.CommitEntry): Int? {
val commitTitle = commit.commit.message.substringBefore("\n")
// check title similar to `Fix import android flavors with compose resources (#4319)`
return if (commitTitle.contains(" (#")) {
commitTitle.substringAfter(" (#").substringBefore(")").toIntOrNull()
} else {
commitToPRLinkMapping[commit.sha]?.substringAfterLast("/")?.toIntOrNull()
}
}
fun typeOf(pullRequest: GitHubPullEntry): Type {
val labels = pullRequest.labels.mapTo(mutableSetOf()) { it.name.lowercase() }
return when {
labels.contains("changelog: highlight") -> Type.Highlighted
labels.contains("changelog: normal") -> Type.Normal
labels.contains("changelog: prerelease fix") -> Type.PrereleaseFix
else -> Type.Unknown
}
}
fun platformsOf(pullRequest: GitHubPullEntry) = pullRequest.labels.mapNotNull {
when (it.name.lowercase()) {
"ios" -> Platform.IOS
"android" -> Platform.Android
"desktop" -> Platform.Desktop
"web" -> Platform.Web
"common" -> Platform.Common
else -> null
}
}
data class ChangelogEntry(
val title: String,
val link: String,
val type: Type,
val platforms: List<Platform>,
)
enum class Type { Highlighted, Normal, PrereleaseFix, Unknown }
enum class Platform(val title: String, val sorting: Int) {
Common("Common", 0x11111),
IOS("iOS", 0x01000),
Android("Android", 0x00100),
Desktop("Desktop", 0x00010),
Web("Web", 0x00001);
override fun toString() = title
}
// example https://api.github.com/repos/JetBrains/compose-multiplatform-core/compare/v1.6.0-rc02...release/1.6.0
data class GitHubCompareResponse(val commits: List<CommitEntry>) {
data class CommitEntry(val sha: String, val commit: Commit)
data class Commit(val message: String)
}
// example https://api.github.com/repos/JetBrains/compose-multiplatform-core/pulls?state=closed
data class GitHubPullEntry(val number: Int, val title: String, val body: String, val labels: List<Label>) {
class Label(val name: String)
}
//region ========================================== UTILS =========================================
// from https://stackoverflow.com/a/41495542
fun String.runCommand(workingDir: File = File(".")) {
ProcessBuilder(*split(" ").toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
.waitFor(5, TimeUnit.MINUTES)
}
fun String.execCommand(workingDir: File = File(".")): String? {
try {
val parts = this.split("\\s".toRegex())
val proc = ProcessBuilder(*parts.toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
proc.waitFor(60, TimeUnit.MINUTES)
return proc.inputStream.bufferedReader().readText()
} catch (e: IOException) {
e.printStackTrace()
return null
}
}
inline fun <reified T> request(
url: String
): T = exponentialRetry {
URL(url).openStream().use {
Gson().fromJson(
it.bufferedReader(),
T::class.java
)
}
}
fun <T> exponentialRetry(block: () -> T): T {
val exception = IOException()
val retriesSeconds = listOf(60, 300, 1200)
for (retriesSecond in retriesSeconds) {
try {
return block()
} catch (e: IOException) {
e.printStackTrace()
exception.addSuppressed(e)
Thread.sleep(retriesSecond.toLong())
}
}
throw exception
}
//endregion

1
tools/commit-to-pr-mapping.txt

@ -0,0 +1 @@
4e948e4bc3a9bd0ee364a6f8ddf5d982394e53c8 https://github.com/JetBrains/compose-multiplatform/pull/4333
Loading…
Cancel
Save