Add runtime diagnostic logging for level setup, native Git calls, inspection, validation, and UI command flow
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -12,4 +12,46 @@ object AppLog {
|
|||||||
fun e(area: String, message: String, error: Throwable? = null) {
|
fun e(area: String, message: String, error: Throwable? = null) {
|
||||||
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})"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 })
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user