Add runtime diagnostic logging for level setup, native Git calls, inspection, validation, and UI command flow

This commit is contained in:
Joe Tretter
2026-06-25 13:53:51 -05:00
parent 035851ac05
commit 60dd3c82f7
8 changed files with 215 additions and 53 deletions

View File

@@ -81,6 +81,15 @@ Test outputs and logs are written under project-local build directories:
- Per-test emulator logcat files: `app/build/outputs/androidTest-results/connected/debug/<device-name>/logcat-*.txt` - Per-test emulator logcat files: `app/build/outputs/androidTest-results/connected/debug/<device-name>/logcat-*.txt`
- Emulator startup log: `build/reports/android-emulator.log` - Emulator startup log: `build/reports/android-emulator.log`
Runtime diagnostics are emitted to Android logcat with the `GitHugAndroid` tag. To capture a focused trace from a device or emulator:
```bash
android-sdk/platform-tools/adb logcat -c
android-sdk/platform-tools/adb logcat -v time -s GitHugAndroid:D '*:S'
```
The trace includes level loading and preparation timings, native Git argv/cwd/exit/output previews, repository inspection timings, validation snapshots, and the final app decision for each submitted command. For level-resolution issues, compare the `GitRuntime`, `GitProcess`, `GitInspector`, `Validation`, and `GitHugApp` lines around the submitted command.
To build installable/debuggable artifacts: To build installable/debuggable artifacts:
```bash ```bash

View File

@@ -20,8 +20,8 @@ android {
applicationId = "solutions.tretter.githugandroid" applicationId = "solutions.tretter.githugandroid"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 176 versionCode = 177
versionName = "0.1.175" versionName = "0.1.176"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true

View File

@@ -13,3 +13,45 @@ object AppLog {
Log.e(TAG, "[$area] $message", error) Log.e(TAG, "[$area] $message", error)
} }
} }
internal fun elapsedMillisSince(startNanos: Long): Long =
(System.nanoTime() - startNanos) / 1_000_000L
internal fun RepoState.diagnosticSnapshot(): String = buildString {
append("initialized=")
append(initialized)
append(", headBranch=")
append(headBranch)
append(", currentDir=")
append(currentDir)
append(", branches=")
append(branches.keys.sorted())
append(", tags=")
append(tags.sorted())
append(", remotes=")
append(remotes.toSortedMap())
append(", fetchedBranches=")
append(fetchedBranches.sorted())
append(", fetchHeadCount=")
append(fetchHeadCount)
append(", pushedBranches=")
append(pushedBranches.sorted())
append(", pushedTags=")
append(pushedTags.sorted())
append(", stashes=")
append(stashes)
append(", submodules=")
append(submodules.toSortedMap())
append(", maintenanceActions=")
append(maintenanceActions.sorted())
append(", config=")
append(config.toSortedMap())
append(", files=")
append(files.map { file ->
"${file.name}(staged=${file.staged},tracked=${file.tracked},deleted=${file.deleted})"
}.sorted())
append(", commits=")
append(commits.map { commit ->
"(${commit.id}, parents=${commit.parentCount}, message=${commit.message})"
})
}

View File

