Files
Githug-Android/app/src/main/java/solutions/tretter/githugandroid/RepoStateSaver.kt
Joe Tretter 943bf03ca0 Route interactive Git through native terminal sessions
Remove Kotlin interactive-add emulation and run patch add via the native Git runtime with PTY-backed session IO.
2026-06-27 13:07:35 -05:00

128 lines
5.5 KiB
Kotlin

package solutions.tretter.githugandroid
import androidx.compose.runtime.saveable.listSaver
val RepoStateSaver = listSaver<RepoState, Any>(
save = { state ->
listOf(
state.initialized,
state.headBranch,
state.files.flatMap {
listOf(
it.name,
it.content,
it.staged.toString(),
it.tracked.toString(),
it.deleted.toString(),
it.stagedContent.orEmpty(),
)
},
state.commits.flatMap {
listOf(
it.id,
it.message,
it.authorTimestampSeconds?.toString().orEmpty(),
it.parentCount.toString(),
)
},
state.branches.flatMap { listOf(it.key, it.value.toString()) },
state.currentDir,
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(),
state.fetchHeadCount,
emptyList<Any>(),
emptyList<Any>(),
)
},
restore = { saved ->
val initialized = saved[0] as Boolean
val headBranch = saved[1] as String
val fileParts = saved[2] as List<*>
val commitParts = saved[3] as List<*>
val branchParts = saved[4] as List<*>
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>()
val fetchHeadCount = saved.getOrNull(15) as? Int ?: 0
RepoState(
initialized = initialized,
headBranch = headBranch,
files = fileParts.chunked(
when {
fileParts.size % 6 == 0 -> 6
fileParts.size % 5 == 0 -> 5
else -> 4
},
).map {
GitFile(
name = it[0] as String,
content = it[1] as String,
staged = (it[2] as String).toBoolean(),
tracked = (it[3] as String).toBoolean(),
deleted = (it.getOrNull(4) as? String)?.toBoolean() ?: false,
stagedContent = (it.getOrNull(5) as? String)?.takeIf { value -> value.isNotEmpty() },
)
},
commits = commitParts.restoreCommitNodes(),
branches = branchParts.chunked(2).associate { (it[0] as String) to (it[1] as String).toInt() },
currentDir = saved[5] as String,
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(),
fetchHeadCount = fetchHeadCount,
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(),
)
}
)
private fun List<*>.restoreCommitNodes(): List<CommitNode> {
val hasTimestampAndParentColumns = size % 4 == 0 && chunked(4).all { chunk ->
val timestamp = chunk.getOrNull(2) as? String
val parentCount = chunk.getOrNull(3) as? String
(timestamp.isNullOrEmpty() || timestamp.toLongOrNull()?.let { it >= 100_000_000L } == true) &&
parentCount?.toIntOrNull() != null
}
val hasTimestampColumn = !hasTimestampAndParentColumns && size % 3 == 0 && chunked(3).all { chunk ->
val timestamp = chunk.getOrNull(2) as? String
timestamp.isNullOrEmpty() || timestamp.toLongOrNull()?.let { it >= 100_000_000L } == true
}
val width = when {
hasTimestampAndParentColumns -> 4
hasTimestampColumn -> 3
else -> 2
}
return chunked(width).mapNotNull { chunk ->
val id = chunk.getOrNull(0) as? String ?: return@mapNotNull null
val message = chunk.getOrNull(1) as? String ?: ""
CommitNode(
id = id,
message = message,
authorTimestampSeconds = if (hasTimestampAndParentColumns || hasTimestampColumn) {
(chunk.getOrNull(2) as? String)?.toLongOrNull()
} else {
null
},
parentCount = if (hasTimestampAndParentColumns) (chunk.getOrNull(3) as? String)?.toIntOrNull() ?: 0 else 0,
)
}
}