Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subversion fixes #8572

Merged
merged 6 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion downloader/src/main/kotlin/VersionControlSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ abstract class VersionControlSystem(
}

val workingTreeRevision = results.last().getOrElse {
throw DownloadException("$type failed to download from URL ${pkg.vcsProcessed.url}.", it)
throw DownloadException(
"$type failed to download from ${pkg.vcsProcessed.url} to '${workingTree.workingDir}'.", it
)
}

pkg.vcsProcessed.path.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.utils.common.CommandLineTool
import org.ossreviewtoolkit.utils.common.ProcessCapture
import org.ossreviewtoolkit.utils.common.collectMessages
import org.ossreviewtoolkit.utils.ort.showStackTrace

const val MERCURIAL_LARGE_FILES_EXTENSION = "largefiles = "
const val MERCURIAL_SPARSE_EXTENSION = "sparse = "
Expand Down Expand Up @@ -104,10 +102,6 @@ class Mercurial : VersionControlSystem(MercurialCommand) {

// Explicitly update the working tree to the desired revision.
MercurialCommand.run(workingTree.workingDir, "update", revision).isSuccess
}.onFailure {
it.showStackTrace()

logger.warn { "Failed to update $type working tree to revision '$revision': ${it.collectMessages()}" }
}.map {
revision
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,81 +103,70 @@ class Subversion : VersionControlSystem() {
return getWorkingTree(targetDir)
}

private fun updateEmptyPath(workingTree: WorkingTree, revision: SVNRevision, path: String) {
val pathIterator = Paths.get(path).iterator()
private fun deepenWorkingTreePath(workingTree: WorkingTree, path: String, revision: SVNRevision): Long {
val finalPath = workingTree.workingDir.resolve(path)
var currentPath = workingTree.workingDir

// Avoid the "None of the targets are working copies" error by deepening one path at a time.
val pathIterator = Paths.get(path).iterator()
val pathRevisions = mutableSetOf<Long>()

while (pathIterator.hasNext()) {
clientManager.updateClient.doUpdate(
currentPath = currentPath.resolve(pathIterator.next().toFile())

pathRevisions += clientManager.updateClient.doUpdate(
currentPath,
revision,
SVNDepth.EMPTY,
if (currentPath != finalPath) SVNDepth.EMPTY else SVNDepth.INFINITY,
/* allowUnversionedObstructions = */ false,
/* depthIsSticky = */ true
)

currentPath = currentPath.resolve(pathIterator.next().toFile())
}

return pathRevisions.single()
}

override fun updateWorkingTree(workingTree: WorkingTree, revision: String, path: String, recursive: Boolean) =
runCatching {
revision.toLongOrNull()?.let { numericRevision ->
// This code path updates the working tree to a numeric revision.
val svnRevision = SVNRevision.create(numericRevision)

// First update the (empty) working tree to the desired revision along the requested path.
updateEmptyPath(workingTree, svnRevision, path)

// Then deepen only the requested path in the desired revision.
clientManager.updateClient.apply { isIgnoreExternals = !recursive }.doUpdate(
workingTree.workingDir.resolve(path),
svnRevision,
SVNDepth.INFINITY,
/* allowUnversionedObstructions = */ false,
/* depthIsSticky = */ true
)
// Note that the path should never be part of the URL as that would root the working tree at that path, but
// the path should be available in the working tree.
val (svnUrl, svnRevision) = revision.toLongOrNull()?.let { numericRevision ->
val url = workingTree.getRemoteUrl()

SVNURL.parseURIEncoded(url) to SVNRevision.create(numericRevision)
} ?: run {
// This code path updates the working tree to a symbolic revision.
val svnUrl = SVNURL.parseURIEncoded(
"${workingTree.getRemoteUrl()}/$revision"
)
val url = listOf(workingTree.getRemoteUrl(), revision).joinToString("/")

// First switch the (empty) working tree to the requested branch / tag.
clientManager.updateClient.doSwitch(
workingTree.workingDir,
svnUrl,
SVNRevision.HEAD,
SVNRevision.HEAD,
SVNDepth.EMPTY,
/* allowUnversionedObstructions = */ false,
/* depthIsSticky = */ true,
/* ignoreAncestry = */ true
)
SVNURL.parseURIEncoded(url) to SVNRevision.HEAD
}

// Then update the working tree in the current revision along the requested path, and ...
updateEmptyPath(workingTree, SVNRevision.HEAD, path)
clientManager.updateClient.isIgnoreExternals = !recursive

// Finally, deepen only the requested path in the current revision.
clientManager.updateClient.apply { isIgnoreExternals = !recursive }.doUpdate(
workingTree.workingDir.resolve(path),
SVNRevision.HEAD,
SVNDepth.INFINITY,
/* allowUnversionedObstructions = */ false,
/* depthIsSticky = */ true
)
logger.info {
val printableRevision = svnRevision.name ?: svnRevision.number
"Switching $type '${workingTree.workingDir}' to $svnUrl at revision $printableRevision."
}

true
}.onFailure {
it.showStackTrace()
// For "peg revision" vs. "revision" see https://svnbook.red-bean.com/en/1.7/svn.advanced.pegrevs.html.
val workingTreeRevision = clientManager.updateClient.doSwitch(
workingTree.workingDir,
svnUrl,
/* pegRevision = */ SVNRevision.HEAD,
/* revision = */ svnRevision,
if (path.isEmpty()) SVNDepth.INFINITY else SVNDepth.EMPTY,
/* allowUnversionedObstructions = */ false,
/* depthIsSticky = */ true
)

logger.warn {
"Failed to update the $type working tree at '${workingTree.workingDir}' to revision '$revision':\n" +
it.collectMessages()
logger.info { "$type working tree '${workingTree.workingDir}' is at revision $workingTreeRevision." }

if (path.isNotEmpty()) {
logger.info { "Deepening path '$path' in $type working tree '${workingTree.workingDir}'." }
val pathRevision = deepenWorkingTreePath(workingTree, path, svnRevision)
check(pathRevision == workingTreeRevision)
}
}.map {
revision

workingTreeRevision.toString()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.ossreviewtoolkit.model.VcsInfo
import org.ossreviewtoolkit.model.VcsType
import org.ossreviewtoolkit.scanner.utils.DefaultWorkingTreeCache
import org.ossreviewtoolkit.utils.common.Os
import org.ossreviewtoolkit.utils.test.ExpensiveTag

class DefaultNestedProvenanceResolverFunTest : WordSpec() {
private val workingTreeCache = DefaultWorkingTreeCache()
Expand Down Expand Up @@ -204,7 +205,7 @@ class DefaultNestedProvenanceResolverFunTest : WordSpec() {
}
}

"work for Subversion tags".config(enabled = false /* This needs fixing, see ORT issue 6160. */) {
"work for Subversion tags".config(tags = setOf(ExpensiveTag)) {
val provenance = RepositoryProvenance(
vcsInfo = VcsInfo(
type = VcsType.SUBVERSION,
Expand Down
Loading