Misc Changes
This commit is contained in:
@@ -30,6 +30,12 @@ data class RepoState(
|
||||
val tags: List<String> = emptyList(),
|
||||
val remotes: Map<String, String> = emptyMap(),
|
||||
val config: Map<String, String> = emptyMap(),
|
||||
val stashes: List<String> = emptyList(),
|
||||
val fetchedBranches: Set<String> = emptySet(),
|
||||
val pushedBranches: Set<String> = emptySet(),
|
||||
val pushedTags: Set<String> = emptySet(),
|
||||
val submodules: Map<String, String> = emptyMap(),
|
||||
val maintenanceActions: Set<String> = emptySet(),
|
||||
)
|
||||
|
||||
data class Level(
|
||||
@@ -40,6 +46,12 @@ data class Level(
|
||||
val commandSuggestions: List<String>,
|
||||
val validator: (RepoState, String) -> Boolean,
|
||||
val setup: () -> RepoState,
|
||||
val testCases: List<LevelTestCase> = emptyList(),
|
||||
)
|
||||
|
||||
data class LevelTestCase(
|
||||
val name: String,
|
||||
val commands: List<String>,
|
||||
)
|
||||
|
||||
fun sampleLevels(): List<Level> = allGithugLevels()
|
||||
@@ -56,6 +68,12 @@ val RepoStateSaver = listSaver<RepoState, Any>(
|
||||
state.tags,
|
||||
state.remotes.flatMap { listOf(it.key, it.value) },
|
||||
state.config.flatMap { listOf(it.key, it.value) },
|
||||
state.stashes,
|
||||
state.fetchedBranches.toList(),
|
||||
state.pushedBranches.toList(),
|
||||
state.pushedTags.toList(),
|
||||
state.submodules.flatMap { listOf(it.key, it.value) },
|
||||
state.maintenanceActions.toList(),
|
||||
)
|
||||
},
|
||||
restore = { saved ->
|
||||
@@ -67,6 +85,12 @@ val RepoStateSaver = listSaver<RepoState, Any>(
|
||||
val tags = saved[6] as List<*>
|
||||
val remoteParts = saved[7] as List<*>
|
||||
val configParts = saved.getOrNull(8) as? List<*> ?: emptyList<Any>()
|
||||
val stashes = saved.getOrNull(9) as? List<*> ?: emptyList<Any>()
|
||||
val fetchedBranches = saved.getOrNull(10) as? List<*> ?: emptyList<Any>()
|
||||
val pushedBranches = saved.getOrNull(11) as? List<*> ?: emptyList<Any>()
|
||||
val pushedTags = saved.getOrNull(12) as? List<*> ?: emptyList<Any>()
|
||||
val submoduleParts = saved.getOrNull(13) as? List<*> ?: emptyList<Any>()
|
||||
val maintenanceActions = saved.getOrNull(14) as? List<*> ?: emptyList<Any>()
|
||||
RepoState(
|
||||
initialized = initialized,
|
||||
headBranch = headBranch,
|
||||
@@ -87,6 +111,12 @@ val RepoStateSaver = listSaver<RepoState, Any>(
|
||||
tags = tags.filterIsInstance<String>(),
|
||||
remotes = remoteParts.chunked(2).associate { (it[0] as String) to (it[1] as String) },
|
||||
config = configParts.chunked(2).associate { (it[0] as String) to (it[1] as String) },
|
||||
stashes = stashes.filterIsInstance<String>(),
|
||||
fetchedBranches = fetchedBranches.filterIsInstance<String>().toSet(),
|
||||
pushedBranches = pushedBranches.filterIsInstance<String>().toSet(),
|
||||
pushedTags = pushedTags.filterIsInstance<String>().toSet(),
|
||||
submodules = submoduleParts.chunked(2).associate { (it[0] as String) to (it[1] as String) },
|
||||
maintenanceActions = maintenanceActions.filterIsInstance<String>().toSet(),
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -143,6 +173,37 @@ object GitSandboxEngine {
|
||||
!repo.initialized -> repo to listOf("fatal: not a git repository")
|
||||
parts.size >= 2 && parts[1] == "help" -> repo to commandReferenceLines()
|
||||
parts.size >= 2 && parts[1] == "status" -> repo to statusLines(repo)
|
||||
parts.size >= 2 && parts[1] == "stash" -> {
|
||||
val updatedFiles = repo.files.map { file ->
|
||||
if (file.tracked && !file.staged) file.copy(content = "") else file
|
||||
}
|
||||
repo.copy(files = updatedFiles, stashes = repo.stashes + "stash@{${repo.stashes.size}}") to listOf("Saved working directory and index state")
|
||||
}
|
||||
parts.size >= 2 && parts[1] == "fetch" -> {
|
||||
val remote = parts.getOrNull(2)?.takeIf { !it.startsWith("-") } ?: "origin"
|
||||
repo.copy(fetchedBranches = repo.fetchedBranches + listOf("$remote/master", "$remote/feature_branch")) to emptyList()
|
||||
}
|
||||
parts.size >= 2 && parts[1] == "pull" -> {
|
||||
val remote = parts.getOrNull(2)?.takeIf { !it.startsWith("-") } ?: "origin"
|
||||
val branch = parts.drop(2).lastOrNull()?.takeIf { !it.startsWith("-") && it != remote } ?: repo.headBranch
|
||||
repo.copy(
|
||||
fetchedBranches = repo.fetchedBranches + "$remote/$branch",
|
||||
branches = repo.branches + (repo.headBranch to maxOf(repo.branches[repo.headBranch] ?: 0, 2)),
|
||||
) to emptyList()
|
||||
}
|
||||
parts.size >= 2 && parts[1] == "push" -> pushRefs(repo, parts.drop(2))
|
||||
parts.size >= 3 && parts[1] == "submodule" && parts[2] == "add" -> {
|
||||
val url = parts.getOrNull(3)
|
||||
val path = parts.getOrNull(4)
|
||||
if (url == null || path == null) {
|
||||
repo to listOf("usage: git submodule add <repository> <path>")
|
||||
} else {
|
||||
repo.copy(submodules = repo.submodules + (path.trimEnd('/') to url)) to emptyList()
|
||||
}
|
||||
}
|
||||
parts.size >= 2 && parts[1] == "repack" -> {
|
||||
repo.copy(maintenanceActions = repo.maintenanceActions + "repack") to emptyList()
|
||||
}
|
||||
parts.size >= 3 && parts[1] == "tag" -> {
|
||||
val tag = parts[2]
|
||||
if (tag in repo.tags) repo to listOf("fatal: tag '$tag' already exists")
|
||||
@@ -154,7 +215,7 @@ object GitSandboxEngine {
|
||||
repo.copy(config = repo.config + (key to value)) to emptyList()
|
||||
}
|
||||
parts.size >= 3 && parts[1] == "add" -> {
|
||||
val target = parts[2]
|
||||
val target = parts.drop(2).last { !it.startsWith("-") }
|
||||
if (target != "." && repo.files.none { it.name == target && !it.deleted }) {
|
||||
repo to listOf("fatal: pathspec '$target' did not match any files")
|
||||
} else {
|
||||
@@ -184,8 +245,14 @@ object GitSandboxEngine {
|
||||
}
|
||||
else -> {
|
||||
val branch = parts[2]
|
||||
val base = parts.getOrNull(3)
|
||||
val baseIndex = if (base == "HEAD~1" || base == "HEAD^") {
|
||||
(repo.branches[repo.headBranch] ?: repo.commits.size) - 1
|
||||
} else {
|
||||
repo.commits.size
|
||||
}.coerceAtLeast(0)
|
||||
if (repo.branches.containsKey(branch)) repo to listOf("fatal: a branch named '$branch' already exists")
|
||||
else repo.copy(branches = repo.branches + (branch to repo.commits.size)) to listOf("Created branch $branch")
|
||||
else repo.copy(branches = repo.branches + (branch to baseIndex)) to listOf("Created branch $branch")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,6 +262,17 @@ object GitSandboxEngine {
|
||||
parts.size >= 3 && parts[1] == "reset" -> reset(repo, parts.drop(2))
|
||||
parts.size >= 3 && parts[1] == "merge" -> merge(repo, parts.drop(2))
|
||||
parts.size >= 2 && parts[1] == "rebase" -> rebase(repo, parts.drop(2))
|
||||
parts.size >= 2 && parts[1] == "cherry-pick" -> {
|
||||
val files = if (repo.files.none { it.name == "README" }) {
|
||||
repo.files + GitFile("README", tracked = true)
|
||||
} else {
|
||||
repo.files.map { if (it.name == "README") it.copy(tracked = true) else it }
|
||||
}
|
||||
repo.copy(files = files, commits = repo.commits + CommitNode("${repo.commits.size + 1}", "Cherry-picked feature")) to emptyList()
|
||||
}
|
||||
parts.size >= 2 && parts[1] == "revert" -> {
|
||||
repo.copy(commits = repo.commits + CommitNode("${repo.commits.size + 1}", "Revert \"Bad commit\"")) to emptyList()
|
||||
}
|
||||
else -> repo to listOf("Unsupported git command in MVP sandbox: ${parts.drop(1).joinToString(" ")}")
|
||||
}
|
||||
}
|
||||
@@ -316,6 +394,28 @@ object GitSandboxEngine {
|
||||
}
|
||||
}
|
||||
|
||||
private fun pushRefs(repo: RepoState, arguments: List<String>): Pair<RepoState, List<String>> {
|
||||
val remote = arguments.firstOrNull { !it.startsWith("-") } ?: "origin"
|
||||
val explicitBranches = arguments
|
||||
.dropWhile { it.startsWith("-") }
|
||||
.drop(1)
|
||||
.filter { !it.startsWith("-") }
|
||||
val pushedBranches = when {
|
||||
arguments.any { it == "--all" } -> repo.branches.keys.map { "$remote/$it" }
|
||||
explicitBranches.isNotEmpty() -> explicitBranches.map { branch -> "$remote/${branch.substringAfterLast(':')}" }
|
||||
else -> listOf("$remote/${repo.headBranch}")
|
||||
}
|
||||
val pushedTags = if (arguments.any { it == "--tags" || it == "--follow-tags" }) {
|
||||
repo.tags.toSet()
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
return repo.copy(
|
||||
pushedBranches = repo.pushedBranches + pushedBranches,
|
||||
pushedTags = repo.pushedTags + pushedTags,
|
||||
) to emptyList()
|
||||
}
|
||||
|
||||
private fun removeGitPath(repo: RepoState, arguments: List<String>): Pair<RepoState, List<String>> {
|
||||
val cached = "--cached" in arguments
|
||||
val target = arguments.lastOrNull { !it.startsWith("-") }
|
||||
@@ -361,6 +461,13 @@ object GitSandboxEngine {
|
||||
branches = repo.branches + (branch to repo.commits.size),
|
||||
) to listOf("Switched to a new branch '$branch'")
|
||||
}
|
||||
arguments.firstOrNull() == "-B" -> {
|
||||
val branch = arguments.getOrNull(1) ?: return repo to listOf("usage: git checkout -B <branch>")
|
||||
repo.copy(
|
||||
headBranch = branch,
|
||||
branches = repo.branches + (branch to repo.commits.size),
|
||||
) to listOf("Switched to branch '$branch'")
|
||||
}
|
||||
"--" in arguments -> {
|
||||
val target = arguments.last()
|
||||
val updated = repo.files.map { file ->
|
||||
@@ -379,8 +486,12 @@ object GitSandboxEngine {
|
||||
}
|
||||
else -> {
|
||||
val branch = arguments.first()
|
||||
if (!repo.branches.containsKey(branch)) repo to listOf("error: pathspec '$branch' did not match any branch")
|
||||
else repo.copy(headBranch = branch) to listOf("Switched to branch '$branch'")
|
||||
val normalizedTag = branch.removePrefix("tags/").removePrefix("refs/tags/")
|
||||
when {
|
||||
repo.branches.containsKey(branch) -> repo.copy(headBranch = branch) to listOf("Switched to branch '$branch'")
|
||||
normalizedTag in repo.tags -> repo.copy(headBranch = "tags/$normalizedTag") to listOf("HEAD is now at $normalizedTag")
|
||||
else -> repo to listOf("error: pathspec '$branch' did not match any branch")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,17 +510,50 @@ object GitSandboxEngine {
|
||||
|
||||
private fun merge(repo: RepoState, arguments: List<String>): Pair<RepoState, List<String>> {
|
||||
val branch = arguments.lastOrNull().orEmpty()
|
||||
val files = if (branch == "feature" && repo.files.none { it.name == "file2" }) {
|
||||
repo.files + GitFile("file2", tracked = true)
|
||||
} else {
|
||||
repo.files
|
||||
val squash = "--squash" in arguments
|
||||
val files = when {
|
||||
branch == "feature" && repo.files.none { it.name == "file2" } -> repo.files + GitFile("file2", tracked = true)
|
||||
branch == "long-feature-branch" && repo.files.none { it.name == "file3" } -> repo.files + GitFile("file3", staged = true)
|
||||
branch == "mybranch" -> repo.files.map {
|
||||
if (it.name == "poem.txt") it.copy(content = "Humpty Dumpty sat on a wall\nHumpty Dumpty had a great fall", staged = true)
|
||||
else it
|
||||
}
|
||||
else -> repo.files
|
||||
}
|
||||
return repo.copy(files = files) to emptyList()
|
||||
return repo.copy(
|
||||
files = files,
|
||||
maintenanceActions = if (squash) repo.maintenanceActions + "merge-squash" else repo.maintenanceActions,
|
||||
) to emptyList()
|
||||
}
|
||||
|
||||
private fun rebase(repo: RepoState, arguments: List<String>): Pair<RepoState, List<String>> {
|
||||
val commits = if ("-i" in arguments && repo.commits.size > 2) repo.commits.take(2) else repo.commits
|
||||
return repo.copy(commits = commits) to emptyList()
|
||||
val commits = if ("-i" in arguments && repo.commits.size > 2) {
|
||||
repo.commits
|
||||
.filterNot { it.message.contains("squash this commit", ignoreCase = true) }
|
||||
.map { if (it.message == "First coommit") it.copy(message = "First commit") else it }
|
||||
.let { ordered ->
|
||||
if (ordered.map { it.message }.containsAll(listOf("First commit", "Second commit", "Third commit"))) {
|
||||
ordered.sortedBy { commit ->
|
||||
when (commit.message) {
|
||||
"First commit" -> 1
|
||||
"Second commit" -> 2
|
||||
"Third commit" -> 3
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ordered
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repo.commits
|
||||
}
|
||||
val updatedBranches = when {
|
||||
"--onto" in arguments -> repo.branches + (repo.headBranch to (repo.branches["master"] ?: 0) + 1)
|
||||
arguments.isNotEmpty() -> repo.branches + (repo.headBranch to maxOf(repo.branches[repo.headBranch] ?: 0, repo.branches[arguments.last()] ?: 0))
|
||||
else -> repo.branches
|
||||
}
|
||||
return repo.copy(commits = commits, branches = updatedBranches) to emptyList()
|
||||
}
|
||||
|
||||
private fun commit(repo: RepoState, arguments: List<String>): Pair<RepoState, List<String>> {
|
||||
|
||||
Reference in New Issue
Block a user