@@ -111,8 +111,10 @@ fun GitHugApp() {
} }
LaunchedEffect(currentLevelIndex) { LaunchedEffect(currentLevelIndex) {
AppLog.d("GitHugApp", "Current level index changed to $currentLevelIndex id=${levels[currentLevelIndex].id}")
delay(100) delay(100)
screenScrollState.animateScrollTo(0) screenScrollState.animateScrollTo(0)
AppLog.d("GitHugApp", "Scrolled to top for level index=$currentLevelIndex id=${levels[currentLevelIndex].id}")
} }
fun updatePaneLayout(transform: (PaneLayout) -> PaneLayout, persist: Boolean = true) { fun updatePaneLayout(transform: (PaneLayout) -> PaneLayout, persist: Boolean = true) {
@@ -214,7 +216,9 @@ fun GitHugApp() {
} }
fun resetCurrentLevel(message: String = "Level reset.") { fun resetCurrentLevel(message: String = "Level reset.") {
val startedAt = System.nanoTime()
val resetLevelId = currentLevel.id val resetLevelId = currentLevel.id
AppLog.d("GitHugApp", "Reset requested level=$resetLevelId")
val updatedCompletedLevels = completedLevels - resetLevelId val updatedCompletedLevels = completedLevels - resetLevelId
completedLevels = updatedCompletedLevels completedLevels = updatedCompletedLevels
scope.launch { persistProgress(updatedCompletedLevels, resetLevelId) } scope.launch { persistProgress(updatedCompletedLevels, resetLevelId) }
@@ -231,6 +235,10 @@ fun GitHugApp() {
gitMessageEditorState = null gitMessageEditorState = null
manPageState = null manPageState = null
applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(listOf(message))) applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(listOf(message)))
AppLog.d(
"GitHugApp",
"Reset finished level=$resetLevelId durationMs=${elapsedMillisSince(startedAt)} repo=${repo.diagnosticSnapshot()}",
)
} }
fun loadLevel( fun loadLevel(
@@ -239,6 +247,7 @@ fun GitHugApp() {
keepSuppressedImeEcho: Boolean = false, keepSuppressedImeEcho: Boolean = false,
prepareInBackground: Boolean = false, prepareInBackground: Boolean = false,
) { ) {
val startedAt = System.nanoTime()
val level = levels[index] val level = levels[index]
val requestId = levelLoadRequestId + 1 val requestId = levelLoadRequestId + 1
levelLoadRequestId = requestId levelLoadRequestId = requestId
@@ -260,21 +269,46 @@ fun GitHugApp() {
if (prepareInBackground) { if (prepareInBackground) {
isPreparingLevel = true isPreparingLevel = true
repo = RepoState() repo = RepoState()
AppLog.d(
"GitHugApp",
"Level load state updated index=$index id=${level.id} requestId=$requestId durationMs=${elapsedMillisSince(startedAt)} preparing=true",
)
scope.launch { scope.launch {
val prepareStartedAt = System.nanoTime()
AppLog.d("GitHugApp", "Background prepare started index=$index id=${level.id} requestId=$requestId")
val preparedRepo = withContext(Dispatchers.Default) { val preparedRepo = withContext(Dispatchers.Default) {
runtime.prepareLevel(level) runtime.prepareLevel(level)
} }
val prepareMs = elapsedMillisSince(prepareStartedAt)
if (levelLoadRequestId == requestId) { if (levelLoadRequestId == requestId) {
AppLog.d("GitHugApp", "Prepared level index=$index id=${level.id}") AppLog.d(
"GitHugApp",
"Background prepare applying index=$index id=${level.id} requestId=$requestId prepareMs=$prepareMs " +
"repo=${preparedRepo.diagnosticSnapshot()}",
)
repo = preparedRepo repo = preparedRepo
output = message output = message
isPreparingLevel = false isPreparingLevel = false
clearCommandInput(recreateField = true) clearCommandInput(recreateField = true)
AppLog.d(
"GitHugApp",
"Background prepare applied index=$index id=${level.id} requestId=$requestId totalMs=${elapsedMillisSince(startedAt)}",
)
} else {
AppLog.d(
"GitHugApp",
"Background prepare discarded index=$index id=${level.id} requestId=$requestId activeRequestId=$levelLoadRequestId prepareMs=$prepareMs",
)
} }
} }
} else { } else {
isPreparingLevel = false isPreparingLevel = false
val prepareStartedAt = System.nanoTime()
repo = runtime.prepareLevel(level) repo = runtime.prepareLevel(level)
AppLog.d(
"GitHugApp",
"Synchronous prepare applied index=$index id=${level.id} prepareMs=${elapsedMillisSince(prepareStartedAt)}",
)
} }
paneLayout = paneLayout.copy( paneLayout = paneLayout.copy(
weights = paneLayout.weights + recommendedPaneWeights( weights = paneLayout.weights + recommendedPaneWeights(
@@ -288,6 +322,10 @@ fun GitHugApp() {
), ),
) )
) )
AppLog.d(
"GitHugApp",
"Level load returned index=$index id=${level.id} requestId=$requestId durationMs=${elapsedMillisSince(startedAt)} preparing=$isPreparingLevel",
)
} }
fun setCommandText(text: String) { fun setCommandText(text: String) {
@@ -348,12 +386,14 @@ fun GitHugApp() {
} }
fun applyCommandResult(raw: String, newRepo: RepoState, lines: List<String>, echoCommand: Boolean) { fun applyCommandResult(raw: String, newRepo: RepoState, lines: List<String>, echoCommand: Boolean) {
val startedAt = System.nanoTime()
val levelForResult = currentLevel val levelForResult = currentLevel
val solvedAfterCommand = levelForResult.validator(newRepo, raw) val solvedAfterCommand = levelForResult.validator(newRepo, raw)
val wasAlreadyCompleted = currentLevel.id in completedLevels val wasAlreadyCompleted = currentLevel.id in completedLevels
AppLog.d( AppLog.d(
"GitHugApp", "GitHugApp",
"Command='$raw' level=${levelForResult.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}", "Command='$raw' level=${levelForResult.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted " +
"completedBefore=${completedLevels.sorted()} outputLineCount=${lines.size} repo=${newRepo.diagnosticSnapshot()}",
) )
val newOutput = buildList { val newOutput = buildList {
addAll(output) addAll(output)
@@ -396,6 +436,10 @@ fun GitHugApp() {
output = newOutput output = newOutput
applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput)) applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(newOutput))
} }
AppLog.d(
"GitHugApp",
"Command result applied level=${levelForResult.id} solved=$solvedAfterCommand durationMs=${elapsedMillisSince(startedAt)}",
)
} }
fun openEditor(invocation: VisualEditorInvocation) { fun openEditor(invocation: VisualEditorInvocation) {
@@ -480,16 +524,23 @@ fun GitHugApp() {
} }
fun runCommand() { fun runCommand() {
val startedAt = System.nanoTime()
val submittedText = commandInput.text val submittedText = commandInput.text
val raw = submittedText.trim() val raw = submittedText.trim()
if (raw.isBlank()) return if (raw.isBlank()) return
if (isPreparingLevel) { if (isPreparingLevel) {
AppLog.d("GitHugApp", "Command blocked while preparing level=${currentLevel.id} raw='$raw'")
output = output + "Still preparing ${currentLevel.title}. Try again in a moment." output = output + "Still preparing ${currentLevel.title}. Try again in a moment."
clearCommandInput(recreateField = true) clearCommandInput(recreateField = true)
applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(output)) applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(output))
return return
} }
val submittedLevelId = currentLevel.id
val isInteractiveInput = repo.interactiveAddSession != null val isInteractiveInput = repo.interactiveAddSession != null
AppLog.d(
"GitHugApp",
"Command submitted level=$submittedLevelId raw='$raw' interactive=$isInteractiveInput repo=${repo.diagnosticSnapshot()}",
)
showHelpOverlay = false showHelpOverlay = false
showTerminalInputHint = false showTerminalInputHint = false
@@ -529,6 +580,7 @@ fun GitHugApp() {
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw) val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
applyCommandResult(raw, newRepo, lines, echoCommand = !isInteractiveInput) applyCommandResult(raw, newRepo, lines, echoCommand = !isInteractiveInput)
AppLog.d("GitHugApp", "Command handling finished level=$submittedLevelId raw='$raw' durationMs=${elapsedMillisSince(startedAt)}")
} }
fun showCommandHelp() { fun showCommandHelp() {

View File

@@ -54,11 +54,27 @@ internal class GitProcessRunner(
arguments: List<String>, arguments: List<String>,
environment: Map<String, String> = emptyMap(), environment: Map<String, String> = emptyMap(),
): ProcessExecutionResult { ): ProcessExecutionResult {
if (context != null) { val startedAt = System.nanoTime()
gitExecDirectory(binary) val execStartedAt = System.nanoTime()
return NativeGitBridge.runGitMain(binary, workingDir, arguments, gitEnvironment(binary, workingDir, environment)) val gitExecPath = gitExecDirectory(binary)
val execDirectoryMs = elapsedMillisSince(execStartedAt)
val fullEnvironment = gitEnvironment(binary, workingDir, environment, gitExecPath)
AppLog.d(
"GitProcess",
"runGit start cwd=${workingDir.absolutePath} argv=${formatGitArgv(arguments)} " +
"extraEnvKeys=${environment.keys.sorted()} execDirectoryMs=$execDirectoryMs",
)
val result = if (context != null) {
NativeGitBridge.runGitMain(binary, workingDir, arguments, fullEnvironment)
} else {
runProcess(binary, workingDir, arguments, fullEnvironment)
} }
return runProcess(binary, workingDir, arguments, environment) AppLog.d(
"GitProcess",
"runGit finish exit=${result.exitCode} durationMs=${elapsedMillisSince(startedAt)} " +
"output=${formatOutputPreview(result.outputLines)}",
)
return result
} }
fun runShellProcess(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult { fun runShellProcess(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult {
@@ -84,15 +100,14 @@ internal class GitProcessRunner(
binary: File, binary: File,
workingDir: File, workingDir: File,
arguments: List<String>, arguments: List<String>,
extraEnvironment: Map<String, String> = emptyMap(), environment: Map<String, String>,
): ProcessExecutionResult { ): ProcessExecutionResult {
return try { return try {
val gitExecPath = gitExecDirectory(binary)
val process = ProcessBuilder(listOf(binary.absolutePath) + arguments) val process = ProcessBuilder(listOf(binary.absolutePath) + arguments)
.directory(workingDir) .directory(workingDir)
.redirectErrorStream(true) .redirectErrorStream(true)
.apply { .apply {
environment().putAll(gitEnvironment(binary, workingDir, extraEnvironment, gitExecPath)) environment().putAll(environment)
} }
.start() .start()
@@ -192,6 +207,25 @@ internal class GitProcessRunner(
} }
alias.setExecutable(true, false) alias.setExecutable(true, false)
} }
private fun formatGitArgv(arguments: List<String>): String =
(listOf("git") + arguments).joinToString(" ") { argument ->
if (argument.any { it.isWhitespace() || it == '"' || it == '\'' }) {
"'" + argument.replace("'", "'\\''") + "'"
} else {
argument
}
}
private fun formatOutputPreview(lines: List<String>): String {
if (lines.isEmpty()) return "[]"
val preview = lines
.take(8)
.joinToString(" | ")
.take(1_000)
val suffix = if (lines.size > 8) " ... (${lines.size} lines)" else " (${lines.size} lines)"
return "[$preview]$suffix"
}
} }
internal data class ProcessExecutionResult( internal data class ProcessExecutionResult(

View File

@@ -7,33 +7,50 @@ internal class GitRepositoryInspector(
private val runGit: (File, File, List<String>, Map<String, String>) -> ProcessExecutionResult, private val runGit: (File, File, List<String>, Map<String, String>) -> ProcessExecutionResult,
) { ) {
fun inspectConfig(sandbox: File, currentDir: String = "."): Map<String, String> { fun inspectConfig(sandbox: File, currentDir: String = "."): Map<String, String> {
val startedAt = System.nanoTime()
val git = nativeGit() val git = nativeGit()
val workingDir = File(sandbox, currentDir).canonicalFile val workingDir = File(sandbox, currentDir).canonicalFile
.takeIf { it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator) } .takeIf { it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator) }
?: sandbox ?: sandbox
val repositoryRoot = repositoryRoot(sandbox, workingDir) ?: sandbox val repositoryRoot = repositoryRoot(sandbox, workingDir) ?: sandbox
return readConfig(git, repositoryRoot) val config = readConfig(git, repositoryRoot)
AppLog.d(
"GitInspector",
"inspectConfig sandbox=${sandbox.name} currentDir=$currentDir durationMs=${elapsedMillisSince(startedAt)} config=${config.toSortedMap()}",
)
return config
} }
fun inspect(sandbox: File, currentDir: String = "."): RepoState { fun inspect(sandbox: File, currentDir: String = "."): RepoState {
val startedAt = System.nanoTime()
val git = nativeGit() val git = nativeGit()
val workingDir = File(sandbox, currentDir).canonicalFile val workingDir = File(sandbox, currentDir).canonicalFile
.takeIf { it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator) } .takeIf { it.path == sandbox.canonicalPath || it.path.startsWith(sandbox.canonicalPath + File.separator) }
?: sandbox ?: sandbox
val repositoryRoot = repositoryRoot(sandbox, workingDir) val repositoryRoot = repositoryRoot(sandbox, workingDir)
val inspectionRoot = repositoryRoot ?: sandbox val inspectionRoot = repositoryRoot ?: sandbox
AppLog.d(
"GitInspector",
"inspect start sandbox=${sandbox.name} currentDir=$currentDir workingDir=${workingDir.absolutePath} " +
"repositoryRoot=${repositoryRoot?.absolutePath ?: "<none>"}",
)
val filesOnDisk = inspectionRoot.walkTopDown() val filesOnDisk = inspectionRoot.walkTopDown()
.filter { it.isFile && !it.relativeTo(inspectionRoot).path.startsWith(".git/") } .filter { it.isFile && !it.relativeTo(inspectionRoot).path.startsWith(".git/") }
.orEmpty() .orEmpty()
.toList() .toList()
if (repositoryRoot == null) { if (repositoryRoot == null) {
return RepoState( val repo = RepoState(
initialized = false, initialized = false,
files = filesOnDisk.map { files = filesOnDisk.map {
GitFile(name = it.relativeTo(inspectionRoot).path, content = it.readText()) GitFile(name = it.relativeTo(inspectionRoot).path, content = it.readText())
}, },
) )
AppLog.d(
"GitInspector",
"inspect finish sandbox=${sandbox.name} durationMs=${elapsedMillisSince(startedAt)} repo=${repo.diagnosticSnapshot()}",
)
return repo
} }
val statusResult = run(git, inspectionRoot, listOf("status", "--porcelain")) val statusResult = run(git, inspectionRoot, listOf("status", "--porcelain"))
@@ -124,7 +141,7 @@ internal class GitRepositoryInspector(
?.let { "tags/$it" } ?.let { "tags/$it" }
?: "DETACHED" ?: "DETACHED"
return RepoState( val repo = RepoState(
initialized = true, initialized = true,
files = filesOnDisk.map { file -> files = filesOnDisk.map { file ->
val relativePath = file.relativeTo(inspectionRoot).path val relativePath = file.relativeTo(inspectionRoot).path
@@ -172,6 +189,11 @@ internal class GitRepositoryInspector(
submodules = submodules, submodules = submodules,
maintenanceActions = maintenanceActions, maintenanceActions = maintenanceActions,
) )
AppLog.d(
"GitInspector",
"inspect finish sandbox=${sandbox.name} durationMs=${elapsedMillisSince(startedAt)} repo=${repo.diagnosticSnapshot()}",
)
return repo
} }
private fun run( private fun run(

View File

@@ -68,14 +68,19 @@ class GitRepositoryRuntime private constructor(
} }
fun prepareLevel(level: Level): RepoState { fun prepareLevel(level: Level): RepoState {
val startedAt = System.nanoTime()
AppLog.d("GitRuntime", "Preparing level id=${level.id} title=${level.title}") AppLog.d("GitRuntime", "Preparing level id=${level.id} title=${level.title}")
val nativeGit = requireNativeGit() val nativeGit = requireNativeGit()
val sandbox = sandboxDir(level) val sandbox = sandboxDir(level)
val resetStartedAt = System.nanoTime()
sandbox.deleteRecursively() sandbox.deleteRecursively()
sandbox.mkdirs() sandbox.mkdirs()
val resetMs = elapsedMillisSince(resetStartedAt)
val desired = level.setup() val desired = level.setup()
AppLog.d("GitRuntime", "Level setup desired id=${level.id} repo=${desired.diagnosticSnapshot()}")
val filesStartedAt = System.nanoTime()
desired.files.forEach { file -> desired.files.forEach { file ->
File(sandbox, file.name).apply { File(sandbox, file.name).apply {
parentFile?.mkdirs() parentFile?.mkdirs()
@@ -83,8 +88,10 @@ class GitRepositoryRuntime private constructor(
} }
} }
File(sandbox, desired.currentDir).mkdirs() File(sandbox, desired.currentDir).mkdirs()
val filesMs = elapsedMillisSince(filesStartedAt)
val needsGit = desired.initialized || desired.files.any { it.staged || it.tracked } || desired.commits.isNotEmpty() val needsGit = desired.initialized || desired.files.any { it.staged || it.tracked } || desired.commits.isNotEmpty()
val materializeStartedAt = System.nanoTime()
if (needsGit) { if (needsGit) {
val initResult = runGit(nativeGit, sandbox, listOf("init", "-b", desired.headBranch)) val initResult = runGit(nativeGit, sandbox, listOf("init", "-b", desired.headBranch))
if (initResult.exitCode != 0) { if (initResult.exitCode != 0) {
@@ -94,12 +101,22 @@ class GitRepositoryRuntime private constructor(
levelMaterializer.materialize(nativeGit, sandbox, desired, level) levelMaterializer.materialize(nativeGit, sandbox, desired, level)
} }
val materializeMs = elapsedMillisSince(materializeStartedAt)
return inspectSandbox(level, desired.currentDir).copy(currentDir = desired.currentDir) val inspectStartedAt = System.nanoTime()
val preparedRepo = inspectSandbox(level, desired.currentDir).copy(currentDir = desired.currentDir)
val inspectMs = elapsedMillisSince(inspectStartedAt)
AppLog.d(
"GitRuntime",
"Prepared level id=${level.id} durationMs=${elapsedMillisSince(startedAt)} resetMs=$resetMs " +
"filesMs=$filesMs materializeMs=$materializeMs inspectMs=$inspectMs repo=${preparedRepo.diagnosticSnapshot()}",
)
return preparedRepo
} }
fun execute(level: Level, currentRepo: RepoState, command: String): Pair<RepoState, List<String>> { fun execute(level: Level, currentRepo: RepoState, command: String): Pair<RepoState, List<String>> {
AppLog.d("GitRuntime", "Executing command for level=${level.id}: $command") val startedAt = System.nanoTime()
AppLog.d("GitRuntime", "Executing command for level=${level.id}: $command currentRepo=${currentRepo.diagnosticSnapshot()}")
val nativeGit = requireNativeGit() val nativeGit = requireNativeGit()
val sandbox = sandboxDir(level) val sandbox = sandboxDir(level)
@@ -120,15 +137,33 @@ class GitRepositoryRuntime private constructor(
} else { } else {
expandShellPathspecs(currentRepo, invocation.command) expandShellPathspecs(currentRepo, invocation.command)
} }
AppLog.d(
"GitRuntime",
"Command parsed level=${level.id} raw='$command' tokens=$tokens expanded=$expandedTokens cwd=${workingDir.absolutePath}",
)
val result = when (expandedTokens.first()) { val result = when (expandedTokens.first()) {
"git" -> currentRepo to runGit(nativeGit, workingDir, expandedTokens.drop(1), invocation.environment).outputLines "git" -> {
val gitResult = runGit(nativeGit, workingDir, expandedTokens.drop(1), invocation.environment)
AppLog.d(
"GitRuntime",
"Git command result level=${level.id} exit=${gitResult.exitCode} output=${gitResult.outputLines}",
)
currentRepo to gitResult.outputLines
}
"help", "?" -> currentRepo to commandReferenceLines() "help", "?" -> currentRepo to commandReferenceLines()
else -> helperCommands.executeExecutableShortcut(sandboxRoot, workingDir, currentRepo, expandedTokens) else -> helperCommands.executeExecutableShortcut(sandboxRoot, workingDir, currentRepo, expandedTokens)
?: helperCommands.execute(sandboxRoot, workingDir, currentRepo, expandedTokens) ?: helperCommands.execute(sandboxRoot, workingDir, currentRepo, expandedTokens)
} }
val refreshStartedAt = System.nanoTime()
val refreshedRepo = refreshRepoAfterCommand(level, currentRepo, result.first, expandedTokens) val refreshedRepo = refreshRepoAfterCommand(level, currentRepo, result.first, expandedTokens)
val refreshMs = elapsedMillisSince(refreshStartedAt)
AppLog.d(
"GitRuntime",
"Command finished level=${level.id} durationMs=${elapsedMillisSince(startedAt)} refreshMs=$refreshMs " +
"repo=${refreshedRepo.diagnosticSnapshot()}",
)
return refreshedRepo to result.second return refreshedRepo to result.second
} }

View File

@@ -131,6 +131,7 @@ private fun loggingValidator(
levelTitle: String, levelTitle: String,
validator: (RepoState, String) -> Boolean, validator: (RepoState, String) -> Boolean,
): (RepoState, String) -> Boolean = { repo, command -> ): (RepoState, String) -> Boolean = { repo, command ->
val startedAt = System.nanoTime()
AppLog.d( AppLog.d(
"Validation", "Validation",
buildString { buildString {
@@ -141,43 +142,10 @@ private fun loggingValidator(
append(" command='") append(" command='")
append(command) append(command)
append("' repo=") append("' repo=")
append(repo.validationSnapshot()) append(repo.diagnosticSnapshot())
}, },
) )
val result = validator(repo, command) val result = validator(repo, command)
AppLog.d("Validation", "Result level=$levelId passed=$result") AppLog.d("Validation", "Result level=$levelId passed=$result durationMs=${elapsedMillisSince(startedAt)}")
result result
} }
private fun RepoState.validationSnapshot(): String = buildString {
append("initialized=")
append(initialized)
append(", headBranch=")
append(headBranch)
append(", branches=")
append(branches.keys.sorted())
append(", tags=")
append(tags.sorted())
append(", remotes=")
append(remotes.toSortedMap())
append(", fetchedBranches=")
append(fetchedBranches.sorted())
append(", fetchHeadCount=")
append(fetchHeadCount)
append(", pushedBranches=")
append(pushedBranches.sorted())
append(", pushedTags=")
append(pushedTags.sorted())
append(", stashes=")
append(stashes)
append(", submodules=")
append(submodules.toSortedMap())
append(", maintenanceActions=")
append(maintenanceActions.sorted())
append(", config=")
append(config.toSortedMap())
append(", files=")
append(files.map { file -> "${file.name}(staged=${file.staged},tracked=${file.tracked})" }.sorted())
append(", commits=")
append(commits.map { it.id to it.message })
}