From 95e7ff4c1dbd456268863c00084c64076e7222cc Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Wed, 7 Aug 2019 12:53:21 -0400 Subject: [PATCH 1/7] Start working on creating changelog --- create-changelog.kts | 125 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 create-changelog.kts diff --git a/create-changelog.kts b/create-changelog.kts new file mode 100755 index 000000000..78c2a41bc --- /dev/null +++ b/create-changelog.kts @@ -0,0 +1,125 @@ +#!/usr/bin/env kscript + +@file:DependsOn("org.json:json:20180813") + +import org.json.JSONArray +import org.json.JSONObject +import java.io.BufferedReader +import java.net.HttpURLConnection +import java.net.URL + +data class Change(val author: String, val message: String, val date: String) { + constructor(commit: JSONObject) : this( + author = (commit["author"] as JSONObject).getString("login"), + message = (commit["commit"] as JSONObject).getString("message"), + date = (((commit["commit"] as JSONObject)["author"] as JSONObject)).getString("date")) +} + +val GITHUB_API_URL = "https://api.github.com"; + +fun getTags(repo: String) = JSONArray(fromURL(URL("$GITHUB_API_URL/repos/$repo/tags"))).filterIsInstance() + +fun getPreviousTag(tagName: String, tags: List) = tags[tags.indexOfFirst { it.getString("name") == tagName } + 1] + +fun getCommitTags(repo: String) = getTags(repo).filter { it.has("commit") } + +fun getParentsFromTag(repo: String, tag: JSONObject) = JSONObject(fromURL(URL("$GITHUB_API_URL/repos/$repo/commits/${(tag["commit"] as JSONObject)["sha"]}")))["parents"] as JSONArray + +fun getParentFromTag(repo: String, tag: JSONObject) = getParentsFromTag(repo, tag) + .also { require(it.length() == 1) { "Release tags must have exactly one parent but got $it" } }[0] as JSONObject + +fun getCommitsBetweenTags( + repo: String, + tagFrom: String, + tagTo: String, + useParentOfTagFrom: Boolean = true): List { + val tagsObj = getTags(repo) + val commitFrom = tagsObj.first { it["name"] == tagFrom } + val commitTo = tagsObj.first { it["name"] == tagTo } + val commitFromParent = getParentFromTag(repo, commitFrom) + val shaTo = (commitTo["commit"] as JSONObject).getString("sha") + val shaFrom = if (useParentOfTagFrom) commitFromParent.getString("sha") else (commitFrom["commit"] as JSONObject).getString("sha") + return compare(repo, shaFrom, shaTo) +} + +fun compare(repo: String, shaFrom: String, shaTo: String): List { + val compareUrl = URL("$GITHUB_API_URL/repos/$repo/compare/$shaFrom...$shaTo") + val compareObj = JSONObject(fromURL(compareUrl)) + val commits = compareObj["commits"] as JSONArray + return commits.map { Change(it as JSONObject) } +} + +fun getCommitsBetweenReleases( + organization: String, + name: String, + versionTo: String, + useParentOfTagFrom: Boolean = true): List { + return getCommitsBetweenReleases( + "$organization/$name", + versionTo, + useParentOfTagFrom = useParentOfTagFrom, + versionToTag = { "$name-$it" }) +} + +fun getCommitsBetweenReleases( + organization: String, + name: String, + versionFrom: String, + versionTo: String, + useParentOfTagFrom: Boolean = true): List { + return getCommitsBetweenReleases( + "$organization/$name", + versionFrom, + versionTo, + useParentOfTagFrom = useParentOfTagFrom, + versionToTag = { "$name-$it" }) +} + + +fun getCommitsBetweenReleases( + repo: String, + versionTo: String, + useParentOfTagFrom: Boolean = true, + versionToTag: (String) -> String): List { + return getCommitsBetweenTags( + repo, + getPreviousTag(versionToTag(versionTo), getCommitTags(repo)).getString("name"), + versionToTag(versionTo), + useParentOfTagFrom = useParentOfTagFrom) +} + +fun getCommitsBetweenReleases( + repo: String, + versionFrom: String, + versionTo: String, + useParentOfTagFrom: Boolean = true, + versionToTag: (String) -> String): List { + return getCommitsBetweenTags( + repo, + versionToTag(versionFrom), + versionToTag(versionTo), + useParentOfTagFrom = useParentOfTagFrom) +} + + +fun fromURL(url: URL): String { + println("Loading $url"); + val conn = url.openConnection() as HttpURLConnection; + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + + if (conn.getResponseCode() != 200) + throw RuntimeException("Failed : HTTP error code : ${conn.responseCode}") + + val br = conn.inputStream.bufferedReader(); + val text = br.use(BufferedReader::readText) + conn.disconnect() + return text +} + +val version = if (args.size > 0) args[0] else "0.18.0" +val commits = getCommitsBetweenReleases("saalfeldlab", "paintera", version, true) +println(commits) + + + From ec37c1ec9e82b8b70dfe8d403b23a771af034e39 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Wed, 7 Aug 2019 13:15:18 -0400 Subject: [PATCH 2/7] Filter for merge commits --- create-changelog.kts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/create-changelog.kts b/create-changelog.kts index 78c2a41bc..493aefaa1 100755 --- a/create-changelog.kts +++ b/create-changelog.kts @@ -119,7 +119,6 @@ fun fromURL(url: URL): String { val version = if (args.size > 0) args[0] else "0.18.0" val commits = getCommitsBetweenReleases("saalfeldlab", "paintera", version, true) -println(commits) - - +val mergeCommits = commits.filter { it.message.startsWith("Merge pull request #") } +mergeCommits.forEach { println(it) } From a08e8c8df556fe3a7dee3007e2889b074f9bd0c3 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Wed, 7 Aug 2019 13:22:51 -0400 Subject: [PATCH 3/7] Start working on adding features etc by tag --- create-changelog.kts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/create-changelog.kts b/create-changelog.kts index 493aefaa1..54a577607 100755 --- a/create-changelog.kts +++ b/create-changelog.kts @@ -122,3 +122,23 @@ val commits = getCommitsBetweenReleases("saalfeldlab", "paintera", version, true val mergeCommits = commits.filter { it.message.startsWith("Merge pull request #") } mergeCommits.forEach { println(it) } +val breaking = mutableListOf() +val features = mutableListOf() +val fixes = mutableListOf() + +for (change in mergeCommits) { + for (line in change.message.lines()) { + if (line.startsWith("[Breaking] ")) { + breaking += line + } else if (line.startsWith("[Feature] ")) { + features += line + } else if (line.startsWith("[Fixes] ")) { + fixes += line + } + } +} + +println(breaking) +println(features) +println(fixes) + From cce44083bf87da4b518ce17aad3c6fc3efe69036 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Thu, 8 Aug 2019 08:03:22 -0400 Subject: [PATCH 4/7] Update changelog creation script --- create-changelog.kts | 128 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 13 deletions(-) diff --git a/create-changelog.kts b/create-changelog.kts index 54a577607..e7310d83b 100755 --- a/create-changelog.kts +++ b/create-changelog.kts @@ -1,10 +1,18 @@ #!/usr/bin/env kscript @file:DependsOn("org.json:json:20180813") +@file:DependsOn("org.apache.maven:maven-model:3.6.1") +@file:DependsOn("org.eclipse.jgit:org.eclipse.jgit:5.4.0.201906121030-r") +@file:DependsOn("org.slf4j:slf4j-simple:1.7.25") +import org.apache.maven.model.Model +import org.apache.maven.model.io.xpp3.MavenXpp3Reader +import org.eclipse.jgit.storage.file.FileRepositoryBuilder import org.json.JSONArray import org.json.JSONObject import java.io.BufferedReader +import java.io.File +import java.io.FileReader import java.net.HttpURLConnection import java.net.URL @@ -15,10 +23,65 @@ data class Change(val author: String, val message: String, val date: String) { date = (((commit["commit"] as JSONObject)["author"] as JSONObject)).getString("date")) } +fun versionFromString(str: String): Version { + return if (str.contains("-")) { + val split = str.split("-") + val Mmp = split[0].split(".").map { it.toInt() }.toIntArray() + Version(Mmp[0], Mmp[1], Mmp[2], split[1]) + } else { + val Mmp = str.split(".").map { it.toInt() }.toIntArray() + Version(Mmp[0], Mmp[1], Mmp[2], null) + } +} + +data class Version( + val major: Int, + val minor: Int, + val patch: Int, + val preRelease: String? = null): Comparable { + + val versionString = preRelease?.let { "$major.$minor.$patch-$preRelease" } ?: "$major.$minor.$patch" + + public constructor(params: Array): this( + params[0].toInt(), + params[1].toInt(), + params[2].toInt(), + if (params.size == 4) params[3] else null) + + override fun toString() = versionString + + override fun compareTo(other: Version): Int { + val majorComp = major.compareTo(other.major) + if (majorComp != 0) + return majorComp + val minorComp = minor.compareTo(other.minor) + if (minorComp != 0) + return minorComp + val patchComp = patch.compareTo(other.patch) + if (patchComp != 0) + return patchComp + if (preRelease === null && other.preRelease != null) + return 1 + if (other.preRelease === null && preRelease != null) + return -1 + else if (other.preRelease === null && preRelease === null) + return 0 + return preRelease!!.compareTo(other.preRelease!!) + } +} + val GITHUB_API_URL = "https://api.github.com"; fun getTags(repo: String) = JSONArray(fromURL(URL("$GITHUB_API_URL/repos/$repo/tags"))).filterIsInstance() +fun getCommitDate( + repo: String, + commit: String): String { + val commitUrl = URL("$GITHUB_API_URL/repos/$repo/commits/$commit") + val commitDate = JSONObject(fromURL(commitUrl)).getJSONObject("commit").getJSONObject("author").getString("date") + return commitDate +} + fun getPreviousTag(tagName: String, tags: List) = tags[tags.indexOfFirst { it.getString("name") == tagName } + 1] fun getCommitTags(repo: String) = getTags(repo).filter { it.has("commit") } @@ -117,28 +180,67 @@ fun fromURL(url: URL): String { return text } -val version = if (args.size > 0) args[0] else "0.18.0" -val commits = getCommitsBetweenReleases("saalfeldlab", "paintera", version, true) -val mergeCommits = commits.filter { it.message.startsWith("Merge pull request #") } -mergeCommits.forEach { println(it) } +val reader = MavenXpp3Reader() +val model = FileReader("pom.xml").use { reader.read(it) } +val isSnapshot = model.version.contains("-SNAPSHOT") +val version = versionFromString(model.version) + +val tags = getCommitTags("saalfeldlab/paintera").filter { + try { + versionFromString(it.getString("name").replace("paintera-", "")) + true + } catch (e: Exception) { + false + } +} + +val repo = FileRepositoryBuilder().setGitDir(File(".git")).build() +val head = repo.resolve("HEAD") +val commitTo = if (isSnapshot) { + head.name +} else { + "paintera-${model.version}" + .takeUnless { t -> tags.count { it.getString("name") == t } == 0 } + ?: head.name +} + +val tagFrom = tags.first { versionFromString(it.getString("name").replace("paintera-", "")) < version } +val commitFrom = getParentFromTag("saalfeldlab/paintera", tagFrom) +val relevantCommits = compare( + repo = "saalfeldlab/paintera", + shaFrom = commitFrom.getString("sha"), + shaTo = commitTo) +// relevantCommits.forEach { println(it) } +val mergeCommits = relevantCommits.filter { it.message.startsWith("Merge pull request #") } val breaking = mutableListOf() val features = mutableListOf() val fixes = mutableListOf() -for (change in mergeCommits) { - for (line in change.message.lines()) { - if (line.startsWith("[Breaking] ")) { - breaking += line - } else if (line.startsWith("[Feature] ")) { - features += line - } else if (line.startsWith("[Fixes] ")) { - fixes += line - } +for (commit in mergeCommits) { + for (line in commit.message.lines()) { + var l = line + val isBreaking = if (l.contains("[BREAKING]")) { + l = l.replace("[BREAKING]", "").trim() + true + } else false + val isFeature = if (l.contains("[FEATURE]")) { + l = l.replace("[FEATURE]", "").trim() + true + } else false + val isBugfix = if (l.contains("[BUGFIX]")) { + l = l.replace("[BUGFIX]", "").trim() + true + } else false + if (isBreaking) breaking.add(l) + if (isFeature) features.add(l) + if (isBugfix) fixes.add(l) } } println(breaking) println(features) println(fixes) +// println("${model.version} is snapshot? $isSnapshot") +// println("$currentCommit ${getCommitDate("saalfeldlab/paintera", currentCommit)}") From 225f90194110510ff78eee642b81af91cc5bce7d Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Thu, 8 Aug 2019 09:23:46 -0400 Subject: [PATCH 5/7] Print changelog as markdown formatted string --- create-changelog.kts | 87 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/create-changelog.kts b/create-changelog.kts index e7310d83b..a05f71930 100755 --- a/create-changelog.kts +++ b/create-changelog.kts @@ -16,11 +16,16 @@ import java.io.FileReader import java.net.HttpURLConnection import java.net.URL -data class Change(val author: String, val message: String, val date: String) { - constructor(commit: JSONObject) : this( - author = (commit["author"] as JSONObject).getString("login"), +data class Change( + val sha: String, + val author: String, + val message: String, + val date: String) { + constructor(commit: JSONObject) : this( + sha = commit.getString("sha"), + author = (commit["author"] as JSONObject).getString("login"), message = (commit["commit"] as JSONObject).getString("message"), - date = (((commit["commit"] as JSONObject)["author"] as JSONObject)).getString("date")) + date = (((commit["commit"] as JSONObject)["author"] as JSONObject)).getString("date")) } fun versionFromString(str: String): Version { @@ -68,6 +73,14 @@ data class Version( return 0 return preRelease!!.compareTo(other.preRelease!!) } + + fun bump(major: Boolean = false, minor: Boolean = false, patch: Boolean = false): Version { + if (major) return Version(this.major + 1, 0, 0, null) + if (minor) return Version(this.major, this.minor + 1, 0, null) + if (patch) return Version(this.major, this.minor, this.patch + 1, null) + return Version(this.major, this.minor, this.patch, this.preRelease) + } + } val GITHUB_API_URL = "https://api.github.com"; @@ -166,7 +179,7 @@ fun getCommitsBetweenReleases( fun fromURL(url: URL): String { - println("Loading $url"); + // println("Loading $url"); val conn = url.openConnection() as HttpURLConnection; conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "application/json"); @@ -213,12 +226,29 @@ val relevantCommits = compare( // relevantCommits.forEach { println(it) } val mergeCommits = relevantCommits.filter { it.message.startsWith("Merge pull request #") } -val breaking = mutableListOf() -val features = mutableListOf() -val fixes = mutableListOf() +val breaking = mutableListOf() +val features = mutableListOf() +val fixes = mutableListOf() +val unversioned = mutableListOf() +val visitedCommits = mutableSetOf() +val pullRequests = mutableListOf>() + +val regex = "Merge pull request #([0-9]+)".toRegex() for (commit in mergeCommits) { - for (line in commit.message.lines()) { + if (visitedCommits.contains(commit.sha)) continue + visitedCommits.add(commit.sha) + // println("lel ${commit.sha}") + val lines = commit.message.lines() + val matchResult = regex.find(lines[0]) + val pullRequestNumber = if (matchResult === null) { + -1 + } else { + matchResult.groupValues[1].toInt() + } + pullRequests += Pair(pullRequestNumber, commit.message) + var isAnything = false + for (line in lines) { var l = line val isBreaking = if (l.contains("[BREAKING]")) { l = l.replace("[BREAKING]", "").trim() @@ -232,15 +262,38 @@ for (commit in mergeCommits) { l = l.replace("[BUGFIX]", "").trim() true } else false - if (isBreaking) breaking.add(l) - if (isFeature) features.add(l) - if (isBugfix) fixes.add(l) + val isUnversioned = if (l.contains("[UNVERSIONED]")) { + l = l.replace("[UNVERSIONED]", "").trim() + true + } else false + isAnything = isBreaking || isFeature || isBugfix || isUnversioned + if (pullRequestNumber > 0) l = "$l (#$pullRequestNumber)" + if (isBreaking) breaking.add(l) + if (isFeature) features.add(l) + if (isBugfix) fixes.add(l) + if (isUnversioned) unversioned.add(l) } + if (!isAnything) { unversioned.add(commit.message.replace("\n+".toRegex(), ": ")) } } -println(breaking) -println(features) -println(fixes) -// println("${model.version} is snapshot? $isSnapshot") -// println("$currentCommit ${getCommitDate("saalfeldlab/paintera", currentCommit)}") +val versionFrom = versionFromString(tagFrom.getString("name").replace("paintera-", "")) +val suggestedVersion = versionFrom.bump(major = !breaking.isEmpty(), minor = !features.isEmpty(), patch = !fixes.isEmpty()) + +var text = "# Paintera $suggestedVersion\nPrevious release: $versionFrom\n\n\n## Changelog" + +if (!breaking.isEmpty()) + text = "$text\n\n### Breaking Changes${breaking.map { "\n - $it" }.joinToString("")}" + +if (!features.isEmpty()) + text = "$text\n\n### New Features${features.map { "\n - $it" }.joinToString("")}" + +if (!fixes.isEmpty()) + text = "$text\n\n### Bug Fixes${fixes.map { "\n - $it" }.joinToString("")}" + +if (!unversioned.isEmpty()) + text = "$text\n\n### Other${unversioned.map { "\n - $it" }.joinToString("")}" +// println(pullRequests) +val pullRequestStrings = pullRequests.map { "### #${it.first}\n${it.second}"} +text = "$text\n\n\n## Pull Requests\n\n${pullRequestStrings.joinToString("\n\n")}" +println(text) From 4d1f90afdfcf4eb5774ab2b171230246f82a7802 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Thu, 8 Aug 2019 09:36:43 -0400 Subject: [PATCH 6/7] Move main code into separate function --- create-changelog.kts | 208 ++++++++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 102 deletions(-) diff --git a/create-changelog.kts b/create-changelog.kts index a05f71930..456b63fad 100755 --- a/create-changelog.kts +++ b/create-changelog.kts @@ -193,107 +193,111 @@ fun fromURL(url: URL): String { return text } -val reader = MavenXpp3Reader() -val model = FileReader("pom.xml").use { reader.read(it) } -val isSnapshot = model.version.contains("-SNAPSHOT") -val version = versionFromString(model.version) - -val tags = getCommitTags("saalfeldlab/paintera").filter { - try { - versionFromString(it.getString("name").replace("paintera-", "")) - true - } catch (e: Exception) { - false - } -} - -val repo = FileRepositoryBuilder().setGitDir(File(".git")).build() -val head = repo.resolve("HEAD") -val commitTo = if (isSnapshot) { - head.name -} else { - "paintera-${model.version}" - .takeUnless { t -> tags.count { it.getString("name") == t } == 0 } - ?: head.name -} - -val tagFrom = tags.first { versionFromString(it.getString("name").replace("paintera-", "")) < version } -val commitFrom = getParentFromTag("saalfeldlab/paintera", tagFrom) -val relevantCommits = compare( - repo = "saalfeldlab/paintera", - shaFrom = commitFrom.getString("sha"), - shaTo = commitTo) -// relevantCommits.forEach { println(it) } -val mergeCommits = relevantCommits.filter { it.message.startsWith("Merge pull request #") } - -val breaking = mutableListOf() -val features = mutableListOf() -val fixes = mutableListOf() -val unversioned = mutableListOf() -val visitedCommits = mutableSetOf() -val pullRequests = mutableListOf>() - -val regex = "Merge pull request #([0-9]+)".toRegex() - -for (commit in mergeCommits) { - if (visitedCommits.contains(commit.sha)) continue - visitedCommits.add(commit.sha) - // println("lel ${commit.sha}") - val lines = commit.message.lines() - val matchResult = regex.find(lines[0]) - val pullRequestNumber = if (matchResult === null) { - -1 - } else { - matchResult.groupValues[1].toInt() - } - pullRequests += Pair(pullRequestNumber, commit.message) - var isAnything = false - for (line in lines) { - var l = line - val isBreaking = if (l.contains("[BREAKING]")) { - l = l.replace("[BREAKING]", "").trim() - true - } else false - val isFeature = if (l.contains("[FEATURE]")) { - l = l.replace("[FEATURE]", "").trim() - true - } else false - val isBugfix = if (l.contains("[BUGFIX]")) { - l = l.replace("[BUGFIX]", "").trim() - true - } else false - val isUnversioned = if (l.contains("[UNVERSIONED]")) { - l = l.replace("[UNVERSIONED]", "").trim() - true - } else false - isAnything = isBreaking || isFeature || isBugfix || isUnversioned - if (pullRequestNumber > 0) l = "$l (#$pullRequestNumber)" - if (isBreaking) breaking.add(l) - if (isFeature) features.add(l) - if (isBugfix) fixes.add(l) - if (isUnversioned) unversioned.add(l) - } - if (!isAnything) { unversioned.add(commit.message.replace("\n+".toRegex(), ": ")) } +fun generateChangelog() { + + val reader = MavenXpp3Reader() + val model = FileReader("pom.xml").use { reader.read(it) } + val isSnapshot = model.version.contains("-SNAPSHOT") + val version = versionFromString(model.version) + + val tags = getCommitTags("saalfeldlab/paintera").filter { + try { + versionFromString(it.getString("name").replace("paintera-", "")) + true + } catch (e: Exception) { + false + } + } + + val repo = FileRepositoryBuilder().setGitDir(File(".git")).build() + val head = repo.resolve("HEAD") + val commitTo = if (isSnapshot) { + head.name + } else { + "paintera-${model.version}" + .takeUnless { t -> tags.count { it.getString("name") == t } == 0 } + ?: head.name + } + + val tagFrom = tags.first { versionFromString(it.getString("name").replace("paintera-", "")) < version } + val commitFrom = getParentFromTag("saalfeldlab/paintera", tagFrom) + val relevantCommits = compare( + repo = "saalfeldlab/paintera", + shaFrom = commitFrom.getString("sha"), + shaTo = commitTo) + // relevantCommits.forEach { println(it) } + val mergeCommits = relevantCommits.filter { it.message.startsWith("Merge pull request #") } + + val breaking = mutableListOf() + val features = mutableListOf() + val fixes = mutableListOf() + val unversioned = mutableListOf() + val visitedCommits = mutableSetOf() + val pullRequests = mutableListOf>() + + val regex = "Merge pull request #([0-9]+)".toRegex() + + for (commit in mergeCommits) { + if (visitedCommits.contains(commit.sha)) continue + visitedCommits.add(commit.sha) + // println("lel ${commit.sha}") + val lines = commit.message.lines() + val matchResult = regex.find(lines[0]) + val pullRequestNumber = if (matchResult === null) { + -1 + } else { + matchResult.groupValues[1].toInt() + } + pullRequests += Pair(pullRequestNumber, commit.message) + var isAnything = false + for (line in lines) { + var l = line + val isBreaking = if (l.contains("[BREAKING]")) { + l = l.replace("[BREAKING]", "").trim() + true + } else false + val isFeature = if (l.contains("[FEATURE]")) { + l = l.replace("[FEATURE]", "").trim() + true + } else false + val isBugfix = if (l.contains("[BUGFIX]")) { + l = l.replace("[BUGFIX]", "").trim() + true + } else false + val isUnversioned = if (l.contains("[UNVERSIONED]")) { + l = l.replace("[UNVERSIONED]", "").trim() + true + } else false + isAnything = isBreaking || isFeature || isBugfix || isUnversioned + if (pullRequestNumber > 0) l = "$l (#$pullRequestNumber)" + if (isBreaking) breaking.add(l) + if (isFeature) features.add(l) + if (isBugfix) fixes.add(l) + if (isUnversioned) unversioned.add(l) + } + if (!isAnything) { unversioned.add(commit.message.replace("\n+".toRegex(), ": ")) } + } + + val versionFrom = versionFromString(tagFrom.getString("name").replace("paintera-", "")) + val suggestedVersion = versionFrom.bump(major = !breaking.isEmpty(), minor = !features.isEmpty(), patch = !fixes.isEmpty()) + + var text = "# Paintera $suggestedVersion\nPrevious release: $versionFrom\n\n\n## Changelog" + + if (!breaking.isEmpty()) + text = "$text\n\n### Breaking Changes${breaking.map { "\n - $it" }.joinToString("")}" + + if (!features.isEmpty()) + text = "$text\n\n### New Features${features.map { "\n - $it" }.joinToString("")}" + + if (!fixes.isEmpty()) + text = "$text\n\n### Bug Fixes${fixes.map { "\n - $it" }.joinToString("")}" + + if (!unversioned.isEmpty()) + text = "$text\n\n### Other${unversioned.map { "\n - $it" }.joinToString("")}" + val pullRequestStrings = pullRequests.map { "### #${it.first}\n${it.second}"} + text = "$text\n\n\n## Pull Requests\n\n${pullRequestStrings.joinToString("\n\n")}" + + println(text) } -val versionFrom = versionFromString(tagFrom.getString("name").replace("paintera-", "")) -val suggestedVersion = versionFrom.bump(major = !breaking.isEmpty(), minor = !features.isEmpty(), patch = !fixes.isEmpty()) - -var text = "# Paintera $suggestedVersion\nPrevious release: $versionFrom\n\n\n## Changelog" - -if (!breaking.isEmpty()) - text = "$text\n\n### Breaking Changes${breaking.map { "\n - $it" }.joinToString("")}" - -if (!features.isEmpty()) - text = "$text\n\n### New Features${features.map { "\n - $it" }.joinToString("")}" - -if (!fixes.isEmpty()) - text = "$text\n\n### Bug Fixes${fixes.map { "\n - $it" }.joinToString("")}" - -if (!unversioned.isEmpty()) - text = "$text\n\n### Other${unversioned.map { "\n - $it" }.joinToString("")}" -// println(pullRequests) -val pullRequestStrings = pullRequests.map { "### #${it.first}\n${it.second}"} -text = "$text\n\n\n## Pull Requests\n\n${pullRequestStrings.joinToString("\n\n")}" - -println(text) +generateChangelog() From 80186695324d95eb2f5445f3013749a9974b7c31 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Thu, 8 Aug 2019 09:59:41 -0400 Subject: [PATCH 7/7] Clean-up changelog-generation script --- create-changelog.kts | 314 ++++++++++++++++--------------------------- 1 file changed, 115 insertions(+), 199 deletions(-) diff --git a/create-changelog.kts b/create-changelog.kts index 456b63fad..785a86735 100755 --- a/create-changelog.kts +++ b/create-changelog.kts @@ -5,7 +5,6 @@ @file:DependsOn("org.eclipse.jgit:org.eclipse.jgit:5.4.0.201906121030-r") @file:DependsOn("org.slf4j:slf4j-simple:1.7.25") -import org.apache.maven.model.Model import org.apache.maven.model.io.xpp3.MavenXpp3Reader import org.eclipse.jgit.storage.file.FileRepositoryBuilder import org.json.JSONArray @@ -17,69 +16,63 @@ import java.net.HttpURLConnection import java.net.URL data class Change( - val sha: String, - val author: String, - val message: String, - val date: String) { - constructor(commit: JSONObject) : this( - sha = commit.getString("sha"), - author = (commit["author"] as JSONObject).getString("login"), + val sha: String, + val author: String, + val message: String, + val date: String) { + constructor(commit: JSONObject) : this( + sha = commit.getString("sha"), + author = (commit["author"] as JSONObject).getString("login"), message = (commit["commit"] as JSONObject).getString("message"), - date = (((commit["commit"] as JSONObject)["author"] as JSONObject)).getString("date")) + date = (((commit["commit"] as JSONObject)["author"] as JSONObject)).getString("date")) } fun versionFromString(str: String): Version { - return if (str.contains("-")) { - val split = str.split("-") - val Mmp = split[0].split(".").map { it.toInt() }.toIntArray() - Version(Mmp[0], Mmp[1], Mmp[2], split[1]) - } else { - val Mmp = str.split(".").map { it.toInt() }.toIntArray() - Version(Mmp[0], Mmp[1], Mmp[2], null) - } + return if (str.contains("-")) { + val split = str.split("-") + val Mmp = split[0].split(".").map { it.toInt() }.toIntArray() + Version(Mmp[0], Mmp[1], Mmp[2], split[1]) + } else { + val Mmp = str.split(".").map { it.toInt() }.toIntArray() + Version(Mmp[0], Mmp[1], Mmp[2], null) + } } data class Version( - val major: Int, - val minor: Int, - val patch: Int, - val preRelease: String? = null): Comparable { - - val versionString = preRelease?.let { "$major.$minor.$patch-$preRelease" } ?: "$major.$minor.$patch" - - public constructor(params: Array): this( - params[0].toInt(), - params[1].toInt(), - params[2].toInt(), - if (params.size == 4) params[3] else null) - - override fun toString() = versionString - - override fun compareTo(other: Version): Int { - val majorComp = major.compareTo(other.major) - if (majorComp != 0) - return majorComp - val minorComp = minor.compareTo(other.minor) - if (minorComp != 0) - return minorComp - val patchComp = patch.compareTo(other.patch) - if (patchComp != 0) - return patchComp - if (preRelease === null && other.preRelease != null) - return 1 - if (other.preRelease === null && preRelease != null) - return -1 - else if (other.preRelease === null && preRelease === null) - return 0 - return preRelease!!.compareTo(other.preRelease!!) - } - - fun bump(major: Boolean = false, minor: Boolean = false, patch: Boolean = false): Version { - if (major) return Version(this.major + 1, 0, 0, null) - if (minor) return Version(this.major, this.minor + 1, 0, null) - if (patch) return Version(this.major, this.minor, this.patch + 1, null) - return Version(this.major, this.minor, this.patch, this.preRelease) - } + val major: Int, + val minor: Int, + val patch: Int, + val preRelease: String? = null) : Comparable { + + val versionString = preRelease?.let { "$major.$minor.$patch-$preRelease" } ?: "$major.$minor.$patch" + + override fun toString() = versionString + + override fun compareTo(other: Version): Int { + val majorComp = major.compareTo(other.major) + if (majorComp != 0) + return majorComp + val minorComp = minor.compareTo(other.minor) + if (minorComp != 0) + return minorComp + val patchComp = patch.compareTo(other.patch) + if (patchComp != 0) + return patchComp + if (preRelease === null && other.preRelease != null) + return 1 + if (other.preRelease === null && preRelease != null) + return -1 + else if (other.preRelease === null && preRelease === null) + return 0 + return preRelease!!.compareTo(other.preRelease!!) + } + + fun bump(major: Boolean = false, minor: Boolean = false, patch: Boolean = false): Version { + if (major) return Version(this.major + 1, 0, 0, null) + if (minor) return Version(this.major, this.minor + 1, 0, null) + if (patch) return Version(this.major, this.minor, this.patch + 1, null) + return Version(this.major, this.minor, this.patch, this.preRelease) + } } @@ -87,16 +80,6 @@ val GITHUB_API_URL = "https://api.github.com"; fun getTags(repo: String) = JSONArray(fromURL(URL("$GITHUB_API_URL/repos/$repo/tags"))).filterIsInstance() -fun getCommitDate( - repo: String, - commit: String): String { - val commitUrl = URL("$GITHUB_API_URL/repos/$repo/commits/$commit") - val commitDate = JSONObject(fromURL(commitUrl)).getJSONObject("commit").getJSONObject("author").getString("date") - return commitDate -} - -fun getPreviousTag(tagName: String, tags: List) = tags[tags.indexOfFirst { it.getString("name") == tagName } + 1] - fun getCommitTags(repo: String) = getTags(repo).filter { it.has("commit") } fun getParentsFromTag(repo: String, tag: JSONObject) = JSONObject(fromURL(URL("$GITHUB_API_URL/repos/$repo/commits/${(tag["commit"] as JSONObject)["sha"]}")))["parents"] as JSONArray @@ -104,20 +87,6 @@ fun getParentsFromTag(repo: String, tag: JSONObject) = JSONObject(fromURL(URL("$ fun getParentFromTag(repo: String, tag: JSONObject) = getParentsFromTag(repo, tag) .also { require(it.length() == 1) { "Release tags must have exactly one parent but got $it" } }[0] as JSONObject -fun getCommitsBetweenTags( - repo: String, - tagFrom: String, - tagTo: String, - useParentOfTagFrom: Boolean = true): List { - val tagsObj = getTags(repo) - val commitFrom = tagsObj.first { it["name"] == tagFrom } - val commitTo = tagsObj.first { it["name"] == tagTo } - val commitFromParent = getParentFromTag(repo, commitFrom) - val shaTo = (commitTo["commit"] as JSONObject).getString("sha") - val shaFrom = if (useParentOfTagFrom) commitFromParent.getString("sha") else (commitFrom["commit"] as JSONObject).getString("sha") - return compare(repo, shaFrom, shaTo) -} - fun compare(repo: String, shaFrom: String, shaTo: String): List { val compareUrl = URL("$GITHUB_API_URL/repos/$repo/compare/$shaFrom...$shaTo") val compareObj = JSONObject(fromURL(compareUrl)) @@ -125,61 +94,8 @@ fun compare(repo: String, shaFrom: String, shaTo: String): List { return commits.map { Change(it as JSONObject) } } -fun getCommitsBetweenReleases( - organization: String, - name: String, - versionTo: String, - useParentOfTagFrom: Boolean = true): List { - return getCommitsBetweenReleases( - "$organization/$name", - versionTo, - useParentOfTagFrom = useParentOfTagFrom, - versionToTag = { "$name-$it" }) -} - -fun getCommitsBetweenReleases( - organization: String, - name: String, - versionFrom: String, - versionTo: String, - useParentOfTagFrom: Boolean = true): List { - return getCommitsBetweenReleases( - "$organization/$name", - versionFrom, - versionTo, - useParentOfTagFrom = useParentOfTagFrom, - versionToTag = { "$name-$it" }) -} - - -fun getCommitsBetweenReleases( - repo: String, - versionTo: String, - useParentOfTagFrom: Boolean = true, - versionToTag: (String) -> String): List { - return getCommitsBetweenTags( - repo, - getPreviousTag(versionToTag(versionTo), getCommitTags(repo)).getString("name"), - versionToTag(versionTo), - useParentOfTagFrom = useParentOfTagFrom) -} - -fun getCommitsBetweenReleases( - repo: String, - versionFrom: String, - versionTo: String, - useParentOfTagFrom: Boolean = true, - versionToTag: (String) -> String): List { - return getCommitsBetweenTags( - repo, - versionToTag(versionFrom), - versionToTag(versionTo), - useParentOfTagFrom = useParentOfTagFrom) -} - fun fromURL(url: URL): String { - // println("Loading $url"); val conn = url.openConnection() as HttpURLConnection; conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "application/json"); @@ -196,86 +112,86 @@ fun fromURL(url: URL): String { fun generateChangelog() { val reader = MavenXpp3Reader() - val model = FileReader("pom.xml").use { reader.read(it) } + val model = FileReader("pom.xml").use { reader.read(it) } val isSnapshot = model.version.contains("-SNAPSHOT") val version = versionFromString(model.version) val tags = getCommitTags("saalfeldlab/paintera").filter { - try { - versionFromString(it.getString("name").replace("paintera-", "")) - true - } catch (e: Exception) { - false - } + try { + versionFromString(it.getString("name").replace("paintera-", "")) + true + } catch (e: Exception) { + false + } } val repo = FileRepositoryBuilder().setGitDir(File(".git")).build() val head = repo.resolve("HEAD") val commitTo = if (isSnapshot) { - head.name + head.name } else { - "paintera-${model.version}" - .takeUnless { t -> tags.count { it.getString("name") == t } == 0 } - ?: head.name + "paintera-${model.version}" + .takeUnless { t -> tags.count { it.getString("name") == t } == 0 } + ?: head.name } - val tagFrom = tags.first { versionFromString(it.getString("name").replace("paintera-", "")) < version } - val commitFrom = getParentFromTag("saalfeldlab/paintera", tagFrom) + val tagFrom = tags.first { versionFromString(it.getString("name").replace("paintera-", "")) < version } + val commitFrom = getParentFromTag("saalfeldlab/paintera", tagFrom) val relevantCommits = compare( - repo = "saalfeldlab/paintera", - shaFrom = commitFrom.getString("sha"), - shaTo = commitTo) - // relevantCommits.forEach { println(it) } + repo = "saalfeldlab/paintera", + shaFrom = commitFrom.getString("sha"), + shaTo = commitTo) val mergeCommits = relevantCommits.filter { it.message.startsWith("Merge pull request #") } - val breaking = mutableListOf() - val features = mutableListOf() - val fixes = mutableListOf() - val unversioned = mutableListOf() + val breaking = mutableListOf() + val features = mutableListOf() + val fixes = mutableListOf() + val unversioned = mutableListOf() val visitedCommits = mutableSetOf() - val pullRequests = mutableListOf>() + val pullRequests = mutableListOf>() val regex = "Merge pull request #([0-9]+)".toRegex() for (commit in mergeCommits) { - if (visitedCommits.contains(commit.sha)) continue - visitedCommits.add(commit.sha) - // println("lel ${commit.sha}") - val lines = commit.message.lines() - val matchResult = regex.find(lines[0]) - val pullRequestNumber = if (matchResult === null) { - -1 - } else { - matchResult.groupValues[1].toInt() - } - pullRequests += Pair(pullRequestNumber, commit.message) - var isAnything = false - for (line in lines) { - var l = line - val isBreaking = if (l.contains("[BREAKING]")) { - l = l.replace("[BREAKING]", "").trim() - true - } else false - val isFeature = if (l.contains("[FEATURE]")) { - l = l.replace("[FEATURE]", "").trim() - true - } else false - val isBugfix = if (l.contains("[BUGFIX]")) { - l = l.replace("[BUGFIX]", "").trim() - true - } else false - val isUnversioned = if (l.contains("[UNVERSIONED]")) { - l = l.replace("[UNVERSIONED]", "").trim() - true - } else false - isAnything = isBreaking || isFeature || isBugfix || isUnversioned - if (pullRequestNumber > 0) l = "$l (#$pullRequestNumber)" - if (isBreaking) breaking.add(l) - if (isFeature) features.add(l) - if (isBugfix) fixes.add(l) - if (isUnversioned) unversioned.add(l) - } - if (!isAnything) { unversioned.add(commit.message.replace("\n+".toRegex(), ": ")) } + if (visitedCommits.contains(commit.sha)) continue + visitedCommits.add(commit.sha) + val lines = commit.message.lines() + val matchResult = regex.find(lines[0]) + val pullRequestNumber = if (matchResult === null) { + -1 + } else { + matchResult.groupValues[1].toInt() + } + pullRequests += Pair(pullRequestNumber, commit.message) + var isAnything = false + for (line in lines) { + var l = line + val isBreaking = if (l.contains("[BREAKING]")) { + l = l.replace("[BREAKING]", "").trim() + true + } else false + val isFeature = if (l.contains("[FEATURE]")) { + l = l.replace("[FEATURE]", "").trim() + true + } else false + val isBugfix = if (l.contains("[BUGFIX]")) { + l = l.replace("[BUGFIX]", "").trim() + true + } else false + val isUnversioned = if (l.contains("[UNVERSIONED]")) { + l = l.replace("[UNVERSIONED]", "").trim() + true + } else false + isAnything = isBreaking || isFeature || isBugfix || isUnversioned + if (pullRequestNumber > 0) l = "$l (#$pullRequestNumber)" + if (isBreaking) breaking.add(l) + if (isFeature) features.add(l) + if (isBugfix) fixes.add(l) + if (isUnversioned) unversioned.add(l) + } + if (!isAnything) { + unversioned.add(commit.message.replace("\n+".toRegex(), ": ")) + } } val versionFrom = versionFromString(tagFrom.getString("name").replace("paintera-", "")) @@ -284,17 +200,17 @@ fun generateChangelog() { var text = "# Paintera $suggestedVersion\nPrevious release: $versionFrom\n\n\n## Changelog" if (!breaking.isEmpty()) - text = "$text\n\n### Breaking Changes${breaking.map { "\n - $it" }.joinToString("")}" + text = "$text\n\n### Breaking Changes${breaking.map { "\n - $it" }.joinToString("")}" if (!features.isEmpty()) - text = "$text\n\n### New Features${features.map { "\n - $it" }.joinToString("")}" + text = "$text\n\n### New Features${features.map { "\n - $it" }.joinToString("")}" if (!fixes.isEmpty()) - text = "$text\n\n### Bug Fixes${fixes.map { "\n - $it" }.joinToString("")}" + text = "$text\n\n### Bug Fixes${fixes.map { "\n - $it" }.joinToString("")}" if (!unversioned.isEmpty()) - text = "$text\n\n### Other${unversioned.map { "\n - $it" }.joinToString("")}" - val pullRequestStrings = pullRequests.map { "### #${it.first}\n${it.second}"} + text = "$text\n\n### Other${unversioned.map { "\n - $it" }.joinToString("")}" + val pullRequestStrings = pullRequests.map { "### #${it.first}\n${it.second}" } text = "$text\n\n\n## Pull Requests\n\n${pullRequestStrings.joinToString("\n\n")}" println(text)