Improve upstream parity for level fixtures

- Add native per-level fixtures for complex histories, tags, branch graphs, rebases, resets, restores, stashes, and local remotes.
- Strengthen repository inspection so branch commit counts, remote-tracking branches, and detached tag checkouts reflect real Git state.
- Align Fetch, Pull, Push Branch, Branch At, and Rebase validators/tests with upstream-style repository state.
- Keep the prompt callout and native filesystem tab-completion fixes from the previous batch.
This commit is contained in:
Joe Tretter
2026-05-07 09:21:04 -05:00
parent 8e88b5e7c5
commit 91e2df3ffe
11 changed files with 526 additions and 17 deletions

View File

@@ -103,6 +103,34 @@ class GitRepositoryRuntime private constructor(
return augmentObservedRepoFacts(currentRepo, inspectedRepo, expandedTokens) to result.second
}
fun completionCandidates(level: Level, currentRepo: RepoState, directoriesOnly: Boolean): List<String> {
val nativeGit = nativeGitBinary()
val sandbox = sandboxDir(level)
if (nativeGit == null || !sandbox.exists()) {
return if (directoriesOnly) {
directoryCompletionCandidates(currentRepo)
} else {
fileCompletionCandidates(currentRepo)
}
}
val sandboxRoot = sandbox.canonicalFile
val workingDir = File(sandboxRoot, currentRepo.currentDir).canonicalFile
.takeIf { it.path == sandboxRoot.path || it.path.startsWith(sandboxRoot.path + File.separator) }
?: sandboxRoot
if (!workingDir.exists() || !workingDir.isDirectory) return emptyList()
return workingDir.walkTopDown()
.drop(1)
.filter { file -> !directoriesOnly || file.isDirectory }
.map { file ->
val relativePath = file.relativeTo(workingDir).path
if (file.isDirectory) "$relativePath/" else relativePath
}
.filter { it.isNotBlank() }
.toList()
}
fun commandReferenceLines(): List<String> {
return buildList {
addAll(GitSandboxEngine.commandReferenceLines())
@@ -390,6 +418,12 @@ class GitRepositoryRuntime private constructor(
materializeNativePushLevel(nativeGit, sandbox)
return
}
if (materializeNativeLevelFixture(levelId, sandbox) { directory, arguments ->
runGit(nativeGit, directory, arguments).exitCode
}
) {
return
}
desired.config.forEach { (key, value) ->
runGit(nativeGit, sandbox, listOf("config", key, value))
@@ -466,8 +500,17 @@ class GitRepositoryRuntime private constructor(
val remoteWorkTree = File(sandbox.parentFile ?: sandbox, "${sandbox.name}-origin")
remoteWorkTree.deleteRecursively()
runGit(nativeGit, sandbox.parentFile ?: sandbox, listOf("clone", sandbox.absolutePath, remoteWorkTree.absolutePath))
remoteWorkTree.mkdirs()
val initResult = runGit(nativeGit, remoteWorkTree, listOf("init", "-b", "master"))
if (initResult.exitCode != 0) {
runGit(nativeGit, remoteWorkTree, listOf("init"))
runGit(nativeGit, remoteWorkTree, listOf("checkout", "-B", "master"))
}
runGit(nativeGit, remoteWorkTree, listOf("config", "receive.denyCurrentBranch", "ignore"))
runGit(nativeGit, sandbox, listOf("remote", "add", "push-setup-origin", File(remoteWorkTree, ".git").absolutePath))
runGit(nativeGit, sandbox, listOf("push", "push-setup-origin", "master"))
runGit(nativeGit, sandbox, listOf("remote", "remove", "push-setup-origin"))
runGit(nativeGit, remoteWorkTree, listOf("checkout", "-f", "master"))
File(remoteWorkTree, "file4").writeText("file4\n")
commitIn(remoteWorkTree, "Fourth commit", "file4")
@@ -580,9 +623,11 @@ class GitRepositoryRuntime private constructor(
val logResult = runGit(nativeGit, sandbox, listOf("log", "--pretty=format:%h\t%s"))
val branchResult = runGit(nativeGit, sandbox, listOf("branch", "--list"))
val remoteBranchResult = runGit(nativeGit, sandbox, listOf("branch", "-r", "--list"))
val tagResult = runGit(nativeGit, sandbox, listOf("tag", "--list"))
val remoteResult = runGit(nativeGit, sandbox, listOf("remote", "-v"))
val headResult = runGit(nativeGit, sandbox, listOf("branch", "--show-current"))
val exactTagResult = runGit(nativeGit, sandbox, listOf("describe", "--tags", "--exact-match"))
val userNameResult = runGit(nativeGit, sandbox, listOf("config", "--get", "user.name"))
val userEmailResult = runGit(nativeGit, sandbox, listOf("config", "--get", "user.email"))
val config = buildMap {
@@ -606,6 +651,23 @@ class GitRepositoryRuntime private constructor(
emptyList()
}
val branches = branchResult.outputLines
.map { it.removePrefix("*").trim() }
.filter { it.isNotBlank() && !it.startsWith("(") }
.associateWith { branch ->
runGit(nativeGit, sandbox, listOf("rev-list", "--count", branch))
.outputLines
.firstOrNull()
?.toIntOrNull()
?: 0
}
val headBranch = headResult.outputLines.firstOrNull()
?.ifBlank { null }
?: exactTagResult.outputLines.firstOrNull()
?.takeIf { exactTagResult.exitCode == 0 && it.isNotBlank() }
?.let { "tags/$it" }
?: "DETACHED"
return RepoState(
initialized = true,
files = filesOnDisk.map { file ->
@@ -627,16 +689,20 @@ class GitRepositoryRuntime private constructor(
tracked = tracked,
deleted = true,
)
},
},
commits = commits,
headBranch = headResult.outputLines.firstOrNull()?.ifBlank { null } ?: "master",
branches = branchResult.outputLines.map { it.removePrefix("*").trim() }.filter { it.isNotBlank() }.associateWith { 0 },
headBranch = headBranch,
branches = branches,
tags = tagResult.outputLines.filter { it.isNotBlank() },
remotes = remoteResult.outputLines.mapNotNull { line ->
val parts = line.trim().split(Regex("\\s+"))
if (parts.size >= 2) parts[0] to parts[1] else null
}.toMap(),
config = config,
fetchedBranches = remoteBranchResult.outputLines
.map { it.removePrefix("*").trim() }
.filter { it.isNotBlank() && " -> " !in it }
.toSet(),
)
}