mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[platform] simplifies updater classes API (IDEA-CR-35410)
This commit is contained in:
-59
@@ -1,59 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.updateSettings.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class CheckForUpdateResult {
|
||||
private final UpdateStrategy.State myState;
|
||||
private final BuildInfo myNewBuild;
|
||||
private final UpdateChannel myUpdatedChannel;
|
||||
private final PatchInfo myPatch;
|
||||
private final ChainInfo myPatchChain;
|
||||
private final Exception myError;
|
||||
|
||||
CheckForUpdateResult(@Nullable BuildInfo newBuild,
|
||||
@Nullable UpdateChannel updatedChannel,
|
||||
@Nullable PatchInfo patch,
|
||||
@Nullable ChainInfo chain) {
|
||||
myState = UpdateStrategy.State.LOADED;
|
||||
myNewBuild = newBuild;
|
||||
myUpdatedChannel = updatedChannel;
|
||||
myPatch = patch;
|
||||
myPatchChain = chain;
|
||||
myError = null;
|
||||
}
|
||||
|
||||
CheckForUpdateResult(@NotNull UpdateStrategy.State state, @Nullable Exception e) {
|
||||
myState = state;
|
||||
myNewBuild = null;
|
||||
myUpdatedChannel = null;
|
||||
myPatch = null;
|
||||
myPatchChain = null;
|
||||
myError = e;
|
||||
}
|
||||
|
||||
public @NotNull UpdateStrategy.State getState() {
|
||||
return myState;
|
||||
}
|
||||
|
||||
public @Nullable BuildInfo getNewBuild() {
|
||||
return myNewBuild;
|
||||
}
|
||||
|
||||
public @Nullable UpdateChannel getUpdatedChannel() {
|
||||
return myUpdatedChannel;
|
||||
}
|
||||
|
||||
public @Nullable PatchInfo getPatch() {
|
||||
return myPatch;
|
||||
}
|
||||
|
||||
public @Nullable ChainInfo getPatchChain() {
|
||||
return myPatchChain;
|
||||
}
|
||||
|
||||
public @Nullable Exception getError() {
|
||||
return myError;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.updateSettings.impl
|
||||
|
||||
import com.intellij.openapi.util.BuildNumber
|
||||
|
||||
class UpdateChain internal constructor(val chain: List<BuildNumber>, val size: String?)
|
||||
|
||||
class CheckForUpdateResult {
|
||||
val state: UpdateStrategy.State
|
||||
val newBuild: BuildInfo?
|
||||
val updatedChannel: UpdateChannel?
|
||||
val patches: UpdateChain?
|
||||
val error: Exception?
|
||||
|
||||
internal constructor(newBuild: BuildInfo?, updatedChannel: UpdateChannel?, patches: UpdateChain?) {
|
||||
this.state = UpdateStrategy.State.LOADED
|
||||
this.newBuild = newBuild
|
||||
this.updatedChannel = updatedChannel
|
||||
this.patches = patches
|
||||
this.error = null
|
||||
}
|
||||
|
||||
internal constructor(state: UpdateStrategy.State, error: Exception?) {
|
||||
this.state = state
|
||||
this.newBuild = null
|
||||
this.updatedChannel = null
|
||||
this.patches = null
|
||||
this.error = error
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -390,10 +390,9 @@ object UpdateChecker {
|
||||
|
||||
if (updatedChannel != null && newBuild != null) {
|
||||
val runnable = {
|
||||
val patch = checkForUpdateResult.patch
|
||||
val chain = checkForUpdateResult.patchChain
|
||||
val patches = checkForUpdateResult.patches
|
||||
val forceHttps = updateSettings.canUseSecureConnection()
|
||||
UpdateInfoDialog(updatedChannel, newBuild, patch, chain, enableLink, forceHttps, updatedPlugins, incompatiblePlugins).show()
|
||||
UpdateInfoDialog(updatedChannel, newBuild, patches, enableLink, forceHttps, updatedPlugins, incompatiblePlugins).show()
|
||||
}
|
||||
|
||||
ourShownNotifications.remove(NotificationUniqueType.PLATFORM)?.forEach { it.expire() }
|
||||
@@ -549,12 +548,12 @@ object UpdateChecker {
|
||||
|
||||
val channel: UpdateChannel?
|
||||
val newBuild: BuildInfo?
|
||||
val patch: PatchInfo?
|
||||
val patches: UpdateChain?
|
||||
if (forceUpdate) {
|
||||
val node = loadElement(updateInfoText).getChild("product")?.getChild("channel") ?: throw IllegalArgumentException("//channel missing")
|
||||
channel = UpdateChannel(node)
|
||||
newBuild = channel.builds.firstOrNull() ?: throw IllegalArgumentException("//build missing")
|
||||
patch = newBuild.patches.firstOrNull()
|
||||
patches = newBuild.patches.firstOrNull()?.let { UpdateChain(listOf(it.fromBuild, newBuild.number), it.size) }
|
||||
}
|
||||
else {
|
||||
val updateInfo = UpdatesInfo(loadElement(updateInfoText))
|
||||
@@ -562,12 +561,12 @@ object UpdateChecker {
|
||||
val checkForUpdateResult = strategy.checkForUpdates()
|
||||
channel = checkForUpdateResult.updatedChannel
|
||||
newBuild = checkForUpdateResult.newBuild
|
||||
patch = checkForUpdateResult.patch
|
||||
patches = checkForUpdateResult.patches
|
||||
}
|
||||
|
||||
if (channel != null && newBuild != null) {
|
||||
val patchFile = if (patchFilePath != null) File(FileUtil.toSystemDependentName(patchFilePath)) else null
|
||||
UpdateInfoDialog(channel, newBuild, patch, patchFile).show()
|
||||
UpdateInfoDialog(channel, newBuild, patches, patchFile).show()
|
||||
}
|
||||
else {
|
||||
NoUpdatesDialog(true).show()
|
||||
|
||||
@@ -6,9 +6,6 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.BuildNumber
|
||||
import com.intellij.openapi.util.BuildRange
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.intellij.util.graph.InboundSemiGraph
|
||||
import com.intellij.util.graph.impl.ShortestPathFinder
|
||||
import org.jdom.Element
|
||||
import org.jdom.JDOMException
|
||||
import java.text.ParseException
|
||||
@@ -25,45 +22,6 @@ class Product internal constructor(node: Element) {
|
||||
val codes: Set<String> = node.getChildren("code").map { it.value.trim() }.toSet()
|
||||
val channels: List<UpdateChannel> = node.getChildren("channel").map(::UpdateChannel)
|
||||
|
||||
fun patchChain(from: BuildNumber, to: BuildNumber): ChainInfo? {
|
||||
val upgrades = MultiMap<BuildNumber, BuildNumber>()
|
||||
val sizes = mutableMapOf<Pair<BuildNumber, BuildNumber>, Int>()
|
||||
|
||||
channels.forEach { channel ->
|
||||
channel.builds.forEach { build ->
|
||||
val toBuild = build.number.withoutProductCode()
|
||||
build.patches.forEach { patch ->
|
||||
if (patch.isAvailable) {
|
||||
val fromBuild = patch.fromBuild.withoutProductCode()
|
||||
upgrades.putValue(toBuild, fromBuild)
|
||||
if (patch.size != null) {
|
||||
val maxSize = Regex("\\d+").findAll(patch.size).map { it.value.toIntOrNull() }.filterNotNull().max()
|
||||
if (maxSize != null) sizes += (fromBuild to toBuild) to maxSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val graph = object : InboundSemiGraph<BuildNumber> {
|
||||
override fun getNodes() = upgrades.keySet() + upgrades.values()
|
||||
override fun getIn(n: BuildNumber) = upgrades[n].iterator()
|
||||
}
|
||||
val path = ShortestPathFinder(graph).findPath(from.withoutProductCode(), to.withoutProductCode())
|
||||
if (path == null || path.size <= 2) return null
|
||||
|
||||
var total = 0
|
||||
for (i in 1 until path.size) {
|
||||
val size = sizes[path[i - 1] to path[i]]
|
||||
if (size == null) {
|
||||
total = -1
|
||||
break
|
||||
}
|
||||
total += size
|
||||
}
|
||||
return ChainInfo(path, total)
|
||||
}
|
||||
|
||||
override fun toString(): String = codes.firstOrNull() ?: "-"
|
||||
}
|
||||
|
||||
@@ -114,8 +72,6 @@ class BuildInfo internal constructor(node: Element) {
|
||||
val downloadUrl: String?
|
||||
get() = buttons.find(ButtonInfo::isDownload)?.url
|
||||
|
||||
fun patch(from: BuildNumber) = patches.find { it.isAvailable && it.fromBuild.compareTo(from) == 0 }
|
||||
|
||||
override fun toString(): String = "${number}/${version}"
|
||||
}
|
||||
|
||||
@@ -137,8 +93,6 @@ class PatchInfo internal constructor(node: Element) {
|
||||
val isAvailable: Boolean = node.getAttributeValue("exclusions")?.splitToSequence(",")?.none { it.trim() == OS_SUFFIX } ?: true
|
||||
}
|
||||
|
||||
class ChainInfo internal constructor(val chain: List<BuildNumber>, val size: Int)
|
||||
|
||||
private fun Element.getMandatoryAttributeValue(attribute: String) =
|
||||
getAttributeValue(attribute) ?: throw JDOMException("${name}@${attribute} missing")
|
||||
|
||||
|
||||
+13
-24
@@ -58,16 +58,14 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
private final boolean myForceHttps;
|
||||
private final Collection<PluginDownloader> myUpdatedPlugins;
|
||||
private final BuildInfo myNewBuild;
|
||||
private final PatchInfo myPatch;
|
||||
private final ChainInfo myChain;
|
||||
private final UpdateChain myPatches;
|
||||
private final boolean myWriteProtected;
|
||||
private final Pair<String, Color> myLicenseInfo;
|
||||
private final File myTestPatch;
|
||||
|
||||
UpdateInfoDialog(@NotNull UpdateChannel channel,
|
||||
@NotNull BuildInfo newBuild,
|
||||
@Nullable PatchInfo patch,
|
||||
@Nullable ChainInfo chain,
|
||||
@Nullable UpdateChain patches,
|
||||
boolean enableLink,
|
||||
boolean forceHttps,
|
||||
@Nullable Collection<PluginDownloader> updatedPlugins,
|
||||
@@ -77,9 +75,8 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
myForceHttps = forceHttps;
|
||||
myUpdatedPlugins = updatedPlugins;
|
||||
myNewBuild = newBuild;
|
||||
myPatch = patch;
|
||||
myChain = chain;
|
||||
myWriteProtected = myPatch != null && !SystemInfo.isWindows && !Files.isWritable(Paths.get(PathManager.getHomePath()));
|
||||
myPatches = patches;
|
||||
myWriteProtected = myPatches != null && !SystemInfo.isWindows && !Files.isWritable(Paths.get(PathManager.getHomePath()));
|
||||
getCancelAction().putValue(DEFAULT_ACTION, Boolean.TRUE);
|
||||
myLicenseInfo = initLicensingInfo(myUpdatedChannel, myNewBuild);
|
||||
myTestPatch = null;
|
||||
@@ -91,7 +88,7 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
}
|
||||
|
||||
FUSApplicationUsageTrigger.getInstance().trigger(IdeUpdateUsageTriggerCollector.class, "dialog.shown");
|
||||
if (myPatch == null && myChain == null) {
|
||||
if (myPatches == null) {
|
||||
FUSApplicationUsageTrigger.getInstance().trigger(IdeUpdateUsageTriggerCollector.class, "dialog.shown.no.patch");
|
||||
}
|
||||
else if (!ApplicationManager.getApplication().isRestartCapable()) {
|
||||
@@ -99,14 +96,13 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
}
|
||||
}
|
||||
|
||||
UpdateInfoDialog(UpdateChannel channel, BuildInfo newBuild, PatchInfo patch, @Nullable File patchFile) {
|
||||
UpdateInfoDialog(UpdateChannel channel, BuildInfo newBuild, UpdateChain patches, @Nullable File patchFile) {
|
||||
super(true);
|
||||
myUpdatedChannel = channel;
|
||||
myForceHttps = true;
|
||||
myUpdatedPlugins = null;
|
||||
myNewBuild = newBuild;
|
||||
myPatch = patch;
|
||||
myChain = null;
|
||||
myPatches = patches;
|
||||
myWriteProtected = false;
|
||||
myLicenseInfo = null;
|
||||
myTestPatch = patchFile;
|
||||
@@ -153,7 +149,7 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
protected Action[] createActions() {
|
||||
List<Action> actions = ContainerUtil.newArrayList();
|
||||
|
||||
if (myPatch != null || myChain != null || myTestPatch != null) {
|
||||
if (myPatches != null || myTestPatch != null) {
|
||||
boolean canRestart = ApplicationManager.getApplication().isRestartCapable();
|
||||
actions.add(new AbstractAction(IdeBundle.message(canRestart ? "updates.download.and.restart.button" : "updates.apply.manually.button")) {
|
||||
{
|
||||
@@ -170,7 +166,7 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
|
||||
List<ButtonInfo> buttons = myNewBuild.getButtons();
|
||||
for (ButtonInfo info : buttons) {
|
||||
if (!info.isDownload() || myPatch == null && myChain == null) {
|
||||
if (!info.isDownload() || myPatches == null) {
|
||||
actions.add(new ButtonAction(info));
|
||||
}
|
||||
}
|
||||
@@ -205,12 +201,8 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
String[] command;
|
||||
try {
|
||||
if (myPatch != null) {
|
||||
File file = UpdateInstaller.downloadPatchFile(myPatch, myNewBuild.getNumber(), myForceHttps, indicator);
|
||||
command = UpdateInstaller.preparePatchCommand(file, indicator);
|
||||
}
|
||||
else if (myChain != null) {
|
||||
List<File> files = UpdateInstaller.downloadPatchChain(myChain.getChain(), myForceHttps, indicator);
|
||||
if (myPatches != null) {
|
||||
List<File> files = UpdateInstaller.downloadPatchChain(myPatches.getChain(), myForceHttps, indicator);
|
||||
command = UpdateInstaller.preparePatchCommand(files, indicator);
|
||||
}
|
||||
else {
|
||||
@@ -343,11 +335,8 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
|
||||
myCurrentVersion.setText(formatVersion(appInfo.getFullVersion(), appInfo.getBuild()));
|
||||
myNewVersion.setText(formatVersion(myNewBuild.getVersion(), myNewBuild.getNumber()));
|
||||
|
||||
if (myPatch != null && !StringUtil.isEmptyOrSpaces(myPatch.getSize())) {
|
||||
myPatchInfo.setText(myPatch.getSize() + " MB");
|
||||
}
|
||||
else if (myChain != null && myChain.getSize() > 0) {
|
||||
myPatchInfo.setText(myChain.getSize() + " MB");
|
||||
if (myPatches != null && !StringUtil.isEmptyOrSpaces(myPatches.getSize())) {
|
||||
myPatchInfo.setText(myPatches.getSize() + " MB");
|
||||
}
|
||||
else {
|
||||
myPatchLabel.setVisible(false);
|
||||
|
||||
-15
@@ -27,21 +27,6 @@ object UpdateInstaller {
|
||||
private val patchesUrl: URL
|
||||
get() = URL(System.getProperty("idea.patches.url") ?: ApplicationInfoEx.getInstanceEx().updateUrls.patchesUrl)
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun downloadPatchFile(patch: PatchInfo, toBuild: BuildNumber, forceHttps: Boolean, indicator: ProgressIndicator): File {
|
||||
indicator.text = IdeBundle.message("update.downloading.patch.progress")
|
||||
val product = ApplicationInfo.getInstance().build.productCode
|
||||
val from = patch.fromBuild.withoutProductCode().asString()
|
||||
val to = toBuild.withoutProductCode().asString()
|
||||
val jdk = if (System.getProperty("idea.java.redist", "").lastIndexOf("NoJavaDistribution") >= 0) "-no-jdk" else ""
|
||||
val patchName = "${product}-${from}-${to}-patch${jdk}-${PatchInfo.OS_SUFFIX}.jar"
|
||||
val url = URL(patchesUrl, patchName).toString()
|
||||
val patchFile = File(getTempDir(), "patch.jar")
|
||||
HttpRequests.request(url).gzip(false).forceHttps(forceHttps).saveToFile(patchFile, indicator)
|
||||
return patchFile
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun downloadPatchChain(chain: List<BuildNumber>, forceHttps: Boolean, indicator: ProgressIndicator): List<File> {
|
||||
|
||||
+51
-3
@@ -3,8 +3,13 @@ package com.intellij.openapi.updateSettings.impl
|
||||
|
||||
import com.intellij.openapi.updateSettings.UpdateStrategyCustomization
|
||||
import com.intellij.openapi.util.BuildNumber
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.intellij.util.graph.InboundSemiGraph
|
||||
import com.intellij.util.graph.impl.ShortestPathFinder
|
||||
import java.util.*
|
||||
|
||||
private val NUMBER = Regex("\\d+")
|
||||
|
||||
class UpdateStrategy(private val currentBuild: BuildNumber, private val updates: UpdatesInfo, private val settings: UserUpdateSettings) {
|
||||
enum class State {
|
||||
LOADED, CONNECTION_ERROR, NOTHING_LOADED
|
||||
@@ -30,9 +35,8 @@ class UpdateStrategy(private val currentBuild: BuildNumber, private val updates:
|
||||
|
||||
val newBuild = result?.first
|
||||
val updatedChannel = result?.second
|
||||
val patch = newBuild?.patch(currentBuild)
|
||||
val chain = if (newBuild != null && patch == null) product.patchChain(currentBuild, newBuild.number) else null
|
||||
return CheckForUpdateResult(newBuild, updatedChannel, patch, chain)
|
||||
val patches = if (newBuild != null) patches(newBuild, product, currentBuild) else null
|
||||
return CheckForUpdateResult(newBuild, updatedChannel, patches)
|
||||
}
|
||||
|
||||
private fun isApplicable(candidate: BuildInfo, ignoredBuilds: Set<String>) =
|
||||
@@ -45,6 +49,50 @@ class UpdateStrategy(private val currentBuild: BuildNumber, private val updates:
|
||||
else if (n2.baselineVersion == lineage && n1.baselineVersion != lineage) -1
|
||||
else n1.compareTo(n2)
|
||||
|
||||
private fun patches(newBuild: BuildInfo, product: Product, from: BuildNumber): UpdateChain? {
|
||||
val single = newBuild.patches.find { it.isAvailable && it.fromBuild.compareTo(from) == 0 }
|
||||
if (single != null) {
|
||||
return UpdateChain(listOf(from, newBuild.number), single.size)
|
||||
}
|
||||
|
||||
val upgrades = MultiMap<BuildNumber, BuildNumber>()
|
||||
val sizes = mutableMapOf<Pair<BuildNumber, BuildNumber>, Int>()
|
||||
|
||||
product.channels.forEach { channel ->
|
||||
channel.builds.forEach { build ->
|
||||
val toBuild = build.number.withoutProductCode()
|
||||
build.patches.forEach { patch ->
|
||||
if (patch.isAvailable) {
|
||||
val fromBuild = patch.fromBuild.withoutProductCode()
|
||||
upgrades.putValue(toBuild, fromBuild)
|
||||
if (patch.size != null) {
|
||||
val maxSize = NUMBER.findAll(patch.size).map { it.value.toIntOrNull() }.filterNotNull().max()
|
||||
if (maxSize != null) sizes += (fromBuild to toBuild) to maxSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val graph = object : InboundSemiGraph<BuildNumber> {
|
||||
override fun getNodes() = upgrades.keySet() + upgrades.values()
|
||||
override fun getIn(n: BuildNumber) = upgrades[n].iterator()
|
||||
}
|
||||
val path = ShortestPathFinder(graph).findPath(from.withoutProductCode(), newBuild.number.withoutProductCode())
|
||||
if (path == null || path.size <= 2) return null
|
||||
|
||||
var total = 0
|
||||
for (i in 1 until path.size) {
|
||||
val size = sizes[path[i - 1] to path[i]]
|
||||
if (size == null) {
|
||||
total = -1
|
||||
break
|
||||
}
|
||||
total += size
|
||||
}
|
||||
return UpdateChain(path, if (total > 0) total.toString() else null)
|
||||
}
|
||||
|
||||
//<editor-fold desc="Deprecated stuff.">
|
||||
@Deprecated("use {@link #UpdateStrategy(BuildNumber, UpdatesInfo, UserUpdateSettings)}")
|
||||
constructor(@Suppress("UNUSED_PARAMETER") majorVersion: Int,
|
||||
|
||||
@@ -37,8 +37,8 @@ class UpdateStrategyTest : BareTestFixtureTestCase() {
|
||||
<patch from="145.258" exclusions="win,mac,unix"/>
|
||||
</build>
|
||||
</channel>"""
|
||||
assertNotNull(check("IU-145.596", ChannelStatus.RELEASE, channels).patch)
|
||||
assertNull(check("IU-145.258", ChannelStatus.RELEASE, channels).patch)
|
||||
assertNotNull(check("IU-145.596", ChannelStatus.RELEASE, channels).patches)
|
||||
assertNull(check("IU-145.258", ChannelStatus.RELEASE, channels).patches)
|
||||
}
|
||||
|
||||
@Test fun `order of builds does not matter`() {
|
||||
@@ -227,9 +227,8 @@ class UpdateStrategyTest : BareTestFixtureTestCase() {
|
||||
</build>
|
||||
</channel>""")
|
||||
assertBuild("182.3684.40", result.newBuild)
|
||||
assertThat(result.patch).isNull()
|
||||
assertThat(result.patchChain?.chain).isEqualTo(listOf("182.3569.1", "182.3684.2", "182.3684.40").map(BuildNumber::fromString))
|
||||
assertThat(result.patchChain?.size).isEqualTo(10)
|
||||
assertThat(result.patches?.chain).isEqualTo(listOf("182.3569.1", "182.3684.2", "182.3684.40").map(BuildNumber::fromString))
|
||||
assertThat(result.patches?.size).isEqualTo("10")
|
||||
}
|
||||
|
||||
@Test fun `building patch chain across channels`() {
|
||||
@@ -250,9 +249,8 @@ class UpdateStrategyTest : BareTestFixtureTestCase() {
|
||||
</build>
|
||||
</channel>""")
|
||||
assertBuild("182.3911.2", result.newBuild)
|
||||
assertThat(result.patch).isNull()
|
||||
assertThat(result.patchChain?.chain).isEqualTo(listOf("182.3684.40", "182.3684.41", "182.3911.2").map(BuildNumber::fromString))
|
||||
assertThat(result.patchChain?.size).isEqualTo(-1)
|
||||
assertThat(result.patches?.chain).isEqualTo(listOf("182.3684.40", "182.3684.41", "182.3911.2").map(BuildNumber::fromString))
|
||||
assertThat(result.patches?.size).isNull()
|
||||
}
|
||||
|
||||
private fun check(currentBuild: String,
|
||||
|
||||
Reference in New Issue
Block a user