Skip to content

Commit

Permalink
Merge pull request #130 from yodamad/dev
Browse files Browse the repository at this point in the history
🎥  Release 2.2
  • Loading branch information
yodamad authored Jun 3, 2021
2 parents f4e344d + 33e86b1 commit 1d8033d
Show file tree
Hide file tree
Showing 22 changed files with 566 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Upgrade platform
run: sudo apt-get upgrade
- name: Install git-svn
run: sudo apt-get install --yes git git-svn
run: sudo apt-get install --yes git git-svn expect
- name: 🔍 Analyze code with SonarQube
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
JAVA_OPTS=""

RUN apt update && \
apt install -y git git-svn subversion
apt install -y git git-svn subversion expect

COPY target/svn2git.jar /usr/svn2git/

Expand Down
9 changes: 8 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>fr.yodamad.svn2git</groupId>
<artifactId>svn-2-git</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
<packaging>jar</packaging>
<name>Svn 2 GitLab</name>

Expand Down Expand Up @@ -75,6 +75,7 @@
<sonar-maven-plugin.version>3.5.0.1254</sonar-maven-plugin.version>
<git-commit-id-plugin.version>2.2.5</git-commit-id-plugin.version>
<jasypt.version>3.0.3</jasypt.version>
<handlebars.version>4.2.0</handlebars.version>

<!-- Sonar properties -->
<sonar.projectKey>yodamad_svn2git</sonar.projectKey>
Expand Down Expand Up @@ -425,6 +426,12 @@
<version>${kotlin.version}</version>
</dependency>

<!-- Templating -->
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>${handlebars.version}</version>
</dependency>

<!-- Jolokia for Glowroot -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fun exceedsMaxSize(workUnit: WorkUnit, path: Path): Boolean {
*/
fun getListFromCommaSeparatedString(commaSeparatedStr: String?): List<String>? {
return if (StringUtils.isNotBlank(commaSeparatedStr)) {
commaSeparatedStr?.split("\\s*,\\s*")?.toTypedArray()?.toList()
commaSeparatedStr?.split(",")
} else {
ArrayList()
}
Expand Down
11 changes: 7 additions & 4 deletions src/main/kotlin/fr/yodamad/svn2git/functions/GitFunctions.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package fr.yodamad.svn2git.functions

import fr.yodamad.svn2git.data.WorkUnit
import fr.yodamad.svn2git.io.Shell
import fr.yodamad.svn2git.service.GitManager
import fr.yodamad.svn2git.service.util.MASTER
import fr.yodamad.svn2git.service.util.ORIGIN_TAGS
import fr.yodamad.svn2git.io.Shell
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.util.*
import java.util.stream.Collectors


Expand Down Expand Up @@ -215,6 +214,10 @@ fun buildTrunk(workUnit: WorkUnit): String? {
return if (mig.flat) {
if (mig.svnGroup == mig.svnProject) {
"--trunk=/"
} else String.format("--trunk=%s/", workUnit.migration.svnProject)
} else String.format("--trunk=%s/trunk", workUnit.migration.svnProject)
} else String.format("--trunk=%s/", workUnit.migration.svnProject.encode())
} else String.format("--trunk=%s/trunk", workUnit.migration.svnProject.encode())
}

fun buildSvnCompleteUrl(workUnit: WorkUnit) =
if (workUnit.migration.svnUrl.endsWith("/")) "${workUnit.migration.svnUrl}${workUnit.migration.svnGroup}"
else "${workUnit.migration.svnUrl}/${workUnit.migration.svnGroup}"
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package fr.yodamad.svn2git.functions

import fr.yodamad.svn2git.io.Shell.isWindows
import org.apache.commons.lang3.StringUtils
import org.springframework.web.util.UriUtils.decode
import org.springframework.web.util.UriUtils.encode

val EMPTY = ""

Expand All @@ -11,3 +13,7 @@ fun formattedOrEmpty(element: String?, container: String, windowsCase: String? =
windowsCase != null && isWindows -> String.format(container, element)
else -> String.format(container, element)
}

