Auto-commit after successful build: update app gameplay/UI, improve build setup

Changed files:\napp/build.gradle.kts
app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt
app/src/test/java/solutions/tretter/githugandroid/LevelSolutionsTest.kt
This commit is contained in:
Joe Tretter
2026-05-02 21:36:42 -05:00
parent 02f26be3fa
commit d6c4c4a2df
3 changed files with 80 additions and 13 deletions

View File

@@ -19,8 +19,8 @@ android {
applicationId = "solutions.tretter.githugandroid" applicationId = "solutions.tretter.githugandroid"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 95 versionCode = 96
versionName = "0.1.94" versionName = "0.1.95"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true

View File

@@ -43,10 +43,7 @@ class GitRepositoryRuntime(private val context: Context) {
runGit(nativeGit, sandbox, listOf("checkout", "-B", desired.headBranch)) runGit(nativeGit, sandbox, listOf("checkout", "-B", desired.headBranch))
} }
val stageTargets = desired.files.filter { it.staged || it.tracked }.map { it.name } materializeNativeGitState(nativeGit, sandbox, desired)
if (stageTargets.isNotEmpty()) {
runGit(nativeGit, sandbox, listOf("add") + stageTargets)
}
} }
return inspectSandbox(level) return inspectSandbox(level)
@@ -146,17 +143,85 @@ class GitRepositoryRuntime(private val context: Context) {
currentRepo to emptyList() currentRepo to emptyList()
} }
} }
"echo" -> currentRepo to listOf(tokens.drop(1).joinToString(" ")) "echo" -> executeEcho(workingDir, currentRepo, tokens)
else -> currentRepo to listOf("Command not supported in prototype runtime. Try a git command or helper command.") else -> currentRepo to listOf("Command not supported in prototype runtime. Try a git command or helper command.")
} }
} }
private fun materializeNativeGitState(nativeGit: File, sandbox: File, desired: RepoState) {
desired.config.forEach { (key, value) ->
runGit(nativeGit, sandbox, listOf("config", key, value))
}
val trackedFiles = desired.files.filter { it.tracked }
val desiredCommitCount = maxOf(
desired.commits.size,
desired.branches.values.maxOrNull() ?: 0,
if (desired.tags.isNotEmpty()) 1 else 0,
)
if (desiredCommitCount > 0) {
repeat(desiredCommitCount) { index ->
filesForSetupCommit(trackedFiles, index, desiredCommitCount).forEach { file ->
runGit(nativeGit, sandbox, listOf("add", file.name))
}
val message = desired.commits.getOrNull(index)?.message ?: "Setup commit ${index + 1}"
runGit(nativeGit, sandbox, listOf("commit", "--allow-empty", "-m", message))
}
desired.branches
.keys
.filterNot { it == desired.headBranch }
.forEach { branch -> runGit(nativeGit, sandbox, listOf("branch", branch)) }
if (desired.branches.containsKey(desired.headBranch)) {
runGit(nativeGit, sandbox, listOf("checkout", desired.headBranch))
}
}
desired.tags.forEach { tag -> runGit(nativeGit, sandbox, listOf("tag", tag)) }
desired.remotes.forEach { (name, url) -> runGit(nativeGit, sandbox, listOf("remote", "add", name, url)) }
val stagedTargets = desired.files.filter { it.staged }.map { it.name }
if (stagedTargets.isNotEmpty()) {
runGit(nativeGit, sandbox, listOf("add") + stagedTargets)
}
}
private fun filesForSetupCommit(files: List<GitFile>, index: Int, commitCount: Int): List<GitFile> {
if (files.isEmpty()) return emptyList()
if (commitCount <= 1) return files
if (index == 0) return files.take(1)
if (index == commitCount - 1) return files.drop(index)
return files.getOrNull(index)?.let { listOf(it) }.orEmpty()
}
private fun executeEcho(workingDir: File, currentRepo: RepoState, tokens: List<String>): Pair<RepoState, List<String>> {
val redirectIndex = tokens.indexOfFirst { it == ">" || it == ">>" }
if (redirectIndex == -1 || redirectIndex == tokens.lastIndex) {
return currentRepo to listOf(tokens.drop(1).joinToString(" "))
}
val file = File(workingDir, tokens[redirectIndex + 1])
file.parentFile?.mkdirs()
val content = tokens.subList(1, redirectIndex).joinToString(" ")
if (tokens[redirectIndex] == ">>") {
if (file.exists() && file.length() > 0L) {
file.appendText("\n$content")
} else {
file.writeText(content)
}
} else {
file.writeText(content)
}
return currentRepo to emptyList()
}
private fun inspectSandbox(level: Level): RepoState { private fun inspectSandbox(level: Level): RepoState {
val sandbox = sandboxDir(level) val sandbox = sandboxDir(level)
val nativeGit = nativeGitBinary() val nativeGit = nativeGitBinary()
val filesOnDisk = sandbox.listFiles() val filesOnDisk = sandbox.walkTopDown()
?.filter { it.isFile && it.name != ".git" } .filter { it.isFile && !it.relativeTo(sandbox).path.startsWith(".git/") }
.orEmpty() .orEmpty()
.toList()
if (nativeGit == null || !File(sandbox, ".git").exists()) { if (nativeGit == null || !File(sandbox, ".git").exists()) {
return RepoState( return RepoState(
@@ -208,9 +273,10 @@ class GitRepositoryRuntime(private val context: Context) {
return RepoState( return RepoState(
initialized = true, initialized = true,
files = filesOnDisk.map { file -> files = filesOnDisk.map { file ->
val (staged, tracked) = statusMap[file.name] ?: (false to true) val relativePath = file.relativeTo(sandbox).path
val (staged, tracked) = statusMap[relativePath] ?: (false to true)
GitFile( GitFile(
name = file.name, name = relativePath,
content = file.readText(), content = file.readText(),
staged = staged, staged = staged,
tracked = tracked, tracked = tracked,
@@ -272,4 +338,4 @@ class GitRepositoryRuntime(private val context: Context) {
private data class ProcessExecutionResult( private data class ProcessExecutionResult(
val exitCode: Int, val exitCode: Int,
val outputLines: List<String>, val outputLines: List<String>,
) )

View File

@@ -25,7 +25,8 @@ class LevelSolutionsTest {
fun knownSolutionsCompleteEveryLevel() { fun knownSolutionsCompleteEveryLevel() {
val evidence = StringBuilder() val evidence = StringBuilder()
evidence.appendLine("GitHug Android level solution evidence") evidence.appendLine("GitHug Android level solution evidence")
evidence.appendLine("Engine: GitSandboxEngine") evidence.appendLine("Engine: GitSandboxEngine fallback")
evidence.appendLine("Scope: validates fallback command handling and level validators; native runtime setup is covered by app runtime tests/builds.")
evidence.appendLine("Levels: ${allGithugLevels().size}") evidence.appendLine("Levels: ${allGithugLevels().size}")
evidence.appendLine() evidence.appendLine()