io.specmatic.core.git.SystemGit.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of specmatic-core Show documentation
Show all versions of specmatic-core Show documentation
Turn your contracts into executable specifications. Contract Driven Development - Collaboratively Design & Independently Deploy MicroServices & MicroFrontends.
package io.specmatic.core.git
import io.specmatic.core.Configuration
import io.specmatic.core.azure.AuthCredentials
import io.specmatic.core.azure.NoGitAuthCredentials
import io.specmatic.core.log.logger
import io.specmatic.core.pattern.ContractException
import io.specmatic.core.utilities.ExternalCommand
import io.specmatic.core.utilities.exceptionCauseMessage
import java.io.File
class SystemGit(override val workingDirectory: String = ".", private val prefix: String = "- ", val authCredentials: AuthCredentials = NoGitAuthCredentials) : GitCommand {
private fun executeWithAuth(vararg command: String): String {
val gitExecutable = listOf(Configuration.gitCommand)
val auth = authCredentials.gitCommandAuthHeaders()
return execute(gitExecutable + auth + command.toList())
}
private fun execute(command: List): String =
executeCommandWithWorkingDirectory(prefix, workingDirectory, command.toList().toTypedArray())
private fun execute(vararg command: String): String =
executeCommandWithWorkingDirectory(prefix, workingDirectory, command.toList().toTypedArray())
private fun executeCommandWithWorkingDirectory(
prefix: String,
workingDirectory: String,
command: Array
): String {
logger.debug("${prefix}Executing: ${command.joinToString(" ")}")
return ExternalCommand(
command,
workingDirectory,
mapOf("GIT_SSL_NO_VERIFY" to "true")
).executeAsSeparateProcess()
}
fun init(): SystemGit = this.also { execute(Configuration.gitCommand, "init") }
override fun add(): SystemGit = this.also { execute(Configuration.gitCommand, "add", ".") }
override fun add(relativePath: String): SystemGit = this.also { execute(Configuration.gitCommand, "add", relativePath) }
override fun commit(): SystemGit = this.also { execute(Configuration.gitCommand, "commit", "-m", "Updated contract") }
override fun push(): SystemGit = this.also { executeWithAuth(Configuration.gitCommand, "push") }
override fun pull(): SystemGit = this.also { executeWithAuth(Configuration.gitCommand, "pull") }
override fun resetHard(): SystemGit = this.also { execute(Configuration.gitCommand, "reset", "--hard", "HEAD") }
override fun resetMixed(): SystemGit = this.also { execute(Configuration.gitCommand, "reset", "--mixed", "HEAD") }
override fun mergeAbort(): SystemGit = this.also { execute(Configuration.gitCommand, "merge", "--aborg") }
override fun checkout(branchName: String): SystemGit = this.also { execute(Configuration.gitCommand, "checkout", branchName) }
override fun merge(branchName: String): SystemGit = this.also { execute(Configuration.gitCommand, "merge", branchName) }
override fun clone(gitRepositoryURI: String, cloneDirectory: File): SystemGit =
this.also { executeWithAuth("clone", evaluateEnvVariablesInGitRepoURI(gitRepositoryURI, System.getenv()), cloneDirectory.absolutePath) }
override fun exists(treeish: String, relativePath: String): Boolean {
return try {
show(treeish, relativePath)
true
} catch(e: NonZeroExitError) {
false
}
}
override fun getCurrentBranch(): String {
return execute(Configuration.gitCommand, "git", "diff", "--name-only", "master")
}
override fun statusPorcelain(): String {
return execute(Configuration.gitCommand, "status", "--porcelain")
}
override fun fetch(): String {
return executeWithAuth("fetch")
}
override fun revisionsBehindCount(): Int {
return execute(Configuration.gitCommand, "rev-list", "--count", "HEAD..@{u}").trim().toInt()
}
override fun checkIgnore(path: String): String {
return try {
execute(Configuration.gitCommand, "check-ignore", path)
} catch (nonZeroExitError:NonZeroExitError) {
""
}
}
override fun getFilesChangeInCurrentBranch(): List {
val defaultBranch = defaultBranch()
val result = execute(Configuration.gitCommand, "diff", defaultBranch, "HEAD", "--name-only")
return result.split(System.lineSeparator()).filter { it.isNotBlank() }
}
override fun getFileInTheDefaultBranch(fileName: String, currentBranch: String): File? {
try {
checkout(defaultBranch())
if (!File(fileName).exists()) return null
return File(fileName)
} finally {
checkout(currentBranch)
}
}
override fun shallowClone(gitRepositoryURI: String, cloneDirectory: File): SystemGit =
this.also {
executeWithAuth("clone", "--depth", "1", gitRepositoryURI, cloneDirectory.absolutePath)
}
override fun gitRoot(): String = execute(Configuration.gitCommand, "rev-parse", "--show-toplevel").trim()
override fun show(treeish: String, relativePath: String): String =
execute(Configuration.gitCommand, "show", "${treeish}:${relativePath}")
override fun workingDirectoryIsGitRepo(): Boolean = try {
execute(Configuration.gitCommand, "rev-parse", "--is-inside-work-tree").trim() == "true"
} catch (e: Throwable) {
false.also {
logger.debug(
"This must not be a git dir, got error ${e.javaClass.name}: ${
exceptionCauseMessage(
e
)
}"
)
}
}
override fun getChangedFiles(): List {
val result = execute(Configuration.gitCommand, "status", "--porcelain=1").trim()
if (result.isEmpty())
return emptyList()
return result.lines().map { it.trim().split(" ", limit = 2)[1] }
}
override fun relativeGitPath(newerContractPath: String): Pair {
val gitRoot = File(SystemGit(File(newerContractPath).absoluteFile.parent).gitRoot())
val git = SystemGit(gitRoot.absolutePath)
val relativeContractPath = File(newerContractPath).absoluteFile.relativeTo(gitRoot.absoluteFile).path
return Pair(git, relativeContractPath)
}
override fun fileIsInGitDir(newerContractPath: String): Boolean {
val parentDir = File(newerContractPath).absoluteFile.parentFile.absolutePath
return SystemGit(workingDirectory = parentDir).workingDirectoryIsGitRepo()
}
override fun inGitRootOf(contractPath: String): GitCommand = SystemGit(File(contractPath).parentFile.absolutePath)
fun getChangesFromMainBranch(mainBranch: String): List {
return execute(Configuration.gitCommand, "diff", "--name-only", mainBranch).split(System.lineSeparator())
}
override fun getRemoteUrl(name: String): String = execute(Configuration.gitCommand, "remote", "get-url", name)
override fun currentBranch(): String {
return execute(Configuration.gitCommand, "rev-parse", "--abbrev-ref", "HEAD").trim()
}
override fun defaultBranch(): String {
System.getenv("LOCAL_GIT_BRANCH")?.let {
return it
}
val defaultBranchName = System.getenv("GITHUB_BASE_REF") ?: defaultBranchFromGit()
return "origin/${defaultBranchName}"
}
private fun defaultBranchFromGit(): String {
val symbolicRef = System.getenv("GITHUB_BASE_REF") ?: execute(Configuration.gitCommand, "symbolic-ref", "refs/remotes/origin/HEAD", "--short")
if ("/" !in symbolicRef)
throw ContractException("Could not understand symbolic-ref value $symbolicRef, expected it to be of the format remote/branch name.")
return symbolicRef.split("/")[1].trim()
}
override fun detachedHEAD(): String {
val result = execute(Configuration.gitCommand, "show", "-s", "--pretty=%D", "HEAD")
return result.trim().split(",")[1].trim()
}
}
fun exitErrorMessageContains(exception: NonZeroExitError, snippets: List): Boolean {
return when (val message = exception.localizedMessage ?: exception.message) {
null -> false
else -> snippets.all { snippet -> snippet in message }
}
}
class NonZeroExitError(error: String, val exitCode:Int) : Throwable(error)