implemented 02-git-gateway.md: added Git access layer with credentials bridge, duration parser, and extensive tests

This commit is contained in:
Michael Hoennig
2026-07-07 07:10:38 +02:00
parent f4a6ec9194
commit a427665ce8
11 changed files with 707 additions and 23 deletions
@@ -0,0 +1,52 @@
package de.hoennig.gittally.git
import org.springframework.stereotype.Component
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
data class GitCommandResult(
val exitCode: Int,
val stdout: String,
val stderr: String,
) {
val isSuccess: Boolean get() = exitCode == 0
fun lines(): List<String> = stdout.lines().map { it.trim() }.filter { it.isNotEmpty() }
}
class GitCommandException(
command: List<String>,
result: GitCommandResult,
) : RuntimeException(
"Command failed with exit code ${result.exitCode}: ${command.joinToString(" ")}\n${result.stderr.trim()}",
)
@Component
class GitCommandRunner {
fun run(
command: List<String>,
workingDir: Path,
environment: Map<String, String> = emptyMap(),
): GitCommandResult {
val builder = ProcessBuilder(command).directory(workingDir.toFile())
builder.environment().putAll(environment)
val process = builder.start()
// stderr is drained concurrently so neither pipe buffer can block the process
val stderr = CompletableFuture.supplyAsync { process.errorStream.bufferedReader().readText() }
val stdout = process.inputStream.bufferedReader().readText()
val exitCode = process.waitFor()
return GitCommandResult(exitCode, stdout, stderr.get())
}
fun runOrThrow(
command: List<String>,
workingDir: Path,
environment: Map<String, String> = emptyMap(),
): GitCommandResult {
val result = run(command, workingDir, environment)
if (!result.isSuccess) {
throw GitCommandException(command, result)
}
return result
}
}