fun String.encode(): String = encode(this, "UTF-8")
fun String.decode(): String = decode(this, "UTF-8")
fun String.gitFormat(): String = this.decode().replace(" ", "_")
4 changes: 4 additions & 0 deletions src/main/kotlin/fr/yodamad/svn2git/init/CheckUp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ class CheckUp {
private val SVN_VERSION = "svn"
private val SVN_ERROR = "⛔️ svn2git requires 'svn' v1+"

private val EXPECT_VERSION = "expect"
private val EXPECT_ERROR = "⛔️ expect binary is required on Linux. 👉 Run apt-get|yum install expect."

val isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows")

@PostConstruct
fun atStartup() {
var allGood = checkGitSvnClone()
allGood = allGood && checkCommand("git svn --version", GIT_SVN_VERSION, GIT_SVN_ERROR)
allGood = allGood && checkCommand("svn --version", SVN_VERSION, SVN_ERROR)
if (!isWindows) allGood = allGood && checkCommand("expect -version", EXPECT_VERSION, EXPECT_ERROR)
if (!allGood) exitProcess(1)
}

Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/fr/yodamad/svn2git/io/Shell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ object Shell {
*/
@JvmOverloads
@Throws(InterruptedException::class, IOException::class)
fun execCommand(commandManager: CommandManager, directory: String, command: String?, securedCommandToPrint: String? = command): Int {
fun execCommand(commandManager: CommandManager, directory: String, command: String?, securedCommandToPrint: String? = command, usePowershell: Boolean = false): Int {
val builder = ProcessBuilder()
val execDir = formatDirectory(directory)
if (isWindows) {
builder.command("cmd.exe", "/c", command)
if (usePowershell) builder.command("powershell.exe", "-File", command)
else builder.command("cmd.exe", "/c", command)
} else {
builder.command("sh", "-c", command)
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/fr/yodamad/svn2git/service/Cleaner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ open class Cleaner(val historyMgr: HistoryManager,
execCommand(workUnit.commandManager, workUnit.directory, svnBranchList)
var elementsToKeep = Files.readAllLines(Paths.get(workUnit.directory, SVN_LIST))
.stream()
.map { l: String -> l.trim { it <= ' ' }.replace("/", "") }
.map { l: String -> l.trim { it <= ' ' }.replace("/", "").encode() }
.collect(Collectors.toList())

// ######### Switch elementsToKeep if necessary ##################################
Expand Down
42 changes: 29 additions & 13 deletions src/main/kotlin/fr/yodamad/svn2git/service/GitManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fr.yodamad.svn2git.domain.MigrationHistory
import fr.yodamad.svn2git.domain.enumeration.StatusEnum
import fr.yodamad.svn2git.domain.enumeration.StepEnum
import fr.yodamad.svn2git.io.Shell.execCommand
import fr.yodamad.svn2git.io.Shell.isWindows
import fr.yodamad.svn2git.repository.MappingRepository
import fr.yodamad.svn2git.service.util.*
import net.logstash.logback.encoder.org.apache.commons.lang.StringEscapeUtils
Expand Down Expand Up @@ -79,36 +80,51 @@ open class GitManager(val historyMgr: HistoryManager,
*/
@Throws(IOException::class, InterruptedException::class)
open fun gitSvnClone(workUnit: WorkUnit) {
val cloneCommand: String
var cloneCommand: String
val safeCommand: String
if (!isEmpty(workUnit.migration.svnPassword)) {
val escapedPassword = StringEscapeUtils.escapeJava(workUnit.migration.svnPassword)
cloneCommand = gitCommandManager.initCommand(workUnit, workUnit.migration.svnUser, escapedPassword)
safeCommand = gitCommandManager.initCommand(workUnit, workUnit.migration.svnUser, STARS)
} else if (!isEmpty(applicationProperties.svn.password)) {
val escapedPassword = StringEscapeUtils.escapeJava(applicationProperties.svn.password)
cloneCommand = gitCommandManager.initCommand(workUnit, applicationProperties.svn.user, escapedPassword)
safeCommand = gitCommandManager.initCommand(workUnit, applicationProperties.svn.user, STARS)
if (!isWindows) {
if (!isEmpty(workUnit.migration.svnPassword)) {
val escapedPassword = StringEscapeUtils.escapeJava(workUnit.migration.svnPassword)
cloneCommand = gitCommandManager.initCommand(workUnit, workUnit.migration.svnUser, escapedPassword)
safeCommand = gitCommandManager.initCommand(workUnit, workUnit.migration.svnUser, STARS)
} else if (!isEmpty(applicationProperties.svn.password)) {
val escapedPassword = StringEscapeUtils.escapeJava(applicationProperties.svn.password)
cloneCommand = gitCommandManager.initCommand(workUnit, applicationProperties.svn.user, escapedPassword)
safeCommand = gitCommandManager.initCommand(workUnit, applicationProperties.svn.user, STARS)
} else {
cloneCommand = gitCommandManager.initCommand(workUnit, null, null)
safeCommand = cloneCommand
}

// Waiting for Windows support...
cloneCommand = gitCommandManager.generateGitSvnCloneScript(workUnit, cloneCommand)
} else {
cloneCommand = gitCommandManager.initCommand(workUnit, null, null)
val commandOptions = gitCommandManager.initOptions(workUnit)
gitCommandManager.generateGitSvnClonePackageForWindows(workUnit, commandOptions)
cloneCommand = "${workUnit.directory}\\git-command.ps1"
safeCommand = cloneCommand
}

val history = historyMgr.startStep(workUnit.migration, StepEnum.SVN_CHECKOUT,
(if (workUnit.commandManager.isFirstAttemptMigration) "" else Constants.REEXECUTION_SKIPPING) + safeCommand)
// Only Clone if first attempt at migration
var cloneOK = true
if (workUnit.commandManager.isFirstAttemptMigration) {
try {
execCommand(workUnit.commandManager, workUnit.root, cloneCommand, safeCommand)
execCommand(workUnit.commandManager, workUnit.root, cloneCommand, safeCommand, true)
} catch (thr: Throwable) {
LOG.warn("Cannot git svn clone", thr.message)
cloneOK = false
LOG.warn("Cannot git svn clone", thr.message)
var round = 0
var notOk = true
while (round++ < applicationProperties.svn.maxFetchAttempts && notOk) {
notOk = gitSvnFetch(workUnit, round)
gitGC(workUnit, round)
}
if (notOk) {
historyMgr.endStep(history, StatusEnum.FAILED, null)
throw RuntimeException()
}
}
}
if (cloneOK) {
Expand All @@ -131,7 +147,7 @@ open class GitManager(val historyMgr: HistoryManager,
open fun gitSvnFetch(workUnit: WorkUnit, round: Int) : Boolean {
val fetchCommand = "git svn fetch";

val history = historyMgr.startStep(workUnit.migration, StepEnum.SVN_FETCH, "Round $round : $fetchCommand")
val history = historyMgr.startStep(workUnit.migration, StepEnum.SVN_FETCH, "$fetchCommand (Round $round)")
return try {
execCommand(workUnit.commandManager, workUnit.directory, fetchCommand)
historyMgr.endStep(history, StatusEnum.DONE, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package fr.yodamad.svn2git.service.util
import fr.yodamad.svn2git.data.WorkUnit
import fr.yodamad.svn2git.domain.enumeration.StatusEnum
import fr.yodamad.svn2git.domain.enumeration.StepEnum
import fr.yodamad.svn2git.functions.decode
import fr.yodamad.svn2git.functions.gitFormat
import fr.yodamad.svn2git.functions.listBranchesOnly
import fr.yodamad.svn2git.io.Shell.execCommand
import fr.yodamad.svn2git.service.GitManager
Expand Down Expand Up @@ -30,12 +32,18 @@ open class GitBranchManager(val gitManager: GitManager,
@Throws(RuntimeException::class)
open fun pushBranch(workUnit: WorkUnit, branch: String): Boolean {
var branchName = branch.replaceFirst("refs/remotes/origin/".toRegex(), "")
branchName = branchName.replaceFirst("origin/".toRegex(), "")
// Spaces aren't permitted, so replaced them with an underscore
branchName = branchName.replaceFirst("origin/".toRegex(), "").gitFormat()
LOG.debug("Branch %s $branchName")
val history = historyMgr.startStep(workUnit.migration, StepEnum.GIT_PUSH, branchName)

if (workUnit.migration.trunk != null && workUnit.migration.trunk != "trunk" && workUnit.migration.trunk.equals(branch.decode())) {
// Don't push branch that is used as new master
return true;
}

try {
execCommand(workUnit.commandManager, workUnit.directory, "git checkout -b $branchName $branch")
execCommand(workUnit.commandManager, workUnit.directory, "git checkout -b \"$branchName\" $branch")
} catch (iEx: IOException) {
LOG.error(FAILED_TO_PUSH_BRANCH, iEx)
historyMgr.endStep(history, StatusEnum.FAILED, iEx.message)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package fr.yodamad.svn2git.service.util

import com.github.jknack.handlebars.Handlebars
import fr.yodamad.svn2git.config.ApplicationProperties
import fr.yodamad.svn2git.data.WorkUnit
import fr.yodamad.svn2git.domain.enumeration.StatusEnum
import fr.yodamad.svn2git.domain.enumeration.StepEnum
import fr.yodamad.svn2git.functions.*
import fr.yodamad.svn2git.io.Shell
import fr.yodamad.svn2git.io.Shell.isWindows
import fr.yodamad.svn2git.service.HistoryManager
import fr.yodamad.svn2git.service.MappingManager
import org.apache.commons.lang3.StringUtils.isEmpty
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.io.File
import java.io.IOException
import java.io.StringWriter
import java.net.URI
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermission.*

@Service
open class GitCommandManager(val historyMgr: HistoryManager,
Expand All @@ -30,30 +36,68 @@ open class GitCommandManager(val historyMgr: HistoryManager,
* @return
*/
open fun initCommand(workUnit: WorkUnit, username: String?, secret: String?): String {
val cloneCommand = String.format("git svn clone %s %s %s",
formattedOrEmpty(username, "--username %s"),
initOptions(workUnit),
buildSvnCompleteUrl(workUnit))

// Get list of svnDirectoryDelete
val svnDirectoryDeleteList: List<String> = mappingMgr.getSvnDirectoryDeleteList(workUnit.migration.id)
// Initialise ignorePaths string that will be passed to git svn clone
val ignorePaths: String = generateIgnorePaths(workUnit.migration.trunk, workUnit.migration.tags, workUnit.migration.branches, workUnit.migration.svnProject, svnDirectoryDeleteList)

// regex with negative look forward allows us to choose the branch and tag names to keep
val ignoreRefs: String = generateIgnoreRefs(workUnit.migration.branchesToMigrate, workUnit.migration.tagsToMigrate)
// replace any multiple whitespaces and return
return cloneCommand.replace("\\s{2,}".toRegex(), " ").trim { it <= ' ' }
}

val cloneCommand = String.format("%s git svn clone %s %s %s %s %s %s %s %s %s%s",
formattedOrEmpty(secret, "echo %s |", "echo(%s|"),
formattedOrEmpty(username, "--username %s"),
open fun initOptions(workUnit: WorkUnit) : String {
val svnDirectoryDeleteList: List<String> = mappingMgr.getSvnDirectoryDeleteList(workUnit.migration.id)
return String.format("%s %s %s %s %s %s",
formattedOrEmpty(workUnit.migration.svnRevision, "-r%s:HEAD"),
setTrunk(workUnit),
setSvnElement("branches", workUnit.migration.branches, workUnit),
setSvnElement("tags", workUnit.migration.tags, workUnit),
ignorePaths, ignoreRefs,
generateIgnorePaths(workUnit.migration.trunk, workUnit.migration.tags, workUnit.migration.branches, workUnit.migration.svnProject, svnDirectoryDeleteList),
if (workUnit.migration.emptyDirs) "--preserve-empty-dirs"
else if (workUnit.migration.emptyDirs == null && applicationProperties.getFlags().isGitSvnClonePreserveEmptyDirsOption) "--preserve-empty-dirs" else EMPTY,
if (workUnit.migration.svnUrl.endsWith("/")) workUnit.migration.svnUrl else "${workUnit.migration.svnUrl}/",
workUnit.migration.svnGroup)
)
}

open fun generateGitSvnCloneScript(workUnit: WorkUnit, gitSvnCloneCommand: String): String {

val scriptInfo = ScriptInfo(gitSvnCloneCommand, workUnit.migration.svnUser, workUnit.migration.svnPassword, "${workUnit.directory}")

val handlebars = Handlebars()
val template = handlebars.compile("templates/scripts/git-svn-clone.sh")

val fileToWrite = File("${workUnit.directory}/git-svn-clone.sh")
val writer = StringWriter()
template.apply(scriptInfo, writer)
fileToWrite.writeText(writer.toString())

if (!isWindows) {
Files.setPosixFilePermissions(
fileToWrite.toPath(),
setOf(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, OTHERS_READ)
)
}
return fileToWrite.path
}

open fun generateGitSvnClonePackageForWindows(workUnit: WorkUnit, cloneOptions: String?) {

val scriptInfo = ScriptInfo("", workUnit.migration.svnUser, workUnit.migration.svnPassword,
"${workUnit.directory}", buildSvnCompleteUrl(workUnit), cloneOptions)

val handlebars = Handlebars()
var template = handlebars.compile("templates/scripts/win/git-command.ps1")

var fileToWrite = File("${workUnit.directory}/git-command.ps1")
var writer = StringWriter()
template.apply(scriptInfo, writer)
fileToWrite.writeText(writer.toString())

template = handlebars.compile("templates/scripts/win/git-svn-clone.ps1")
fileToWrite = File("${workUnit.directory}/git-svn-clone.ps1")
writer = StringWriter()
template.apply(null, writer)
fileToWrite.writeText(writer.toString())

// replace any multiple whitespaces and return
return cloneCommand.replace("\\s{2,}".toRegex(), " ").trim { it <= ' ' }
}

/**
Expand Down Expand Up @@ -140,3 +184,8 @@ open class GitCommandManager(val historyMgr: HistoryManager,
else -> workUnit.migration.gitlabToken
}
}

/**
* Info to inject in generated script
*/
data class ScriptInfo(val svnCommand: String, val svnUser: String, val svnPassword: String, val workingDir: String, val svnUrl: String? = "", val cloneOptions: String? = "")
Loading

0 comments on commit 1d8033d

Please sign in to comment.