From 992638b3b188fa922ccb443438d2167476c5ce40 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 22 Feb 2022 17:18:41 +0000 Subject: [PATCH 01/22] Create mergify module --- build.sbt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.sbt b/build.sbt index 8962effb..947eac35 100644 --- a/build.sbt +++ b/build.sbt @@ -66,6 +66,14 @@ lazy val githubActions = project name := "sbt-typelevel-github-actions" ) +lazy val mergify = project + .in(file("mergify")) + .enablePlugins(SbtPlugin) + .settings( + name := "sbt-typelevel-mergify" + ) + .dependsOn(githubActions) + lazy val versioning = project .in(file("versioning")) .enablePlugins(SbtPlugin) From 0e2d21915867c3262fa6c168e9706c293d090101 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 22 Feb 2022 20:30:09 +0000 Subject: [PATCH 02/22] Start fleshing out mergify plugin --- build.sbt | 1 + .../typelevel/sbt/mergify/MergifyAction.scala | 40 +++++++++++++++++++ .../typelevel/sbt/mergify/MergifyPlugin.scala | 33 +++++++++++++++ .../typelevel/sbt/mergify/MergifyPrRule.scala | 23 +++++++++++ 4 files changed, 97 insertions(+) create mode 100644 mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala create mode 100644 mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala create mode 100644 mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala diff --git a/build.sbt b/build.sbt index 947eac35..aa545ef2 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,7 @@ lazy val root = tlCrossRootProject.aggregate( settings, github, githubActions, + mergify, versioning, mima, sonatype, diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala new file mode 100644 index 00000000..fd113411 --- /dev/null +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.sbt.mergify + +sealed abstract class MergifyAction + +object MergifyAction { + + final case class Merge( + method: Option[String] = None, + rebaseFallback: Option[String] = None, + commitMessageTemplate: Option[String] = None + ) extends MergifyAction + + final case class Label( + add: List[String] = Nil, + remove: List[String] = Nil, + removeAll: Option[Boolean] = None + ) extends MergifyAction + + // this should prevent exhaustivity checking, + // so pattern matches always have to include a default case + // this lets us add more cases without breaking backwards compat + private[this] object Dummy extends MergifyAction + +} diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala new file mode 100644 index 00000000..9998f26f --- /dev/null +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -0,0 +1,33 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.sbt.mergify + +import sbt._, Keys._ +import org.typelevel.sbt.gha._ + +object MergifyPlugin extends AutoPlugin { + + object autoImport { + lazy val mergifyPrRules = settingKey[Seq[MergifyPrRule]]("The mergify pull request rules") + lazy val mergifyEnableSteward = settingKey[Boolean]( + "Whether to generate an automerge rule for Scala Steward PRs (default: true)") + } + + override def requires = GenerativePlugin + override def trigger: PluginTrigger = allRequirements + +} diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala new file mode 100644 index 00000000..3077400d --- /dev/null +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.sbt.mergify + +final case class MergifyPrRule( + name: String, + conditions: List[String], + actions: Map[String, MergifyAction] +) From 17bd11eebccc7ce580a69881de4ac594be3f05e0 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 22 Feb 2022 21:56:13 +0000 Subject: [PATCH 03/22] Checkpointing wip --- .../sbt/mergify/MergifyCondition.scala | 24 +++++++++++++++++++ .../typelevel/sbt/mergify/MergifyPlugin.scala | 5 ++++ .../typelevel/sbt/mergify/MergifyPrRule.scala | 6 ++--- .../sbt/mergify/MergifyStewardConfig.scala | 24 +++++++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala create mode 100644 mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala new file mode 100644 index 00000000..25320ef6 --- /dev/null +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.sbt.mergify + +sealed abstract class MergifyCondition + +object MergifyCondition { + final case class And(conditions: List[MergifyCondition]) + final case class Or(conditions: List[MergifyCondition]) +} diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 9998f26f..8918ca43 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -25,6 +25,11 @@ object MergifyPlugin extends AutoPlugin { lazy val mergifyPrRules = settingKey[Seq[MergifyPrRule]]("The mergify pull request rules") lazy val mergifyEnableSteward = settingKey[Boolean]( "Whether to generate an automerge rule for Scala Steward PRs (default: true)") + + lazy val mergifyRequiredJobs = + settingKey[Seq[String]]("Ids for jobs that must succeed for merging (default: [build])") + lazy val mergifySuccessConditions = settingKey[List[String]]( + "Success conditions for merging (default: auto-generated from `mergifyRequiredJobs` setting)") } override def requires = GenerativePlugin diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala index 3077400d..b052f112 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala @@ -17,7 +17,7 @@ package org.typelevel.sbt.mergify final case class MergifyPrRule( - name: String, - conditions: List[String], - actions: Map[String, MergifyAction] + name: String, + conditions: List[String], + actions: Map[String, MergifyAction] ) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala new file mode 100644 index 00000000..deded328 --- /dev/null +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.sbt.mergify + +final case class MergifyStewardConfig( + author: String = "scala-steward", + minor: Boolean = false, + patch: Boolean = true, + labels: List[String] +) From 5b86e388b09753349138ce453933532459fe14c7 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 23 Feb 2022 21:29:53 +0000 Subject: [PATCH 04/22] Checkpoint matrix expansion changes --- .github/workflows/ci.yml | 4 +- .../typelevel/sbt/gha/GenerativePlugin.scala | 64 +++++++++++++------ .../typelevel/sbt/mergify/MergifyAction.scala | 19 ++++-- .../sbt/mergify/MergifyCondition.scala | 6 +- .../typelevel/sbt/mergify/MergifyPlugin.scala | 38 ++++++++++- .../typelevel/sbt/mergify/MergifyPrRule.scala | 4 +- .../sbt/mergify/MergifyStewardConfig.scala | 25 +++++++- 7 files changed, 123 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2501db8d..7c500d0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,11 +77,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/series/0.4') - run: mkdir -p github/target github-actions/target kernel/target versioning/target ci-release/target target .js/target mdocs/target site/target ci-signing/target mima/target .jvm/target .native/target no-publish/target sonatype/target ci/target sonatype-ci-release/target core/target settings/target project/target + run: mkdir -p github/target github-actions/target kernel/target versioning/target ci-release/target target .js/target mdocs/target site/target ci-signing/target mergify/target mima/target .jvm/target .native/target no-publish/target sonatype/target ci/target sonatype-ci-release/target core/target settings/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/series/0.4') - run: tar cf targets.tar github/target github-actions/target kernel/target versioning/target ci-release/target target .js/target mdocs/target site/target ci-signing/target mima/target .jvm/target .native/target no-publish/target sonatype/target ci/target sonatype-ci-release/target core/target settings/target project/target + run: tar cf targets.tar github/target github-actions/target kernel/target versioning/target ci-release/target target .js/target mdocs/target site/target ci-signing/target mergify/target mima/target .jvm/target .native/target no-publish/target sonatype/target ci/target sonatype-ci-release/target core/target settings/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/series/0.4') diff --git a/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala b/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala index 988c59fd..9346535b 100644 --- a/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala +++ b/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala @@ -606,7 +606,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} githubWorkflowGeneratedDownloadSteps := { val extraKeys = githubWorkflowArtifactDownloadExtraKeys.value val additions = githubWorkflowBuildMatrixAdditions.value - val matrix = additions.map { + val matrixAdds = additions.map { case (key, values) => if (extraKeys(key)) key -> values // we want to iterate over all values @@ -615,25 +615,23 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} } val keys = "scala" :: additions.keys.toList.sorted + val oses = githubWorkflowOSes.value.toList val scalas = githubWorkflowScalaVersions.value.toList - val exclusions = githubWorkflowBuildMatrixExclusions.value + val javas = githubWorkflowJavaVersions.value.toList + val exclusions = githubWorkflowBuildMatrixExclusions.value.toList // we build the list of artifacts, by iterating over all combinations of keys - val artifacts = matrix - .toList - .sortBy(_._1) - .map(_._2) - .foldLeft(scalas.map(List(_))) { (artifacts, values) => - for { - artifact <- artifacts - value <- values - } yield artifact :+ value - } // then, we filter artifacts for keys that are excluded from the matrix - .filterNot { artifact => - val job = keys.zip(artifact).toMap - exclusions.exists { // there is an exclude that matches the current job - case MatrixExclude(matching) => matching.toSet.subsetOf(job.toSet) - } + val artifacts = + expandMatrix( + oses, + scalas, + javas, + matrixAdds, + Nil, + exclusions + ).map { + case _ :: scala :: _ :: tail => scala :: tail + case _ => sys.error("Bug generating artifact download steps") // shouldn't happen } if (githubWorkflowArtifactUpload.value) { @@ -862,7 +860,37 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} } ) - private[gha] def diff(expected: String, actual: String): String = { + private[sbt] def expandMatrix( + oses: List[String], + scalas: List[String], + javas: List[JavaSpec], + matrixAdds: Map[String, List[String]], + includes: List[MatrixInclude], + excludes: List[MatrixExclude] + ): List[List[String]] = { + val keys = "os" :: "scala" :: "java" :: matrixAdds.keys.toList.sorted + val matrix = + matrixAdds + ("os" -> oses) + ("scala" -> scalas) + ("java" -> javas.map(_.render)) + + // expand the matrix + keys + .foldLeft(List(List.empty[String])) { (cells, key) => + val values = matrix.getOrElse(key, Nil) + cells.flatMap { cell => values.map(v => cell ::: v :: Nil) } + } + .filterNot { cell => // remove the excludes + val job = keys.zip(cell).toMap + excludes.exists { // there is an exclude that matches the current job + case MatrixExclude(matching) => matching.toSet.subsetOf(job.toSet) + } + } ::: includes.map { // add the includes + case MatrixInclude(matching, additions) => + // yoloing here, but let's wait for the bug report + keys.map(matching) ::: additions.values.toList + } + } + + private[sbt] def diff(expected: String, actual: String): String = { val expectedLines = expected.split("\n", -1) val actualLines = actual.split("\n", -1) val (lines, _) = diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala index fd113411..7940d823 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala @@ -16,7 +16,9 @@ package org.typelevel.sbt.mergify -sealed abstract class MergifyAction +sealed abstract class MergifyAction { + def name: String +} object MergifyAction { @@ -24,17 +26,20 @@ object MergifyAction { method: Option[String] = None, rebaseFallback: Option[String] = None, commitMessageTemplate: Option[String] = None - ) extends MergifyAction + ) extends MergifyAction { + def name = "merge" + } final case class Label( add: List[String] = Nil, remove: List[String] = Nil, removeAll: Option[Boolean] = None - ) extends MergifyAction + ) extends MergifyAction { + def name = "label" + } - // this should prevent exhaustivity checking, - // so pattern matches always have to include a default case - // this lets us add more cases without breaking backwards compat - private[this] object Dummy extends MergifyAction + private[this] object Dummy extends MergifyAction { // break exhaustivity checking + def name = "dummy" + } } diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala index 25320ef6..f85b43b0 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala @@ -19,6 +19,8 @@ package org.typelevel.sbt.mergify sealed abstract class MergifyCondition object MergifyCondition { - final case class And(conditions: List[MergifyCondition]) - final case class Or(conditions: List[MergifyCondition]) + final case class Custom(condition: String) extends MergifyCondition + final case class And(conditions: List[MergifyCondition]) extends MergifyCondition + final case class Or(conditions: List[MergifyCondition]) extends MergifyCondition + private[this] final object Dummy extends MergifyCondition // break exhaustivity checking } diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 8918ca43..645ac832 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -23,16 +23,48 @@ object MergifyPlugin extends AutoPlugin { object autoImport { lazy val mergifyPrRules = settingKey[Seq[MergifyPrRule]]("The mergify pull request rules") - lazy val mergifyEnableSteward = settingKey[Boolean]( - "Whether to generate an automerge rule for Scala Steward PRs (default: true)") + + lazy val mergifyStewardConfig = settingKey[Option[MergifyStewardConfig]]( + "Config for the automerge rule for Scala Steward PRs, set to None to disable.") lazy val mergifyRequiredJobs = settingKey[Seq[String]]("Ids for jobs that must succeed for merging (default: [build])") - lazy val mergifySuccessConditions = settingKey[List[String]]( + + lazy val mergifySuccessConditions = settingKey[Seq[MergifyCondition]]( "Success conditions for merging (default: auto-generated from `mergifyRequiredJobs` setting)") } override def requires = GenerativePlugin override def trigger: PluginTrigger = allRequirements + import autoImport._ + import GenerativePlugin.autoImport._ + + override def buildSettings: Seq[Setting[_]] = Seq( + mergifyRequiredJobs := Seq("build"), + mergifySuccessConditions := jobSuccessConditions.value, + mergifyPrRules := { + mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList + } + ) + + private lazy val jobSuccessConditions = Def.setting { + githubWorkflowGeneratedCI.value.flatMap { + case job if mergifyRequiredJobs.value.contains(job.id) => + GenerativePlugin + .expandMatrix( + job.oses, + job.scalas, + job.javas, + job.matrixAdds, + job.matrixIncs, + job.matrixExcs + ) + .map { cell => + MergifyCondition.Custom(s"status-success=${job.name} (${cell.mkString(", ")})") + } + case _ => Nil + } + } + } diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala index b052f112..4db01502 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala @@ -18,6 +18,6 @@ package org.typelevel.sbt.mergify final case class MergifyPrRule( name: String, - conditions: List[String], - actions: Map[String, MergifyAction] + conditions: List[MergifyCondition], + actions: List[MergifyAction] ) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala index deded328..5079fc99 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala @@ -17,8 +17,27 @@ package org.typelevel.sbt.mergify final case class MergifyStewardConfig( + name: String = "merge scala-steward's PRs", author: String = "scala-steward", minor: Boolean = false, - patch: Boolean = true, - labels: List[String] -) + merge: MergifyAction.Merge = MergifyAction.Merge() +) { + + private[mergify] def toPrRule(buildConditions: List[MergifyCondition]): MergifyPrRule = { + val authorCond = MergifyCondition.Custom(s"author=$author") + + val bodyCond = { + val patchCond = MergifyCondition.Custom("body~=labels:.*early-semver-spec-patch") + val minorCond = MergifyCondition.Custom("body~=labels:.*early-semver-minor") + if (minor) MergifyCondition.Or(List(patchCond, minorCond)) + else patchCond + } + + MergifyPrRule( + name, + authorCond :: bodyCond :: buildConditions, + List(merge) + ) + } + +} From 4a183776d3606e766ce6b9e3b552309d385bb716 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 25 Feb 2022 22:00:16 +0000 Subject: [PATCH 05/22] Extract out YamlCompiler --- .../typelevel/sbt/gha/GenerativePlugin.scala | 49 +---------------- .../org/typelevel/sbt/gha/YamlCompiler.scala | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 48 deletions(-) create mode 100644 github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala diff --git a/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala b/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala index 9346535b..cb1f0bcd 100644 --- a/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala +++ b/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala @@ -63,54 +63,7 @@ object GenerativePlugin extends AutoPlugin { } import autoImport._ - - private def indent(output: String, level: Int): String = { - val space = (0 until level * 2).map(_ => ' ').mkString - (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") - } - - private def isSafeString(str: String): Boolean = - !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity - str.indexOf('#') >= 0 || // same for comment - str.indexOf('!') == 0 || - str.indexOf('*') == 0 || - str.indexOf('-') == 0 || - str.indexOf('?') == 0 || - str.indexOf('{') == 0 || - str.indexOf('}') == 0 || - str.indexOf('[') == 0 || - str.indexOf(']') == 0 || - str.indexOf(',') == 0 || - str.indexOf('|') == 0 || - str.indexOf('>') == 0 || - str.indexOf('@') == 0 || - str.indexOf('`') == 0 || - str.indexOf('"') == 0 || - str.indexOf('\'') == 0 || - str.indexOf('&') == 0) - - private def wrap(str: String): String = - if (str.indexOf('\n') >= 0) - "|\n" + indent(str, 1) - else if (isSafeString(str)) - str - else - s"'${str.replace("'", "''")}'" - - def compileList(items: List[String], level: Int): String = { - val rendered = items.map(wrap) - if (rendered.map(_.length).sum < 40) // just arbitrarily... - rendered.mkString(" [", ", ", "]") - else - "\n" + indent(rendered.map("- " + _).mkString("\n"), level) - } - - def compileListOfSimpleDicts(items: List[Map[String, String]]): String = - items map { dict => - val rendered = dict map { case (key, value) => s"$key: $value" } mkString "\n" - - "-" + indent(rendered, 1).substring(1) - } mkString "\n" + import YamlCompiler._ def compilePREventType(tpe: PREventType): String = { import PREventType._ diff --git a/github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala b/github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala new file mode 100644 index 00000000..371b0e5e --- /dev/null +++ b/github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala @@ -0,0 +1,53 @@ +package org.typelevel.sbt.gha + +private[sbt] object YamlCompiler { + + def indent(output: String, level: Int): String = { + val space = (0 until level * 2).map(_ => ' ').mkString + (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") + } + + def isSafeString(str: String): Boolean = + !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity + str.indexOf('#') >= 0 || // same for comment + str.indexOf('!') == 0 || + str.indexOf('*') == 0 || + str.indexOf('-') == 0 || + str.indexOf('?') == 0 || + str.indexOf('{') == 0 || + str.indexOf('}') == 0 || + str.indexOf('[') == 0 || + str.indexOf(']') == 0 || + str.indexOf(',') == 0 || + str.indexOf('|') == 0 || + str.indexOf('>') == 0 || + str.indexOf('@') == 0 || + str.indexOf('`') == 0 || + str.indexOf('"') == 0 || + str.indexOf('\'') == 0 || + str.indexOf('&') == 0) + + def wrap(str: String): String = + if (str.indexOf('\n') >= 0) + "|\n" + indent(str, 1) + else if (isSafeString(str)) + str + else + s"'${str.replace("'", "''")}'" + + def compileList(items: List[String], level: Int, maxLength: Int = 40): String = { + val rendered = items.map(wrap) + if (rendered.map(_.length).sum < maxLength) // just arbitrarily... + rendered.mkString(" [", ", ", "]") + else + "\n" + indent(rendered.map("- " + _).mkString("\n"), level) + } + + def compileListOfSimpleDicts(items: List[Map[String, String]]): String = + items map { dict => + val rendered = dict map { case (key, value) => s"$key: $value" } mkString "\n" + + "-" + indent(rendered, 1).substring(1) + } mkString "\n" + +} From 12d22165cdbe5461b4190a6f004f993185c680da Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 25 Feb 2022 23:18:37 +0000 Subject: [PATCH 06/22] Minimum token of usefulness --- .../typelevel/sbt/mergify/MergifyPlugin.scala | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 645ac832..f04782ad 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -22,6 +22,11 @@ import org.typelevel.sbt.gha._ object MergifyPlugin extends AutoPlugin { object autoImport { + lazy val mergifyGenerate = taskKey[Unit]( + "Generates (and overwrites if extant) a .mergify.yml according to configuration") + lazy val mergifyCheck = taskKey[Unit]( + "Checks to see if the .mergify.yml files are equivalent to what would be generated and errors if otherwise") + lazy val mergifyPrRules = settingKey[Seq[MergifyPrRule]]("The mergify pull request rules") lazy val mergifyStewardConfig = settingKey[Option[MergifyStewardConfig]]( @@ -45,6 +50,11 @@ object MergifyPlugin extends AutoPlugin { mergifySuccessConditions := jobSuccessConditions.value, mergifyPrRules := { mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList + }, + mergifyGenerate := { + IO.write( + (ThisBuild / baseDirectory).value / ".mergify.yml", + generateMergifyContents.value) } ) @@ -67,4 +77,53 @@ object MergifyPlugin extends AutoPlugin { } } + import YamlCompiler._ + + private lazy val generateMergifyContents = Def.task { + compileMergify(mergifyPrRules.value.toList) + } + + private def compileMergify(prRules: List[MergifyPrRule]): String = { + s"""|# This file was automatically generated by sbt-typelevel-mergify using the + |# mergifyGenerate task. You should add and commit this file to + |# your git repository. It goes without saying that you shouldn't edit + |# this file by hand! Instead, if you wish to make changes, you should + |# change your sbt build configuration to revise the mergify configuration + |# to meet your needs, then regenerate this file. + |pullRequestRules:${compileList(prRules.map(compilePrRule), 2, 0)} + |""".stripMargin + } + + private def compilePrRule(rule: MergifyPrRule): String = { + s"""|name: ${rule.name} + |conditions:${compileList(rule.conditions.map(compileCondition), 2, 0)} + |actions:${compileList(rule.actions.map(compileAction), 2, 0)} + |""".stripMargin + } + + private def compileCondition(condition: MergifyCondition): String = { + import MergifyCondition._ + condition match { + case Custom(condition) => condition + case And(conditions) => s"and:${compileList(conditions.map(compileCondition(_)), 2, 0)}" + case Or(conditions) => s"or:${compileList(conditions.map(compileCondition(_)), 2, 0)}" + case _ => sys.error("should not happen") + } + } + + private def compileAction(action: MergifyAction): String = { + import MergifyAction._ + action match { + case Merge(method, rebaseFallback, commitMessageTemplate) => + method.fold("")(m => s"method: ${m}\n") + + rebaseFallback.fold("")(rf => s"rebase_fallback: ${rf}\n") + + commitMessageTemplate.fold("")(cmt => s"commit_message_template: ${cmt}\n") + case Label(add, remove, removeAll) => + Some(add).filter(_.nonEmpty).fold("")(a => s"add:${compileList(a, 2)}\n") + + Some(remove).filter(_.nonEmpty).fold("")(r => s"remove:${compileList(r, 2)}\n") + + removeAll.fold("")(ra => s"removeAll: ${ra}\n") + case _ => sys.error("should not happen") + } + } + } From 0e09de5f1be3aa266268891116211c57dac9263d Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 00:29:20 +0000 Subject: [PATCH 07/22] Why work so hard --- .mergify.yml | 18 +++++++ mergify/build.sbt | 1 + .../typelevel/sbt/mergify/MergifyAction.scala | 40 +++++++++++---- .../sbt/mergify/MergifyCondition.scala | 25 ++++++++++ .../typelevel/sbt/mergify/MergifyPlugin.scala | 49 +++++-------------- .../typelevel/sbt/mergify/MergifyPrRule.scala | 7 +++ project/build.sbt | 1 + project/mergify.sbt | 1 + 8 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 .mergify.yml create mode 100644 mergify/build.sbt create mode 120000 project/mergify.sbt diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 00000000..5c24399e --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,18 @@ +# This file was automatically generated by sbt-typelevel-mergify using the +# mergifyGenerate task. You should add and commit this file to +# your git repository. It goes without saying that you shouldn't edit +# this file by hand! Instead, if you wish to make changes, you should +# change your sbt build configuration to revise the mergify configuration +# to meet your needs, then regenerate this file. +pull_request_rules: +- name: merge scala-steward's PRs + conditions: + - author=scala-steward + - body~=labels:.*early-semver-spec-patch + - status-success=Build and Test (ubuntu-latest, 2.12.15, temurin@8, rootJVM) + actions: + - merge: + method: null + rebase_fallback: null + commit_message_template: null + diff --git a/mergify/build.sbt b/mergify/build.sbt new file mode 100644 index 00000000..ec725236 --- /dev/null +++ b/mergify/build.sbt @@ -0,0 +1 @@ +libraryDependencies += "io.circe" %% "circe-yaml" % "0.14.1" diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala index 7940d823..e0d7b3d3 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala @@ -16,30 +16,50 @@ package org.typelevel.sbt.mergify -sealed abstract class MergifyAction { - def name: String -} +import io.circe.Encoder +import io.circe.Json +import io.circe.syntax._ + +sealed abstract class MergifyAction object MergifyAction { + implicit def encoder: Encoder[MergifyAction] = Encoder.instance { + case merge: Merge => merge.asJson + case label: Label => label.asJson + case _ => sys.error("should not happen") + } + final case class Merge( method: Option[String] = None, rebaseFallback: Option[String] = None, commitMessageTemplate: Option[String] = None - ) extends MergifyAction { - def name = "merge" + ) extends MergifyAction + + object Merge { + implicit def encoder: Encoder[Merge] = + Encoder + .forProduct3("method", "rebase_fallback", "commit_message_template") { (m: Merge) => + (m.method, m.rebaseFallback, m.commitMessageTemplate) + } + .mapJson(m => Json.obj("merge" -> m)) } final case class Label( add: List[String] = Nil, remove: List[String] = Nil, removeAll: Option[Boolean] = None - ) extends MergifyAction { - def name = "label" - } + ) extends MergifyAction - private[this] object Dummy extends MergifyAction { // break exhaustivity checking - def name = "dummy" + object Label { + implicit def encoder: Encoder[Label] = + Encoder + .forProduct3("add", "remove", "remove_all") { (l: Label) => + (l.add, l.remove, l.removeAll) + } + .mapJson(l => Json.obj("label" -> l)) } + private[this] object Dummy extends MergifyAction + } diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala index f85b43b0..eddcc687 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala @@ -16,11 +16,36 @@ package org.typelevel.sbt.mergify +import io.circe.Encoder +import io.circe.Json +import io.circe.syntax._ + sealed abstract class MergifyCondition object MergifyCondition { + implicit def encoder: Encoder[MergifyCondition] = Encoder.instance { + case custom: Custom => custom.asJson + case and: And => and.asJson + case or: Or => or.asJson + case _ => sys.error("shouldn't happen") + } + final case class Custom(condition: String) extends MergifyCondition + object Custom { + implicit def encoder: Encoder[Custom] = Encoder.encodeString.contramap(_.condition) + } + final case class And(conditions: List[MergifyCondition]) extends MergifyCondition + object And { + implicit def encoder: Encoder[And] = + Encoder.forProduct1("conditions")((_: And).conditions).mapJson(a => Json.obj("and" -> a)) + } + final case class Or(conditions: List[MergifyCondition]) extends MergifyCondition + object Or { + implicit def encoder: Encoder[Or] = + Encoder.forProduct1("conditions")((_: Or).conditions).mapJson(o => Json.obj("or" -> o)) + } + private[this] final object Dummy extends MergifyCondition // break exhaustivity checking } diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index f04782ad..25bb151c 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -46,6 +46,7 @@ object MergifyPlugin extends AutoPlugin { import GenerativePlugin.autoImport._ override def buildSettings: Seq[Setting[_]] = Seq( + mergifyStewardConfig := Some(MergifyStewardConfig()), mergifyRequiredJobs := Seq("build"), mergifySuccessConditions := jobSuccessConditions.value, mergifyPrRules := { @@ -58,6 +59,11 @@ object MergifyPlugin extends AutoPlugin { } ) + override def projectSettings: Seq[Setting[_]] = Seq( + mergifyGenerate / aggregate := false, + mergifyCheck / aggregate := false + ) + private lazy val jobSuccessConditions = Def.setting { githubWorkflowGeneratedCI.value.flatMap { case job if mergifyRequiredJobs.value.contains(job.id) => @@ -77,53 +83,20 @@ object MergifyPlugin extends AutoPlugin { } } - import YamlCompiler._ - private lazy val generateMergifyContents = Def.task { - compileMergify(mergifyPrRules.value.toList) - } + import _root_.io.circe.syntax._ + import _root_.io.circe.yaml.syntax._ + + val contents = Map("pull_request_rules" -> mergifyPrRules.value.toList) - private def compileMergify(prRules: List[MergifyPrRule]): String = { s"""|# This file was automatically generated by sbt-typelevel-mergify using the |# mergifyGenerate task. You should add and commit this file to |# your git repository. It goes without saying that you shouldn't edit |# this file by hand! Instead, if you wish to make changes, you should |# change your sbt build configuration to revise the mergify configuration |# to meet your needs, then regenerate this file. - |pullRequestRules:${compileList(prRules.map(compilePrRule), 2, 0)} - |""".stripMargin - } - - private def compilePrRule(rule: MergifyPrRule): String = { - s"""|name: ${rule.name} - |conditions:${compileList(rule.conditions.map(compileCondition), 2, 0)} - |actions:${compileList(rule.actions.map(compileAction), 2, 0)} + |${contents.asJson.asYaml.spaces2} |""".stripMargin } - private def compileCondition(condition: MergifyCondition): String = { - import MergifyCondition._ - condition match { - case Custom(condition) => condition - case And(conditions) => s"and:${compileList(conditions.map(compileCondition(_)), 2, 0)}" - case Or(conditions) => s"or:${compileList(conditions.map(compileCondition(_)), 2, 0)}" - case _ => sys.error("should not happen") - } - } - - private def compileAction(action: MergifyAction): String = { - import MergifyAction._ - action match { - case Merge(method, rebaseFallback, commitMessageTemplate) => - method.fold("")(m => s"method: ${m}\n") + - rebaseFallback.fold("")(rf => s"rebase_fallback: ${rf}\n") + - commitMessageTemplate.fold("")(cmt => s"commit_message_template: ${cmt}\n") - case Label(add, remove, removeAll) => - Some(add).filter(_.nonEmpty).fold("")(a => s"add:${compileList(a, 2)}\n") + - Some(remove).filter(_.nonEmpty).fold("")(r => s"remove:${compileList(r, 2)}\n") + - removeAll.fold("")(ra => s"removeAll: ${ra}\n") - case _ => sys.error("should not happen") - } - } - } diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala index 4db01502..649ed946 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala @@ -16,8 +16,15 @@ package org.typelevel.sbt.mergify +import io.circe.Encoder + final case class MergifyPrRule( name: String, conditions: List[MergifyCondition], actions: List[MergifyAction] ) + +object MergifyPrRule { + implicit def encoder: Encoder[MergifyPrRule] = + Encoder.forProduct3("name", "conditions", "actions")(m => (m.name, m.conditions, m.actions)) +} diff --git a/project/build.sbt b/project/build.sbt index 27c448e3..673bf53b 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -6,6 +6,7 @@ val modules = List( "github", "github-actions", "kernel", + "mergify", "mima", "no-publish", "settings", diff --git a/project/mergify.sbt b/project/mergify.sbt new file mode 120000 index 00000000..9a4cbd2c --- /dev/null +++ b/project/mergify.sbt @@ -0,0 +1 @@ +../mergify/build.sbt \ No newline at end of file From 74bc703ebabad16e6430402eda4efaed9ab115dd Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 00:35:04 +0000 Subject: [PATCH 08/22] Revert "Extract out YamlCompiler" This reverts commit 4a183776d3606e766ce6b9e3b552309d385bb716. --- .../typelevel/sbt/gha/GenerativePlugin.scala | 49 ++++++++++++++++- .../org/typelevel/sbt/gha/YamlCompiler.scala | 53 ------------------- 2 files changed, 48 insertions(+), 54 deletions(-) delete mode 100644 github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala diff --git a/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala b/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala index cb1f0bcd..9346535b 100644 --- a/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala +++ b/github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala @@ -63,7 +63,54 @@ object GenerativePlugin extends AutoPlugin { } import autoImport._ - import YamlCompiler._ + + private def indent(output: String, level: Int): String = { + val space = (0 until level * 2).map(_ => ' ').mkString + (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") + } + + private def isSafeString(str: String): Boolean = + !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity + str.indexOf('#') >= 0 || // same for comment + str.indexOf('!') == 0 || + str.indexOf('*') == 0 || + str.indexOf('-') == 0 || + str.indexOf('?') == 0 || + str.indexOf('{') == 0 || + str.indexOf('}') == 0 || + str.indexOf('[') == 0 || + str.indexOf(']') == 0 || + str.indexOf(',') == 0 || + str.indexOf('|') == 0 || + str.indexOf('>') == 0 || + str.indexOf('@') == 0 || + str.indexOf('`') == 0 || + str.indexOf('"') == 0 || + str.indexOf('\'') == 0 || + str.indexOf('&') == 0) + + private def wrap(str: String): String = + if (str.indexOf('\n') >= 0) + "|\n" + indent(str, 1) + else if (isSafeString(str)) + str + else + s"'${str.replace("'", "''")}'" + + def compileList(items: List[String], level: Int): String = { + val rendered = items.map(wrap) + if (rendered.map(_.length).sum < 40) // just arbitrarily... + rendered.mkString(" [", ", ", "]") + else + "\n" + indent(rendered.map("- " + _).mkString("\n"), level) + } + + def compileListOfSimpleDicts(items: List[Map[String, String]]): String = + items map { dict => + val rendered = dict map { case (key, value) => s"$key: $value" } mkString "\n" + + "-" + indent(rendered, 1).substring(1) + } mkString "\n" def compilePREventType(tpe: PREventType): String = { import PREventType._ diff --git a/github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala b/github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala deleted file mode 100644 index 371b0e5e..00000000 --- a/github-actions/src/main/scala/org/typelevel/sbt/gha/YamlCompiler.scala +++ /dev/null @@ -1,53 +0,0 @@ -package org.typelevel.sbt.gha - -private[sbt] object YamlCompiler { - - def indent(output: String, level: Int): String = { - val space = (0 until level * 2).map(_ => ' ').mkString - (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") - } - - def isSafeString(str: String): Boolean = - !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity - str.indexOf('#') >= 0 || // same for comment - str.indexOf('!') == 0 || - str.indexOf('*') == 0 || - str.indexOf('-') == 0 || - str.indexOf('?') == 0 || - str.indexOf('{') == 0 || - str.indexOf('}') == 0 || - str.indexOf('[') == 0 || - str.indexOf(']') == 0 || - str.indexOf(',') == 0 || - str.indexOf('|') == 0 || - str.indexOf('>') == 0 || - str.indexOf('@') == 0 || - str.indexOf('`') == 0 || - str.indexOf('"') == 0 || - str.indexOf('\'') == 0 || - str.indexOf('&') == 0) - - def wrap(str: String): String = - if (str.indexOf('\n') >= 0) - "|\n" + indent(str, 1) - else if (isSafeString(str)) - str - else - s"'${str.replace("'", "''")}'" - - def compileList(items: List[String], level: Int, maxLength: Int = 40): String = { - val rendered = items.map(wrap) - if (rendered.map(_.length).sum < maxLength) // just arbitrarily... - rendered.mkString(" [", ", ", "]") - else - "\n" + indent(rendered.map("- " + _).mkString("\n"), level) - } - - def compileListOfSimpleDicts(items: List[Map[String, String]]): String = - items map { dict => - val rendered = dict map { case (key, value) => s"$key: $value" } mkString "\n" - - "-" + indent(rendered, 1).substring(1) - } mkString "\n" - -} From 6e3b473ba5eba91bad434b18fd9110763f2ed8c5 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 01:57:51 +0000 Subject: [PATCH 09/22] Fixing and dog-fooding --- .mergify.yml | 6 ++- build.sbt | 3 ++ .../sbt/mergify/MergifyCondition.scala | 6 +-- .../typelevel/sbt/mergify/MergifyPlugin.scala | 37 +++++++++++++++++-- .../sbt/mergify/MergifyStewardConfig.scala | 10 ++--- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 5c24399e..a7f74e77 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -4,12 +4,16 @@ # this file by hand! Instead, if you wish to make changes, you should # change your sbt build configuration to revise the mergify configuration # to meet your needs, then regenerate this file. + pull_request_rules: - name: merge scala-steward's PRs conditions: - author=scala-steward - - body~=labels:.*early-semver-spec-patch + - or: + - body~=labels:.*early-semver-patch + - body~=labels:.*early-semver-minor - status-success=Build and Test (ubuntu-latest, 2.12.15, temurin@8, rootJVM) + - '#approved-reviews-by>=1' actions: - merge: method: null diff --git a/build.sbt b/build.sbt index aa545ef2..17678967 100644 --- a/build.sbt +++ b/build.sbt @@ -11,6 +11,9 @@ ThisBuild / developers := List( tlGitHubDev("djspiewak", "Daniel Spiewak") ) +ThisBuild / mergifyStewardConfig ~= { _.map(_.copy(mergeMinors = true)) } +ThisBuild / mergifySuccessConditions += MergifyCondition.Custom("#approved-reviews-by>=1") + lazy val root = tlCrossRootProject.aggregate( kernel, noPublish, diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala index eddcc687..87e813b6 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala @@ -37,14 +37,12 @@ object MergifyCondition { final case class And(conditions: List[MergifyCondition]) extends MergifyCondition object And { - implicit def encoder: Encoder[And] = - Encoder.forProduct1("conditions")((_: And).conditions).mapJson(a => Json.obj("and" -> a)) + implicit def encoder: Encoder[And] = Encoder.forProduct1("and")(_.conditions) } final case class Or(conditions: List[MergifyCondition]) extends MergifyCondition object Or { - implicit def encoder: Encoder[Or] = - Encoder.forProduct1("conditions")((_: Or).conditions).mapJson(o => Json.obj("or" -> o)) + implicit def encoder: Encoder[Or] = Encoder.forProduct1("or")(_.conditions) } private[this] final object Dummy extends MergifyCondition // break exhaustivity checking diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 25bb151c..7afe466c 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -37,6 +37,15 @@ object MergifyPlugin extends AutoPlugin { lazy val mergifySuccessConditions = settingKey[Seq[MergifyCondition]]( "Success conditions for merging (default: auto-generated from `mergifyRequiredJobs` setting)") + + type MergifyAction = org.typelevel.sbt.mergify.MergifyAction + val MergifyAction = org.typelevel.sbt.mergify.MergifyAction + type MergifyCondition = org.typelevel.sbt.mergify.MergifyCondition + val MergifyCondition = org.typelevel.sbt.mergify.MergifyCondition + type MergifyPrRule = org.typelevel.sbt.mergify.MergifyPrRule + val MergifyPrRule = org.typelevel.sbt.mergify.MergifyPrRule + type MergifyStewardConfig = org.typelevel.sbt.mergify.MergifyStewardConfig + val MergifyStewardConfig = org.typelevel.sbt.mergify.MergifyStewardConfig } override def requires = GenerativePlugin @@ -53,9 +62,26 @@ object MergifyPlugin extends AutoPlugin { mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList }, mergifyGenerate := { - IO.write( - (ThisBuild / baseDirectory).value / ".mergify.yml", - generateMergifyContents.value) + IO.write(mergifyYaml.value, generateMergifyContents.value) + }, + mergifyCheck := { + val log = state.value.log + + def reportMismatch(file: File, expected: String, actual: String): Unit = { + log.error(s"Expected:\n$expected") + log.error(s"Actual:\n${GenerativePlugin.diff(expected, actual)}") + sys.error( + s"${file.getName} does not contain contents that would have been generated by sbt-typelevel-mergify; try running mergifyGenerate") + } + + def compare(file: File, expected: String): Unit = { + val actual = IO.read(file) + if (expected != actual) { + reportMismatch(file, expected, actual) + } + } + + compare(mergifyYaml.value, generateMergifyContents.value) } ) @@ -83,6 +109,10 @@ object MergifyPlugin extends AutoPlugin { } } + private lazy val mergifyYaml = Def.setting { + (ThisBuild / baseDirectory).value / ".mergify.yml" + } + private lazy val generateMergifyContents = Def.task { import _root_.io.circe.syntax._ import _root_.io.circe.yaml.syntax._ @@ -95,6 +125,7 @@ object MergifyPlugin extends AutoPlugin { |# this file by hand! Instead, if you wish to make changes, you should |# change your sbt build configuration to revise the mergify configuration |# to meet your needs, then regenerate this file. + | |${contents.asJson.asYaml.spaces2} |""".stripMargin } diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala index 5079fc99..84be682e 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyStewardConfig.scala @@ -19,24 +19,24 @@ package org.typelevel.sbt.mergify final case class MergifyStewardConfig( name: String = "merge scala-steward's PRs", author: String = "scala-steward", - minor: Boolean = false, - merge: MergifyAction.Merge = MergifyAction.Merge() + mergeMinors: Boolean = false, + action: MergifyAction.Merge = MergifyAction.Merge() ) { private[mergify] def toPrRule(buildConditions: List[MergifyCondition]): MergifyPrRule = { val authorCond = MergifyCondition.Custom(s"author=$author") val bodyCond = { - val patchCond = MergifyCondition.Custom("body~=labels:.*early-semver-spec-patch") + val patchCond = MergifyCondition.Custom("body~=labels:.*early-semver-patch") val minorCond = MergifyCondition.Custom("body~=labels:.*early-semver-minor") - if (minor) MergifyCondition.Or(List(patchCond, minorCond)) + if (mergeMinors) MergifyCondition.Or(List(patchCond, minorCond)) else patchCond } MergifyPrRule( name, authorCond :: bodyCond :: buildConditions, - List(merge) + List(action) ) } From 9e14a2b6daddcfb93d37823666ed2bc9a5bdf571 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 02:07:22 +0000 Subject: [PATCH 10/22] Drop nulls --- .mergify.yml | 5 +---- .../main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala | 5 +++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index a7f74e77..9787f4bf 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -15,8 +15,5 @@ pull_request_rules: - status-success=Build and Test (ubuntu-latest, 2.12.15, temurin@8, rootJVM) - '#approved-reviews-by>=1' actions: - - merge: - method: null - rebase_fallback: null - commit_message_template: null + - merge: {} diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 7afe466c..be8935c2 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -115,9 +115,10 @@ object MergifyPlugin extends AutoPlugin { private lazy val generateMergifyContents = Def.task { import _root_.io.circe.syntax._ - import _root_.io.circe.yaml.syntax._ + import _root_.io.circe.yaml.Printer val contents = Map("pull_request_rules" -> mergifyPrRules.value.toList) + val printer = Printer.spaces2.copy(dropNullKeys = true) s"""|# This file was automatically generated by sbt-typelevel-mergify using the |# mergifyGenerate task. You should add and commit this file to @@ -126,7 +127,7 @@ object MergifyPlugin extends AutoPlugin { |# change your sbt build configuration to revise the mergify configuration |# to meet your needs, then regenerate this file. | - |${contents.asJson.asYaml.spaces2} + |${printer.pretty(contents.asJson)} |""".stripMargin } From a8cbe69d3a68d60008cc593dc37fa66ac8dd5fc1 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 02:12:12 +0000 Subject: [PATCH 11/22] Linting --- .../main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala index 87e813b6..ea6f8c29 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyCondition.scala @@ -17,7 +17,6 @@ package org.typelevel.sbt.mergify import io.circe.Encoder -import io.circe.Json import io.circe.syntax._ sealed abstract class MergifyCondition From 640d0cea5b43b73a16af687c84eb3e4a8e392318 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 02:42:51 +0000 Subject: [PATCH 12/22] mergify module introduced in 0.4.6 --- build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 17678967..bc6de885 100644 --- a/build.sbt +++ b/build.sbt @@ -74,7 +74,8 @@ lazy val mergify = project .in(file("mergify")) .enablePlugins(SbtPlugin) .settings( - name := "sbt-typelevel-mergify" + name := "sbt-typelevel-mergify", + tlVersionIntroduced := Map("2.12" -> "0.4.6") ) .dependsOn(githubActions) From 9c9c3eec39f29c98bd756203ad65d605b30b6b49 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 02:58:46 +0000 Subject: [PATCH 13/22] Mergify generate/check tag-along w/ workflow --- .../scala/org/typelevel/sbt/mergify/MergifyPlugin.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index be8935c2..6d3b5fc7 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -87,7 +87,11 @@ object MergifyPlugin extends AutoPlugin { override def projectSettings: Seq[Setting[_]] = Seq( mergifyGenerate / aggregate := false, - mergifyCheck / aggregate := false + mergifyCheck / aggregate := false, + githubWorkflowGenerate := githubWorkflowGenerate + .dependsOn((ThisBuild / mergifyGenerate)) + .value, + githubWorkflowCheck := githubWorkflowCheck.dependsOn((ThisBuild / mergifyCheck)).value ) private lazy val jobSuccessConditions = Def.setting { From 1810bd7fb52af63954306f7346d8e25a24a0e03c Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 04:03:21 +0000 Subject: [PATCH 14/22] Fix actions rendering --- .mergify.yml | 2 +- .../typelevel/sbt/mergify/MergifyAction.scala | 21 ++++++++----------- .../typelevel/sbt/mergify/MergifyPrRule.scala | 4 +++- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 9787f4bf..30c1ef57 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -15,5 +15,5 @@ pull_request_rules: - status-success=Build and Test (ubuntu-latest, 2.12.15, temurin@8, rootJVM) - '#approved-reviews-by>=1' actions: - - merge: {} + merge: {} diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala index e0d7b3d3..9c96203e 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyAction.scala @@ -17,10 +17,11 @@ package org.typelevel.sbt.mergify import io.circe.Encoder -import io.circe.Json import io.circe.syntax._ -sealed abstract class MergifyAction +sealed abstract class MergifyAction { + private[mergify] def name = getClass.getSimpleName.toLowerCase +} object MergifyAction { @@ -38,11 +39,9 @@ object MergifyAction { object Merge { implicit def encoder: Encoder[Merge] = - Encoder - .forProduct3("method", "rebase_fallback", "commit_message_template") { (m: Merge) => - (m.method, m.rebaseFallback, m.commitMessageTemplate) - } - .mapJson(m => Json.obj("merge" -> m)) + Encoder.forProduct3("method", "rebase_fallback", "commit_message_template") { + (m: Merge) => (m.method, m.rebaseFallback, m.commitMessageTemplate) + } } final case class Label( @@ -53,11 +52,9 @@ object MergifyAction { object Label { implicit def encoder: Encoder[Label] = - Encoder - .forProduct3("add", "remove", "remove_all") { (l: Label) => - (l.add, l.remove, l.removeAll) - } - .mapJson(l => Json.obj("label" -> l)) + Encoder.forProduct3("add", "remove", "remove_all") { (l: Label) => + (l.add, l.remove, l.removeAll) + } } private[this] object Dummy extends MergifyAction diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala index 649ed946..a9421bb5 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPrRule.scala @@ -26,5 +26,7 @@ final case class MergifyPrRule( object MergifyPrRule { implicit def encoder: Encoder[MergifyPrRule] = - Encoder.forProduct3("name", "conditions", "actions")(m => (m.name, m.conditions, m.actions)) + Encoder.forProduct3("name", "conditions", "actions") { m => + (m.name, m.conditions, m.actions.map(a => a.name -> a).toMap) + } } From b39ea127bf6d2d60db9bd40633f1e5d62407a4c8 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 26 Feb 2022 05:01:12 +0000 Subject: [PATCH 15/22] Add mergifyLabelPaths setting --- .../scala/org/typelevel/sbt/mergify/MergifyPlugin.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 6d3b5fc7..03f38ca2 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -19,6 +19,8 @@ package org.typelevel.sbt.mergify import sbt._, Keys._ import org.typelevel.sbt.gha._ +import java.nio.file.Path + object MergifyPlugin extends AutoPlugin { object autoImport { @@ -38,6 +40,9 @@ object MergifyPlugin extends AutoPlugin { lazy val mergifySuccessConditions = settingKey[Seq[MergifyCondition]]( "Success conditions for merging (default: auto-generated from `mergifyRequiredJobs` setting)") + lazy val mergifyLabelPaths = settingKey[Map[String, Path]]( + "A map from label to path (default: auto-populated for every subproject in your build)") + type MergifyAction = org.typelevel.sbt.mergify.MergifyAction val MergifyAction = org.typelevel.sbt.mergify.MergifyAction type MergifyCondition = org.typelevel.sbt.mergify.MergifyCondition @@ -57,6 +62,7 @@ object MergifyPlugin extends AutoPlugin { override def buildSettings: Seq[Setting[_]] = Seq( mergifyStewardConfig := Some(MergifyStewardConfig()), mergifyRequiredJobs := Seq("build"), + mergifyLabelPaths := Map.empty, mergifySuccessConditions := jobSuccessConditions.value, mergifyPrRules := { mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList From fd160ed14e5dfea858721d338c05cd8e8f445323 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 1 Mar 2022 18:29:52 +0000 Subject: [PATCH 16/22] Setup PR auto-labeling --- .mergify.yml | 128 ++++++++++++++++++ .../typelevel/sbt/mergify/MergifyPlugin.scala | 51 ++++++- 2 files changed, 177 insertions(+), 2 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 30c1ef57..1c0b98d8 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -16,4 +16,132 @@ pull_request_rules: - '#approved-reviews-by>=1' actions: merge: {} +- name: Label ci PRs + conditions: + - files~=^ci + actions: + label: + add: + - ci + remove: [] +- name: Label ci-release PRs + conditions: + - files~=^ci-release + actions: + label: + add: + - ci-release + remove: [] +- name: Label ci-signing PRs + conditions: + - files~=^ci-signing + actions: + label: + add: + - ci-signing + remove: [] +- name: Label core PRs + conditions: + - files~=^core + actions: + label: + add: + - core + remove: [] +- name: Label github PRs + conditions: + - files~=^github + actions: + label: + add: + - github + remove: [] +- name: Label github-actions PRs + conditions: + - files~=^github-actions + actions: + label: + add: + - github-actions + remove: [] +- name: Label kernel PRs + conditions: + - files~=^kernel + actions: + label: + add: + - kernel + remove: [] +- name: Label mdocs PRs + conditions: + - files~=^mdocs + actions: + label: + add: + - mdocs + remove: [] +- name: Label mergify PRs + conditions: + - files~=^mergify + actions: + label: + add: + - mergify + remove: [] +- name: Label mima PRs + conditions: + - files~=^mima + actions: + label: + add: + - mima + remove: [] +- name: Label no-publish PRs + conditions: + - files~=^no-publish + actions: + label: + add: + - no-publish + remove: [] +- name: Label settings PRs + conditions: + - files~=^settings + actions: + label: + add: + - settings + remove: [] +- name: Label site PRs + conditions: + - files~=^site + actions: + label: + add: + - site + remove: [] +- name: Label sonatype PRs + conditions: + - files~=^sonatype + actions: + label: + add: + - sonatype + remove: [] +- name: Label sonatype-ci-release PRs + conditions: + - files~=^sonatype-ci-release + actions: + label: + add: + - sonatype-ci-release + remove: [] +- name: Label versioning PRs + conditions: + - files~=^versioning + actions: + label: + add: + - versioning + remove: [] diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 03f38ca2..fff80012 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -65,7 +65,20 @@ object MergifyPlugin extends AutoPlugin { mergifyLabelPaths := Map.empty, mergifySuccessConditions := jobSuccessConditions.value, mergifyPrRules := { - mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList + val baseDir = (LocalRootProject / baseDirectory).value.toPath + val stewardRule = + mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList + val labelRules = + mergifyLabelPaths.value.toList.sorted.map { + case (label, path) => + val relPath = baseDir.relativize(path) + MergifyPrRule( + s"Label ${label} PRs", + List(MergifyCondition.Custom(s"files~=^${relPath}")), + List(MergifyAction.Label(add = List(label))) + ) + } + stewardRule ++ labelRules }, mergifyGenerate := { IO.write(mergifyYaml.value, generateMergifyContents.value) @@ -97,7 +110,22 @@ object MergifyPlugin extends AutoPlugin { githubWorkflowGenerate := githubWorkflowGenerate .dependsOn((ThisBuild / mergifyGenerate)) .value, - githubWorkflowCheck := githubWorkflowCheck.dependsOn((ThisBuild / mergifyCheck)).value + githubWorkflowCheck := githubWorkflowCheck.dependsOn((ThisBuild / mergifyCheck)).value, + ThisBuild / mergifyLabelPaths := { + val labelPaths = (ThisBuild / mergifyLabelPaths).value + val label = projectLabel.value + def isRoot = label._2 == + (LocalRootProject / baseDirectory).value.toPath + if (label._1.startsWith(".") || isRoot) { // don't label this project + labelPaths + } else { + val add = labelPaths.get(label._1) match { + case Some(path) => label._1 -> commonAncestor(path, label._2) + case None => label + } + labelPaths + add + } + } ) private lazy val jobSuccessConditions = Def.setting { @@ -119,6 +147,25 @@ object MergifyPlugin extends AutoPlugin { } } + private lazy val projectLabel = Def.setting { + val path = (Compile / sourceDirectories) + .? + .value + .getOrElse(Seq.empty) + .map(_.toPath) + .foldLeft(baseDirectory.value.toPath)(commonAncestor(_, _)) + path.getFileName.toString -> path + } + + // x and y should be absolute/normalized + private def commonAncestor(x: Path, y: Path): Path = { + val n = math.min(x.getNameCount, y.getNameCount) + (0 until n) + .takeWhile(i => x.getName(i) == y.getName(i)) + .map(x.getName(_)) + .foldLeft(java.nio.file.Paths.get("/"))(_.resolve(_)) + } + private lazy val mergifyYaml = Def.setting { (ThisBuild / baseDirectory).value / ".mergify.yml" } From aca1d3320855beab281f27fe18156c7644784b8a Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 1 Mar 2022 18:31:42 +0000 Subject: [PATCH 17/22] Fix dangling newline --- .mergify.yml | 1 - .../main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 1c0b98d8..18dc8957 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -144,4 +144,3 @@ pull_request_rules: add: - versioning remove: [] - diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index fff80012..cf36b126 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -184,8 +184,7 @@ object MergifyPlugin extends AutoPlugin { |# change your sbt build configuration to revise the mergify configuration |# to meet your needs, then regenerate this file. | - |${printer.pretty(contents.asJson)} - |""".stripMargin + |${printer.pretty(contents.asJson)}""".stripMargin } } From 34df1ac7c659c79b1a89e1b04640fd631f1f258c Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 1 Mar 2022 18:35:28 +0000 Subject: [PATCH 18/22] Formatting --- .../main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index cf36b126..0a9e8e4b 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -68,7 +68,7 @@ object MergifyPlugin extends AutoPlugin { val baseDir = (LocalRootProject / baseDirectory).value.toPath val stewardRule = mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList - val labelRules = + val labelRules = mergifyLabelPaths.value.toList.sorted.map { case (label, path) => val relPath = baseDir.relativize(path) From 41340e25915d0bab8d4c2377957e3017639e10b9 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 1 Mar 2022 18:42:26 +0000 Subject: [PATCH 19/22] Add trailing slash --- .mergify.yml | 32 +++++++++---------- .../typelevel/sbt/mergify/MergifyPlugin.scala | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 18dc8957..a04d3b7f 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -18,7 +18,7 @@ pull_request_rules: merge: {} - name: Label ci PRs conditions: - - files~=^ci + - files~=^ci/ actions: label: add: @@ -26,7 +26,7 @@ pull_request_rules: remove: [] - name: Label ci-release PRs conditions: - - files~=^ci-release + - files~=^ci-release/ actions: label: add: @@ -34,7 +34,7 @@ pull_request_rules: remove: [] - name: Label ci-signing PRs conditions: - - files~=^ci-signing + - files~=^ci-signing/ actions: label: add: @@ -42,7 +42,7 @@ pull_request_rules: remove: [] - name: Label core PRs conditions: - - files~=^core + - files~=^core/ actions: label: add: @@ -50,7 +50,7 @@ pull_request_rules: remove: [] - name: Label github PRs conditions: - - files~=^github + - files~=^github/ actions: label: add: @@ -58,7 +58,7 @@ pull_request_rules: remove: [] - name: Label github-actions PRs conditions: - - files~=^github-actions + - files~=^github-actions/ actions: label: add: @@ -66,7 +66,7 @@ pull_request_rules: remove: [] - name: Label kernel PRs conditions: - - files~=^kernel + - files~=^kernel/ actions: label: add: @@ -74,7 +74,7 @@ pull_request_rules: remove: [] - name: Label mdocs PRs conditions: - - files~=^mdocs + - files~=^mdocs/ actions: label: add: @@ -82,7 +82,7 @@ pull_request_rules: remove: [] - name: Label mergify PRs conditions: - - files~=^mergify + - files~=^mergify/ actions: label: add: @@ -90,7 +90,7 @@ pull_request_rules: remove: [] - name: Label mima PRs conditions: - - files~=^mima + - files~=^mima/ actions: label: add: @@ -98,7 +98,7 @@ pull_request_rules: remove: [] - name: Label no-publish PRs conditions: - - files~=^no-publish + - files~=^no-publish/ actions: label: add: @@ -106,7 +106,7 @@ pull_request_rules: remove: [] - name: Label settings PRs conditions: - - files~=^settings + - files~=^settings/ actions: label: add: @@ -114,7 +114,7 @@ pull_request_rules: remove: [] - name: Label site PRs conditions: - - files~=^site + - files~=^site/ actions: label: add: @@ -122,7 +122,7 @@ pull_request_rules: remove: [] - name: Label sonatype PRs conditions: - - files~=^sonatype + - files~=^sonatype/ actions: label: add: @@ -130,7 +130,7 @@ pull_request_rules: remove: [] - name: Label sonatype-ci-release PRs conditions: - - files~=^sonatype-ci-release + - files~=^sonatype-ci-release/ actions: label: add: @@ -138,7 +138,7 @@ pull_request_rules: remove: [] - name: Label versioning PRs conditions: - - files~=^versioning + - files~=^versioning/ actions: label: add: diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 0a9e8e4b..4c4b628a 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -74,7 +74,7 @@ object MergifyPlugin extends AutoPlugin { val relPath = baseDir.relativize(path) MergifyPrRule( s"Label ${label} PRs", - List(MergifyCondition.Custom(s"files~=^${relPath}")), + List(MergifyCondition.Custom(s"files~=^${relPath}/")), List(MergifyAction.Label(add = List(label))) ) } From 95e8e688f8e623cc5bc62fbc9990e0abc1fabd22 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 1 Mar 2022 18:51:28 +0000 Subject: [PATCH 20/22] File is more friendly with sbt dsl --- .../org/typelevel/sbt/mergify/MergifyPlugin.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 4c4b628a..621d0df2 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -40,8 +40,8 @@ object MergifyPlugin extends AutoPlugin { lazy val mergifySuccessConditions = settingKey[Seq[MergifyCondition]]( "Success conditions for merging (default: auto-generated from `mergifyRequiredJobs` setting)") - lazy val mergifyLabelPaths = settingKey[Map[String, Path]]( - "A map from label to path (default: auto-populated for every subproject in your build)") + lazy val mergifyLabelPaths = settingKey[Map[String, File]]( + "A map from label to file path (default: auto-populated for every subproject in your build)") type MergifyAction = org.typelevel.sbt.mergify.MergifyAction val MergifyAction = org.typelevel.sbt.mergify.MergifyAction @@ -70,8 +70,8 @@ object MergifyPlugin extends AutoPlugin { mergifyStewardConfig.value.map(_.toPrRule(mergifySuccessConditions.value.toList)).toList val labelRules = mergifyLabelPaths.value.toList.sorted.map { - case (label, path) => - val relPath = baseDir.relativize(path) + case (label, file) => + val relPath = baseDir.relativize(file.toPath) MergifyPrRule( s"Label ${label} PRs", List(MergifyCondition.Custom(s"files~=^${relPath}/")), @@ -120,10 +120,10 @@ object MergifyPlugin extends AutoPlugin { labelPaths } else { val add = labelPaths.get(label._1) match { - case Some(path) => label._1 -> commonAncestor(path, label._2) + case Some(path) => label._1 -> commonAncestor(path.toPath, label._2) case None => label } - labelPaths + add + labelPaths + (add._1 -> add._2.toFile) } } ) From 969a2e2bf9450d1234e373e4b1c4ecf6052e2afb Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 1 Mar 2022 19:02:02 +0000 Subject: [PATCH 21/22] Support user-added labels --- .mergify.yml | 8 ++++++++ build.sbt | 1 + .../scala/org/typelevel/sbt/mergify/MergifyPlugin.scala | 5 +++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index a04d3b7f..5d985a70 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -48,6 +48,14 @@ pull_request_rules: add: - core remove: [] +- name: Label docs PRs + conditions: + - files~=^docs/ + actions: + label: + add: + - docs + remove: [] - name: Label github PRs conditions: - files~=^github/ diff --git a/build.sbt b/build.sbt index bc6de885..8208da73 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,7 @@ ThisBuild / developers := List( ThisBuild / mergifyStewardConfig ~= { _.map(_.copy(mergeMinors = true)) } ThisBuild / mergifySuccessConditions += MergifyCondition.Custom("#approved-reviews-by>=1") +ThisBuild / mergifyLabelPaths += { "docs" -> file("docs") } lazy val root = tlCrossRootProject.aggregate( kernel, diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 621d0df2..940b749a 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -71,10 +71,11 @@ object MergifyPlugin extends AutoPlugin { val labelRules = mergifyLabelPaths.value.toList.sorted.map { case (label, file) => - val relPath = baseDir.relativize(file.toPath) + val relPath = baseDir.relativize(file.toPath.toAbsolutePath.normalize) + val suffix = if (file.isDirectory) "/" else "" MergifyPrRule( s"Label ${label} PRs", - List(MergifyCondition.Custom(s"files~=^${relPath}/")), + List(MergifyCondition.Custom(s"files~=^${relPath}${suffix}")), List(MergifyAction.Label(add = List(label))) ) } From 9843119f4a2aaaa67e646dd9911db23b9f3a44af Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 1 Mar 2022 19:57:09 +0000 Subject: [PATCH 22/22] Refactoring --- .../typelevel/sbt/mergify/MergifyPlugin.scala | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala index 940b749a..91706851 100644 --- a/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala +++ b/mergify/src/main/scala/org/typelevel/sbt/mergify/MergifyPlugin.scala @@ -114,17 +114,13 @@ object MergifyPlugin extends AutoPlugin { githubWorkflowCheck := githubWorkflowCheck.dependsOn((ThisBuild / mergifyCheck)).value, ThisBuild / mergifyLabelPaths := { val labelPaths = (ThisBuild / mergifyLabelPaths).value - val label = projectLabel.value - def isRoot = label._2 == - (LocalRootProject / baseDirectory).value.toPath - if (label._1.startsWith(".") || isRoot) { // don't label this project - labelPaths - } else { - val add = labelPaths.get(label._1) match { - case Some(path) => label._1 -> commonAncestor(path.toPath, label._2) - case None => label - } - labelPaths + (add._1 -> add._2.toFile) + projectLabel.value.fold(labelPaths) { + case (label, path) => + val add = labelPaths.get(label) match { + case Some(f) => label -> commonAncestor(f.toPath, path) + case None => label -> path + } + labelPaths + (add._1 -> add._2.toFile) } } ) @@ -155,7 +151,13 @@ object MergifyPlugin extends AutoPlugin { .getOrElse(Seq.empty) .map(_.toPath) .foldLeft(baseDirectory.value.toPath)(commonAncestor(_, _)) - path.getFileName.toString -> path + + val label = path.getFileName.toString + + def isRoot = path == (LocalRootProject / baseDirectory).value.toPath + if (label.startsWith(".") || isRoot) // don't label this project + None + else Some(label -> path) } // x and y should be absolute/normalized