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/GameModels.kt app/src/main/java/solutions/tretter/githugandroid/GitHelpCommands.kt app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt app/src/main/java/solutions/tretter/githugandroid/Visual.kt app/src/main/java/solutions/tretter/githugandroid/levels/AdvancedLevels.kt app/src/main/java/solutions/tretter/githugandroid/levels/LevelCatalog.kt app/src/test/java/solutions/tretter/githugandroid/GitHelpCommandsTest.kt app/src/test/java/solutions/tretter/githugandroid/GitSandboxEngineTest.kt
This commit is contained in:
@@ -12,6 +12,7 @@ data class GitFile(
|
||||
val content: String = "",
|
||||
val staged: Boolean = false,
|
||||
val tracked: Boolean = false,
|
||||
val deleted: Boolean = false,
|
||||
)
|
||||
|
||||
data class CommitNode(
|
||||
@@ -48,7 +49,7 @@ val RepoStateSaver = listSaver<RepoState, Any>(
|
||||
listOf(
|
||||
state.initialized,
|
||||
state.headBranch,
|
||||
state.files.flatMap { listOf(it.name, it.content, it.staged.toString(), it.tracked.toString()) },
|
||||
state.files.flatMap { listOf(it.name, it.content, it.staged.toString(), it.tracked.toString(), it.deleted.toString()) },
|
||||
state.commits.flatMap { listOf(it.id, it.message) },
|
||||
state.branches.flatMap { listOf(it.key, it.value.toString()) },
|
||||
state.currentDir,
|
||||
@@ -69,12 +70,13 @@ val RepoStateSaver = listSaver<RepoState, Any>(
|
||||
RepoState(
|
||||
initialized = initialized,
|
||||
headBranch = headBranch,
|
||||
files = fileParts.chunked(4).map {
|
||||
files = fileParts.chunked(if (fileParts.size % 5 == 0) 5 else 4).map {
|
||||
GitFile(
|
||||
name = it[0] as String,
|
||||
content = it[1] as String,
|
||||
staged = (it[2] as String).toBoolean(),
|
||||
tracked = (it[3] as String).toBoolean(),
|
||||
deleted = (it.getOrNull(4) as? String)?.toBoolean() ?: false,
|
||||
)
|
||||
},
|
||||
commits = commitParts.chunked(2).map {
|
||||
@@ -112,16 +114,22 @@ object GitSandboxEngine {
|
||||
parts[0] == "help" || parts[0] == "?" -> repo to commandReferenceLines()
|
||||
parts[0] == "touch" && parts.size >= 2 -> {
|
||||
val name = parts[1]
|
||||
if (repo.files.any { it.name == name }) repo to listOf("$name already exists")
|
||||
if (repo.files.any { it.name == name && !it.deleted }) repo to listOf("$name already exists")
|
||||
else repo.copy(files = repo.files + GitFile(name = name)) to emptyList()
|
||||
}
|
||||
parts[0] == "mkdir" && parts.size >= 2 -> repo to emptyList()
|
||||
parts[0] == "rm" && parts.size >= 2 -> {
|
||||
val target = parts[1]
|
||||
repo.copy(files = repo.files.filterNot { it.name == target }) to emptyList()
|
||||
repo.copy(files = repo.files.mapNotNull { file ->
|
||||
when {
|
||||
file.name != target -> file
|
||||
file.tracked -> file.copy(deleted = true, staged = false)
|
||||
else -> null
|
||||
}
|
||||
}) to emptyList()
|
||||
}
|
||||
parts[0] == "echo" -> writeEcho(repo, parts)
|
||||
parts[0] == "ls" -> repo to repo.files.map { it.name }.ifEmpty { listOf() }
|
||||
parts[0] == "ls" -> repo to repo.files.filterNot { it.deleted }.map { it.name }.ifEmpty { listOf() }
|
||||
parts[0] != "git" -> repo to listOf("Command not supported in sandbox. Try a git command or 'touch'.")
|
||||
parts.size >= 2 && parts[1] == "init" -> repo.copy(initialized = true, branches = mapOf("master" to repo.commits.size)) to listOf("Initialized empty Git repository")
|
||||
!repo.initialized -> repo to listOf("fatal: not a git repository")
|
||||
@@ -139,15 +147,15 @@ object GitSandboxEngine {
|
||||
}
|
||||
parts.size >= 3 && parts[1] == "add" -> {
|
||||
val target = parts[2]
|
||||
if (target != "." && repo.files.none { it.name == target }) {
|
||||
if (target != "." && repo.files.none { it.name == target && !it.deleted }) {
|
||||
repo to listOf("fatal: pathspec '$target' did not match any files")
|
||||
} else {
|
||||
val updated = repo.files.map { if (target == "." || it.name == target) it.copy(staged = true) else it }
|
||||
val updated = repo.files.map { if ((target == "." || it.name == target) && !it.deleted) it.copy(staged = true) else it }
|
||||
repo.copy(files = updated) to listOf("staged ${if (target == ".") "all files" else target}")
|
||||
}
|
||||
}
|
||||
parts.size >= 3 && parts[1] == "rm" -> removeGitPath(repo, parts.drop(2))
|
||||
parts.size >= 4 && parts[1] == "mv" -> moveGitPath(repo, parts[2], parts[3])
|
||||
parts.size >= 4 && parts[1] == "mv" -> moveGitPath(repo, expandPathspecs(repo, parts.drop(2)))
|
||||
parts.size >= 2 && parts[1] == "commit" -> commit(repo, parts.drop(2))
|
||||
parts.size >= 2 && parts[1] == "log" -> {
|
||||
repo to if (repo.commits.isEmpty()) listOf("fatal: your current branch '${repo.headBranch}' does not have any commits yet")
|
||||
@@ -244,12 +252,28 @@ object GitSandboxEngine {
|
||||
} else {
|
||||
val current = updatedFiles[index]
|
||||
val nextContent = if (append && current.content.isNotEmpty()) "${current.content}\n$content" else content
|
||||
updatedFiles[index] = current.copy(content = nextContent)
|
||||
updatedFiles[index] = current.copy(content = nextContent, deleted = false)
|
||||
}
|
||||
|
||||
return repo.copy(files = updatedFiles) to emptyList()
|
||||
}
|
||||
|
||||
fun expandPathspecs(repo: RepoState, arguments: List<String>): List<String> {
|
||||
return arguments.flatMap { argument ->
|
||||
if (!argument.hasGlob()) {
|
||||
listOf(argument)
|
||||
} else {
|
||||
val regex = argument.globToRegex()
|
||||
repo.files
|
||||
.filterNot { it.deleted }
|
||||
.map { it.name }
|
||||
.filter { regex.matches(it) }
|
||||
.sorted()
|
||||
.ifEmpty { listOf(argument) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeGitPath(repo: RepoState, arguments: List<String>): Pair<RepoState, List<String>> {
|
||||
val cached = "--cached" in arguments
|
||||
val target = arguments.lastOrNull { !it.startsWith("-") }
|
||||
@@ -266,9 +290,22 @@ object GitSandboxEngine {
|
||||
return repo.copy(files = updated) to emptyList()
|
||||
}
|
||||
|
||||
private fun moveGitPath(repo: RepoState, source: String, destination: String): Pair<RepoState, List<String>> {
|
||||
private fun moveGitPath(repo: RepoState, arguments: List<String>): Pair<RepoState, List<String>> {
|
||||
val destination = arguments.lastOrNull() ?: return repo to listOf("usage: git mv <source> <destination>")
|
||||
val sources = arguments.dropLast(1)
|
||||
if (sources.isEmpty()) return repo to listOf("usage: git mv <source> <destination>")
|
||||
val destinationIsDirectory = sources.size > 1 || destination.endsWith("/")
|
||||
val updated = repo.files.map { file ->
|
||||
if (file.name == source) file.copy(name = destination, staged = true) else file
|
||||
if (file.name in sources) {
|
||||
val target = if (destinationIsDirectory) {
|
||||
destination.trimEnd('/') + "/" + file.name.substringAfterLast('/')
|
||||
} else {
|
||||
destination
|
||||
}
|
||||
file.copy(name = target, staged = true)
|
||||
} else {
|
||||
file
|
||||
}
|
||||
}
|
||||
return repo.copy(files = updated) to emptyList()
|
||||
}
|
||||
@@ -439,18 +476,27 @@ object GitSandboxEngine {
|
||||
|
||||
private fun statusLines(repo: RepoState): List<String> {
|
||||
val staged = repo.files.filter { it.staged }.map {
|
||||
if (it.tracked) "modified: ${it.name}" else "new file: ${it.name}"
|
||||
when {
|
||||
it.deleted -> "deleted: ${it.name}"
|
||||
it.tracked -> "modified: ${it.name}"
|
||||
else -> "new file: ${it.name}"
|
||||
}
|
||||
}
|
||||
val unstaged = repo.files.filterNot { it.staged || it.tracked }.map { "untracked: ${it.name}" }
|
||||
val deleted = repo.files.filter { it.deleted && it.tracked && !it.staged }.map { "deleted: ${it.name}" }
|
||||
val unstaged = repo.files.filterNot { it.staged || it.tracked || it.deleted }.map { "untracked: ${it.name}" }
|
||||
return buildList {
|
||||
add("On branch ${repo.headBranch}")
|
||||
if (staged.isEmpty() && unstaged.isEmpty()) {
|
||||
if (staged.isEmpty() && deleted.isEmpty() && unstaged.isEmpty()) {
|
||||
add("nothing to commit, working tree clean")
|
||||
} else {
|
||||
if (staged.isNotEmpty()) {
|
||||
add("Changes to be committed:")
|
||||
addAll(staged)
|
||||
}
|
||||
if (deleted.isNotEmpty()) {
|
||||
add("Changes not staged for commit:")
|
||||
addAll(deleted)
|
||||
}
|
||||
if (unstaged.isNotEmpty()) {
|
||||
add("Untracked files:")
|
||||
addAll(unstaged)
|
||||
@@ -459,6 +505,27 @@ object GitSandboxEngine {
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.hasGlob(): Boolean = any { it == '*' || it == '?' }
|
||||
|
||||
private fun String.globToRegex(): Regex {
|
||||
val pattern = buildString {
|
||||
append('^')
|
||||
this@globToRegex.forEach { char ->
|
||||
when (char) {
|
||||
'*' -> append("[^/]*")
|
||||
'?' -> append("[^/]")
|
||||
'.', '(', ')', '+', '|', '^', '$', '@', '%', '{', '}', '[', ']', '\\' -> {
|
||||
append('\\')
|
||||
append(char)
|
||||
}
|
||||
else -> append(char)
|
||||
}
|
||||
}
|
||||
append('$')
|
||||
}
|
||||
return Regex(pattern)
|
||||
}
|
||||
|
||||
private data class ParsedCommitArguments(
|
||||
val message: String? = null,
|
||||
val stageAllTracked: Boolean = false,
|
||||
|
||||
Reference in New Issue
Block a user