Skip to content

Commit

Permalink
Resolve #86: Add SVN support
Browse files Browse the repository at this point in the history
  • Loading branch information
pnowakowski committed Dec 6, 2017
1 parent 4e0886c commit 7f105ab
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 0 deletions.
2 changes: 2 additions & 0 deletions downloader/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ dependencies {
compile project(':util')

compile "com.beust:jcommander:$jcommanderVersion"
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jacksonVersion"
compile "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion"

testCompile "io.kotlintest:kotlintest:$kotlintestVersion"

Expand Down
84 changes: 84 additions & 0 deletions downloader/src/funTest/kotlin/SubversionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2017 HERE Europe B.V.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package com.here.ort.downloader.vcs

import com.here.ort.downloader.Expensive
import io.kotlintest.TestCaseContext
import io.kotlintest.matchers.beGreaterThan
import io.kotlintest.matchers.should
import io.kotlintest.matchers.shouldBe

import io.kotlintest.matchers.shouldNotBe
import io.kotlintest.specs.StringSpec
import java.io.File

private const val REPO_URL = "https://github.com/square/retrofit.git"

class SubversionTest : StringSpec() {
private lateinit var outputDir: File

// Required to make lateinit of outputDir work.
// override val oneInstancePerTest = false

override fun interceptTestCase(context: TestCaseContext, test: () -> Unit) {
outputDir = createTempDir()
super.interceptTestCase(context, test)
outputDir.deleteRecursively()
}

init {
"Detected Subversion version is not empty" {
val version = Subversion.getVersion()
version shouldNotBe ""
}

"Subversion can download single revision" {
val revision = "1000"
val downloadedRev = Subversion.download(REPO_URL, revision, null, "", outputDir)
downloadedRev shouldBe revision
}.config(tags = setOf(Expensive))

"Subversion can download sub path" {
val subdir = "retrofit-converters"
val notCheckoutSubDir = "retrofit-adapters"
Subversion.download(REPO_URL, null, subdir, "", outputDir)

val outputDirList = Subversion.getWorkingDirectory(outputDir).workingDir.list()
outputDirList.indexOf(subdir) should beGreaterThan(-1)
outputDirList.indexOf(notCheckoutSubDir) shouldBe -1
}.config(tags = setOf(Expensive))

"Subversion can download version" {
val version = "2.0.0"
val revisionForVersion = "1512"
val downloadedRev = Subversion.download(REPO_URL, null, null, version, outputDir)
downloadedRev shouldBe revisionForVersion
}.config(tags = setOf(Expensive))

"Subversion can download entire repo" {
Subversion.download(REPO_URL, null, null, "", outputDir)
val outputDirList = Subversion.getWorkingDirectory(outputDir).workingDir.list()
outputDirList.indexOf("trunk") should beGreaterThan(-1)
outputDirList.indexOf("tags") should beGreaterThan(-1)
outputDirList.indexOf("branches") should beGreaterThan(-1)
}.config(tags = setOf(Expensive))

}
}
139 changes: 139 additions & 0 deletions downloader/src/main/kotlin/vcs/Subversion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2017 HERE Europe B.V.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package com.here.ort.downloader.vcs

import ch.frankel.slf4k.*

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.xml.XmlFactory
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

import com.here.ort.downloader.Main
import com.here.ort.downloader.VersionControlSystem
import com.here.ort.util.ProcessCapture
import com.here.ort.util.getCommandVersion
import com.here.ort.util.log

import java.io.File
import java.io.IOException

data class SubversionLogEntry(
@JacksonXmlProperty(isAttribute = true)
val revision: String,
@JacksonXmlProperty
val msg: String,
@JacksonXmlProperty
val date: String,
@JacksonXmlProperty
val author: String) {
}

object Subversion : VersionControlSystem() {
override fun getVersion(): String {
val subversionVersionRegex = Regex("svn, version (?<version>[\\d.]+) \\(.+\\)")

return getCommandVersion("svn") {
subversionVersionRegex.matchEntire(it.lineSequence().first())?.groups?.get("version")?.value ?: ""
}
}

override fun getWorkingDirectory(vcsDirectory: File) =
object : WorkingDirectory(vcsDirectory) {

val infoCommandResult = ProcessCapture("svn", "info", workingDir.absolutePath)

override fun isValid() = infoCommandResult.exitValue() == 0;

override fun getRemoteUrl() = infoCommandResult.stdout().lineSequence()
.first { it.startsWith("URL:") }.removePrefix("URL:").trim()

override fun getRevision() = getLineValue("Revision: ")

override fun getRootPath(path: File) = getLineValue("Working Copy Root Path:")

override fun getPathToRoot(path: File): String
= getLineValue("Path:").substringAfter(File.separatorChar)

private fun getLineValue(linePrefix: String) =
infoCommandResult.requireSuccess().stdout().lineSequence().first { it.startsWith(linePrefix) }
.removePrefix(linePrefix).trim()
}

override fun isApplicableProvider(vcsProvider: String) = vcsProvider.toLowerCase() in listOf("subversion", "svn")

override fun isApplicableUrl(vcsUrl: String) = ProcessCapture("svn", "ls", vcsUrl).exitValue() == 0

override fun download(vcsUrl: String, vcsRevision: String?, vcsPath: String?, version: String,
targetDir: File): String {

runSvnCommand(targetDir, "co", vcsUrl, "--depth", "empty", ".")

val revision = if (vcsRevision != null && vcsRevision.isNotBlank()) {
vcsRevision
} else if (version.isNotBlank()) {
try {
log.info { "Trying to determine revision for version: $version" }
val tagsList = runSvnCommand(targetDir, "list", "$vcsUrl/tags").stdout().trim().lineSequence()
val tagName = tagsList.firstOrNull {
val trimmedTag = it.trimEnd('/')
trimmedTag.endsWith(version)
|| trimmedTag.endsWith(version.replace('.', '_'))
}

val xml = runSvnCommand(targetDir,
"log",
"$vcsUrl/tags/$tagName",
"--xml").stdout().trim()
val xmlMapper = ObjectMapper(XmlFactory()).registerKotlinModule()
val logEntries: List<SubversionLogEntry> = xmlMapper.readValue(xml,
xmlMapper.typeFactory.constructCollectionType(List::class.java, SubversionLogEntry::class.java))
logEntries.firstOrNull()?.revision ?: ""

} catch (e: IOException) {
if (Main.stacktrace) {
e.printStackTrace()
}

log.warn { "Could not determine revision for version: $version. Falling back to fetching everything." }
""
}

} else {
""
}


if (vcsPath != null && vcsPath.isNotBlank()) {
targetDir.resolve(vcsPath).mkdirs()
}

if (revision.isNotBlank()) {
runSvnCommand(targetDir, "up", "-r", revision, "--set-depth", "infinity", vcsPath ?: "")
} else {
runSvnCommand(targetDir, "up", "--set-depth", "infinity", vcsPath?.apply { } ?: "")
}

return Subversion.getWorkingDirectory(targetDir).getRevision()
}

private fun runSvnCommand(workingDir: File, vararg args: String) = ProcessCapture(workingDir, "svn",
*args).requireSuccess()
}

0 comments on commit 7f105ab

Please sign in to comment.