Speed up runtime state refresh

This commit is contained in:
Joe Tretter
2026-06-24 17:44:53 -05:00
parent 0554c736e4
commit 1c15297ae6
7 changed files with 180 additions and 22 deletions

View File

@@ -22,4 +22,21 @@ class GitRepositoryRuntimeInstrumentedTest {
assertTrue(updatedRepo.initialized)
assertTrue(level.validator(updatedRepo, "git init"))
}
@Test
fun nativeGitRuntimeCompletesConfigLevelOnDevice() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val runtime = GitRepositoryRuntime(context)
val level = configLevel()
assertTrue(runtime.unavailableMessage(), runtime.isNativeGitAvailable())
val repo = runtime.prepareLevel(level)
val (namedRepo, _) = runtime.execute(level, repo, "git config user.name GitHug")
val (configuredRepo, _) = runtime.execute(level, namedRepo, "git config user.email githug@example.com")
assertTrue(configuredRepo.config["user.name"].orEmpty().isNotBlank())
assertTrue(configuredRepo.config["user.email"].orEmpty().isNotBlank())
assertTrue(level.validator(configuredRepo, "git config user.email githug@example.com"))
}
}

View File

@@ -6,16 +6,21 @@ internal class GitRepositoryInspector(
private val nativeGit: () -> File,
private val runGit: (File, File, List<String>, Map<String, String>) -> ProcessExecutionResult,
) {
fun inspectConfig(sandbox: File, currentDir: String = "."): Map<String, String> {
val git = nativeGit()
val workingDir = File(sandbox, currentDir).canonicalFile
.takeIf { it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator) }
?: sandbox
val repositoryRoot = repositoryRoot(sandbox, workingDir) ?: sandbox
return readConfig(git, repositoryRoot)
}
fun inspect(sandbox: File, currentDir: String = "."): RepoState {
val git = nativeGit()
val workingDir = File(sandbox, currentDir).canonicalFile
.takeIf { it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator) }
?: sandbox
val repositoryRoot = generateSequence(workingDir) { directory ->
directory.parentFile?.takeIf {
it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator)
}
}.firstOrNull { File(it, ".git").exists() }
val repositoryRoot = repositoryRoot(sandbox, workingDir)
val inspectionRoot = repositoryRoot ?: sandbox
val filesOnDisk = inspectionRoot.walkTopDown()
.filter { it.isFile && !it.relativeTo(inspectionRoot).path.startsWith(".git/") }
@@ -54,8 +59,6 @@ internal class GitRepositoryInspector(
val remoteResult = run(git, inspectionRoot, listOf("remote", "-v"))
val headResult = run(git, inspectionRoot, listOf("branch", "--show-current"))
val exactTagResult = run(git, inspectionRoot, listOf("describe", "--tags", "--exact-match"))
val userNameResult = run(git, inspectionRoot, listOf("config", "--get", "user.name"))
val userEmailResult = run(git, inspectionRoot, listOf("config", "--get", "user.email"))
val stashResult = run(git, inspectionRoot, listOf("stash", "list", "--format=%gd"))
val submodules = inspectSubmodules(git, inspectionRoot)
val maintenanceActions = buildSet {
@@ -68,14 +71,7 @@ internal class GitRepositoryInspector(
?.readLines()
?.count { it.isNotBlank() }
?: 0
val config = buildMap {
userNameResult.outputLines.firstOrNull()
?.takeIf { userNameResult.exitCode == 0 && it.isNotBlank() }
?.let { put("user.name", it) }
userEmailResult.outputLines.firstOrNull()
?.takeIf { userEmailResult.exitCode == 0 && it.isNotBlank() }
?.let { put("user.email", it) }
}
val config = readConfig(git, inspectionRoot)
val commits = if (logResult.exitCode == 0) {
logResult.outputLines.filter { it.isNotBlank() }.mapNotNull { line ->
@@ -185,6 +181,28 @@ internal class GitRepositoryInspector(
environment: Map<String, String> = emptyMap(),
): ProcessExecutionResult = runGit(binary, workingDir, arguments, environment)
private fun repositoryRoot(sandbox: File, workingDir: File): File? {
val sandboxPath = sandbox.canonicalPath
return generateSequence(workingDir) { directory ->
directory.parentFile?.takeIf {
it.path == sandboxPath || it.path.startsWith(sandboxPath + File.separator)
}
}.firstOrNull { File(it, ".git").exists() }
}
private fun readConfig(git: File, inspectionRoot: File): Map<String, String> {
val userNameResult = run(git, inspectionRoot, listOf("config", "--get", "user.name"))
val userEmailResult = run(git, inspectionRoot, listOf("config", "--get", "user.email"))
return buildMap {
userNameResult.outputLines.firstOrNull()
?.takeIf { userNameResult.exitCode == 0 && it.isNotBlank() }
?.let { put("user.name", it) }
userEmailResult.outputLines.firstOrNull()
?.takeIf { userEmailResult.exitCode == 0 && it.isNotBlank() }
?.let { put("user.email", it) }
}
}
private fun inspectRemoteRefs(git: File, inspectionRoot: File, remoteLines: List<String>): RemoteRefs {
val remotes = remoteLines.mapNotNull { line ->
val parts = line.trim().split(Regex("\\s+"))

View File

@@ -128,8 +128,8 @@ class GitRepositoryRuntime private constructor(
?: helperCommands.execute(sandboxRoot, workingDir, currentRepo, expandedTokens)
}
val inspectedRepo = inspectSandbox(level, result.first.currentDir).copy(currentDir = result.first.currentDir)
return inspectedRepo to result.second
val refreshedRepo = refreshRepoAfterCommand(level, currentRepo, result.first, expandedTokens)
return refreshedRepo to result.second
}
fun completionCandidates(level: Level, currentRepo: RepoState, directoriesOnly: Boolean): List<String> {
@@ -236,6 +236,72 @@ class GitRepositoryRuntime private constructor(
private fun inspectSandbox(level: Level, currentDir: String = "."): RepoState =
repositoryInspector.inspect(sandboxDir(level), currentDir)
private fun refreshRepoAfterCommand(
level: Level,
previousRepo: RepoState,
commandRepo: RepoState,
tokens: List<String>,
): RepoState {
if (tokens.isEmpty()) return previousRepo
if (!commandNeedsStateRefresh(tokens)) return commandRepo
if (tokens.isGitConfigCommand()) {
return commandRepo.copy(
config = repositoryInspector.inspectConfig(sandboxDir(level), commandRepo.currentDir),
)
}
return inspectSandbox(level, commandRepo.currentDir).copy(currentDir = commandRepo.currentDir)
}
private fun commandNeedsStateRefresh(tokens: List<String>): Boolean {
return when (tokens.firstOrNull()) {
"git" -> tokens.gitCommandNeedsStateRefresh()
"sh" -> true
"touch", "mkdir", "md", "rm", "del" -> true
"echo" -> tokens.any { it == ">" || it == ">>" }
else -> tokens.firstOrNull()?.startsWith("./") == true
}
}
private fun List<String>.gitCommandNeedsStateRefresh(): Boolean {
val command = drop(1).firstOrNull { it != "-C" && !it.startsWith("--git-dir=") && !it.startsWith("--work-tree=") }
?: return false
return when (command) {
"init",
"config",
"add",
"stage",
"rm",
"mv",
"commit",
"commit-tree",
"checkout",
"switch",
"restore",
"branch",
"tag",
"remote",
"fetch",
"pull",
"push",
"merge",
"rebase",
"reset",
"revert",
"stash",
"cherry-pick",
"repack",
"submodule",
"update-ref",
"read-tree",
-> true
else -> false
}
}
private fun List<String>.isGitConfigCommand(): Boolean {
return firstOrNull() == "git" && drop(1).firstOrNull { !it.startsWith("-") } == "config"
}
private fun nativeGitBinary(): File? {
return nativeGitOverride
?.takeIf { it.exists() && it.canExecute() }

View File

@@ -258,6 +258,48 @@ class GitSandboxEngineTest {
}
}
@Test
fun nativeConfigLevelSolvesAfterSettingNameAndEmail() {
val git = testGitBinary()
assumeTrue(git.exists() && git.canExecute())
val root = Files.createTempDirectory("githug-config-level").toFile()
try {
val runtime = GitRepositoryRuntime(root, git)
val level = configLevel()
val repo = runtime.prepareLevel(level)
val (namedRepo, _) = runtime.execute(level, repo, "git config user.name GitHug")
val (configuredRepo, _) = runtime.execute(level, namedRepo, "git config user.email githug@example.com")
assertEquals("GitHug", configuredRepo.config["user.name"])
assertEquals("githug@example.com", configuredRepo.config["user.email"])
assertTrue(level.validator(configuredRepo, "git config user.email githug@example.com"))
} finally {
root.deleteRecursively()
}
}
@Test
fun nativeReadOnlyHelperCommandsDoNotRefreshAwayTransientState() {
val git = testGitBinary()
assumeTrue(git.exists() && git.canExecute())
val root = Files.createTempDirectory("githug-read-only-helper").toFile()
try {
val runtime = GitRepositoryRuntime(root, git)
val level = configLevel()
val repo = runtime.prepareLevel(level).copy(
maintenanceActions = setOf("transient-marker"),
)
val (listedRepo, output) = runtime.execute(level, repo, "ls")
assertTrue(".git" in output)
assertEquals(setOf("transient-marker"), listedRepo.maintenanceActions)
} finally {
root.deleteRecursively()
}
}
@Test
fun nativeGitExecAliasesRefreshWhenBinaryChanges() {
val git = testGitBinary()