neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
Give the Android app a versionCode that moves
74e1fb0 · Gabriel Morell · 2026-09-12 20:04
Message
{commit_body(@commit)}
Files changed
modified
android/app/build.gradle.kts
+19
−2
@@ -5,6 +5,10 @@ plugins {
| 5 | 5 | alias(libs.plugins.kotlin.serialization) |
| 6 | 6 | } |
| 7 | 7 | |
| 8 | +// Bumped by hand for a real release; the build metadata after it comes | |
| 9 | +// from CI. | |
| 10 | +val baseVersion = "0.1.0" | |
| 11 | + | |
| 8 | 12 | android { |
| 9 | 13 | namespace = "org.neiam.gitgud.app" |
| 10 | 14 | compileSdk = 34 |
@@ -13,8 +17,21 @@ android {
| 13 | 17 | applicationId = "org.neiam.gitgud.app" |
| 14 | 18 | minSdk = 26 |
| 15 | 19 | targetSdk = 34 |
| 16 | − versionCode = 1 | |
| 17 | − versionName = "0.1.0" | |
| 20 | + // Android refuses an update whose versionCode isn't higher than | |
| 21 | + // what's installed, and F-Droid orders versions by it — so a | |
| 22 | + // hardcoded 1 means no phone ever sees a second release. It also | |
| 23 | + // means every CI build publishes the same filename with | |
| 24 | + // different bytes, which the registry correctly refuses with a | |
| 25 | + // 409. | |
| 26 | + // | |
| 27 | + // The CI run number is monotonic and needs no state kept | |
| 28 | + // anywhere. Local builds stay at 1, which is fine: they are | |
| 29 | + // never published. | |
| 30 | + versionCode = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 1 | |
| 31 | + | |
| 32 | + // The commit is what you want when someone reports a bug | |
| 33 | + // against a build. Local builds just say "dev". | |
| 34 | + versionName = "$baseVersion+" + ((System.getenv("GITHUB_SHA") ?: "dev").take(7)) | |
| 18 | 35 | } |
| 19 | 36 | |
| 20 | 37 | buildTypes { |
modified
lib/git_gud/packages/formats/fdroid.ex
+11
−1
@@ -461,8 +461,18 @@ defmodule GitGud.Packages.Formats.Fdroid do
| 461 | 461 | "filename" => Apk.filename(info) |
| 462 | 462 | })} |
| 463 | 463 | |
| 464 | + # The generic "a file with that name is already published" | |
| 465 | + # doesn't point anywhere useful here, and this has exactly one | |
| 466 | + # cause worth naming: a versionCode that didn't move. Android | |
| 467 | + # refuses an update that doesn't increase it and F-Droid orders | |
| 468 | + # by it, so a repeated one is a bug in the build rather than a | |
| 469 | + # publishing accident. | |
| 464 | 470 | {:error, :file_exists} -> |
| 465 | − {:error, :file_exists} | |
| 471 | + {:error, | |
| 472 | + {409, | |
| 473 | + "#{Apk.filename(info)} is already published with different content — " <> | |
| 474 | + "versionCode #{info.version_code} has been used before. Bump it: Android " <> | |
| 475 | + "won't install an update whose versionCode hasn't increased."}} | |
| 466 | 476 | |
| 467 | 477 | {:error, reason} -> |
| 468 | 478 | {:error, {400, inspect(reason)}} |
modified
test/git_gud/packages/fdroid_format_test.exs
+21
−0
@@ -116,6 +116,27 @@ defmodule GitGud.Packages.Formats.FdroidTest do
| 116 | 116 | assert file.metadata["nativecode"] == ["arm64-v8a"] |
| 117 | 117 | end |
| 118 | 118 | |
| 119 | + test "republishing a versionCode says which one, and why", %{repo: repo} do | |
| 120 | + apk = build_apk() | |
| 121 | + {:ok, _, _} = publish(repo, apk) | |
| 122 | + | |
| 123 | + # Same versionCode, different bytes — which is what a CI build | |
| 124 | + # with a hardcoded versionCode produces on its second run. | |
| 125 | + different = build_apk([{~c"extra.txt", "changes the digest"}]) | |
| 126 | + | |
| 127 | + assert {:error, {409, message}} = publish(repo, different) | |
| 128 | + assert message =~ "org.neiam.gitgud.app_1.apk" | |
| 129 | + assert message =~ "versionCode 1" | |
| 130 | + assert message =~ "won't install an update" | |
| 131 | + end | |
| 132 | + | |
| 133 | + test "republishing identical bytes is fine", %{repo: repo} do | |
| 134 | + apk = build_apk() | |
| 135 | + assert {:ok, _, _} = publish(repo, apk) | |
| 136 | + # A re-run that produced the same artifact is not a conflict. | |
| 137 | + assert {:ok, _, _} = publish(repo, apk) | |
| 138 | + end | |
| 139 | + | |
| 119 | 140 | test "refuses something that isn't an APK", %{repo: repo} do |
| 120 | 141 | assert {:error, {422, _}} = publish(repo, "definitely not a zip") |
| 121 | 142 | end |
Parents: 1d76240