Refresh native Git helper aliases after app upgrade

- Rebuild the app-private git-exec helper directory when the packaged native Git binary changes.
- Remove stale helper aliases before recreating symlinks/copies.
- Add regression coverage for refreshing cached Git exec aliases across binary changes.
This commit is contained in:
Joe Tretter
2026-05-11 21:48:14 -05:00
parent 4bac818571
commit 75c8396f00
3 changed files with 54 additions and 2 deletions

View File

@@ -19,8 +19,8 @@ android {
applicationId = "solutions.tretter.githugandroid" applicationId = "solutions.tretter.githugandroid"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 136 versionCode = 137
versionName = "0.1.135" versionName = "0.1.136"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true

View File

@@ -852,6 +852,7 @@ class GitRepositoryRuntime private constructor(
} else { } else {
File(binary.parentFile ?: binary.absoluteFile.parentFile ?: sandboxesRoot, "git-exec") File(binary.parentFile ?: binary.absoluteFile.parentFile ?: sandboxesRoot, "git-exec")
} }
refreshGitExecDirectoryIfNeeded(directory, binary)
directory.mkdirs() directory.mkdirs()
File(directory, "git").also { alias -> File(directory, "git").also { alias ->
@@ -870,7 +871,27 @@ class GitRepositoryRuntime private constructor(
return directory return directory
} }
private fun refreshGitExecDirectoryIfNeeded(directory: File, binary: File) {
val fingerprint = buildString {
append(binary.absolutePath)
append('\n')
append(binary.length())
append('\n')
append(binary.lastModified())
}
val stamp = File(directory, ".binary-fingerprint")
val previousFingerprint = stamp.takeIf { it.isFile }?.readText()
if (previousFingerprint != fingerprint) {
directory.deleteRecursively()
}
directory.mkdirs()
stamp.writeText(fingerprint)
}
private fun createGitAlias(binary: File, alias: File) { private fun createGitAlias(binary: File, alias: File) {
if (alias.exists() || Files.isSymbolicLink(alias.toPath())) {
alias.delete()
}
if (context != null) { if (context != null) {
try { try {
Os.symlink(binary.absolutePath, alias.absolutePath) Os.symlink(binary.absolutePath, alias.absolutePath)

View File

@@ -185,6 +185,37 @@ class GitSandboxEngineTest {
} }
} }
@Test
fun nativeGitExecAliasesRefreshWhenBinaryChanges() {
val git = testGitBinary()
assumeTrue(git.exists() && git.canExecute())
val root = Files.createTempDirectory("githug-exec-refresh").toFile()
try {
val firstGit = File(root, "first-git").also { git.copyTo(it); it.setExecutable(true) }
val secondGit = File(root, "second-git").also { git.copyTo(it); it.setExecutable(true) }
val level = level(
id = "exec-refresh-test",
title = "Exec Refresh Test",
description = "",
hints = emptyList(),
commandSuggestions = emptyList(),
setup = { RepoState(initialized = true) },
validator = { _, _ -> false },
)
GitRepositoryRuntime(root, firstGit).prepareLevel(level)
val alias = File(root, "git-exec/git")
assertTrue(alias.exists())
assertTrue(File(root, "git-exec/.binary-fingerprint").readText().contains(firstGit.absolutePath))
GitRepositoryRuntime(root, secondGit).prepareLevel(level)
assertTrue(alias.exists())
assertTrue(File(root, "git-exec/.binary-fingerprint").readText().contains(secondGit.absolutePath))
} finally {
root.deleteRecursively()
}
}
@Test @Test
fun nativePushLevelHasDivergedStatusAndRebasesCleanly() { fun nativePushLevelHasDivergedStatusAndRebasesCleanly() {
val git = testGitBinary() val git = testGitBinary()