Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ branch names, credentials, signing material, or other secrets.

- Release builds and signing are separate from PR CI. Release artifacts are created only through
the dedicated release workflow/Fastlane lane.
- Automated release PRs use a merge commit preserving the release branch history; verify both
parents and the tested tree before tagging the actual merge commit.
- Tagged releases publish signed APKs and `SHA256SUMS` to GitHub Releases. Google Play integration
is removed. Document installation from release assets; PR APKs are for testing only.
- Tagged releases scan all signed APKs with VirusTotal before publication using the release-only
Expand Down
2 changes: 1 addition & 1 deletion docs/en/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ That command lists the lanes available in the checked-out version of the project
| `bundle exec fastlane android test` | Runs `native_tests` and `android_checks`; this is the normal pre-commit command; excludes Python, Docker integration and emulator tests. |
| `bundle exec fastlane android debug_artifact` | Builds `app/build/outputs/apk/debug/app-debug.apk`. |
| `bundle exec fastlane android release_prepare version:0.1.2` | Generates EN/RU notes, increments the version and creates a release PR (requires API/token setup). |
| `bundle exec fastlane android release_finish version:0.1.2 pr:123 head:FULL_SHA` | Requires full CI, squash merges the specified PR head and tags the merged commit. |
| `bundle exec fastlane android release_finish version:0.1.2 pr:123 head:FULL_SHA` | Requires full CI, merges the specified PR head with a merge commit and tags the merged commit. |
| `bundle exec fastlane android virus_total` | Scan signed APKs in `dist/release` (or `artifacts:PATH`); requires `VIRUSTOTAL_API_KEY`, blocks on detections/API failures. |
| `bundle exec fastlane android release_artifacts` | Builds and verifies the signed release APKs and `SHA256SUMS` in `dist/release`. |

Expand Down
11 changes: 6 additions & 5 deletions docs/en/release-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Run **Actions → Prepare and merge release → Run workflow**, select `main`, and enter
an explicit new version such as `0.1.2` (without `v`). Starting this workflow authorizes
creation of a release PR, automatic squash merge after full CI, and a release tag.
creation of a release PR, automatic merge with a merge commit after full CI, and a release tag.
The existing **Release Android artifacts** workflow then builds/signs/publishes APKs.
This does not replace the [device release review](release-testing.md).

Expand Down Expand Up @@ -38,14 +38,14 @@ Keep the existing Android signing secrets for the tag workflow:
`ANDROID_SIGNING_KEY_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`,
`ANDROID_KEY_PASSWORD`. The preparation and PR checks do not receive them.

Enable squash merges. Protect `main`, require `Change scope`, `Python tests and style`,
Enable merge commits. Protect `main`, require `Change scope`, `Python tests and style`,
`Native Go tests`, `Android tests and checks`,
`Android emulator API 26 / Device tests`, and `Android emulator API 35 / Device tests`,
and disable bypass for the bot.
If policy requires human review, approve the generated PR yourself; automation does
not approve its own PR or bypass that policy. A denied merge stops finalization;
rerun the failed finalization job after approval. Merge queues are not supported by
this direct squash-merge workflow. Tag rules must permit the bot to create `v*` tags.
this direct merge workflow. Tag rules must permit the bot to create `v*` tags.

Use a separate bot account if you want its permissions distinct from your own.
This initial implementation uses a PAT; GitHub App token minting/renewal is not
Expand Down Expand Up @@ -85,9 +85,10 @@ allows PR CI and the tag-triggered release build to run normally.
are insufficient. The recorded comparison base must match the release parent. The PR number is
also recorded as a successful CI step because GitHub may remove run-to-PR
links after merge; this allows a verified retry of tag creation.
7. Recheck PR identity and unchanged `main`, then request squash merge with the
7. Recheck PR identity and unchanged `main`, then request a merge commit with the
expected head SHA. GitHub protections still apply. Verify that the resulting
commit belongs to `main` and has exactly the tested head's tree.
commit belongs to `main`, has the verified base and head as its two parents,
and has exactly the tested head's tree.
8. Create lightweight tag `vX.Y.Z` on **the actual merged commit**, never on the
release branch head or whatever `main` happens to point to later. The PAT causes
the existing tag build to run. Its new GitHub Release uses the same committed
Expand Down
2 changes: 1 addition & 1 deletion docs/ru/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ bundle exec fastlane lanes
| `bundle exec fastlane android test` | Выполняет `native_tests` и `android_checks`; основная команда перед коммитом; без Python, Docker-интеграции и тестов на эмуляторах. |
| `bundle exec fastlane android debug_artifact` | Собирает `app/build/outputs/apk/debug/app-debug.apk`. |
| `bundle exec fastlane android release_prepare version:0.1.2` | Генерирует EN/RU changelog, повышает версию и создаёт release PR (нужна настройка API/токенов). |
| `bundle exec fastlane android release_finish version:0.1.2 pr:123 head:FULL_SHA` | Ждёт полного CI, выполняет squash merge указанного head PR и тегирует итоговый commit. |
| `bundle exec fastlane android release_finish version:0.1.2 pr:123 head:FULL_SHA` | Ждёт полного CI, выполняет слияние указанного head PR с отдельным merge-коммитом и тегирует итоговый commit. |
| `bundle exec fastlane android virus_total` | Проверяет подписанные APK в `dist/release` (или `artifacts:PATH`); нужен `VIRUSTOTAL_API_KEY`, обнаружения/ошибки API блокируют публикацию. |
| `bundle exec fastlane android release_artifacts` | Собирает и проверяет подписанные APK и `SHA256SUMS` в `dist/release`. |

Expand Down
11 changes: 6 additions & 5 deletions docs/ru/release-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Откройте **Actions → Prepare and merge release → Run workflow**, выберите `main`
и задайте новую версию, например `0.1.2` (без `v`). Запуск разрешает создание
релизного PR, автоматический squash merge после полного CI и создание тега.
релизного PR, автоматическое слияние с merge-коммитом после полного CI и создание тега.
Существующий **Release Android artifacts** затем соберёт, подпишет и опубликует APK.
Это не заменяет [проверку релиза на устройствах](release-testing.md).

Expand Down Expand Up @@ -38,13 +38,13 @@ Actions. PR CI не получает API-ключ и не загружает APK
`ANDROID_SIGNING_KEY_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`,
`ANDROID_KEY_PASSWORD`. Подготовка и проверки PR их не получают.

Разрешите squash merge. Защитите `main`, сделайте обязательными `Change scope`,
Разрешите merge-коммиты. Защитите `main`, сделайте обязательными `Change scope`,
`Python tests and style`, `Native Go tests`, `Android tests and checks`,
`Android emulator API 26 / Device tests`, `Android emulator API 35 / Device tests`
и отключите обход правил для бота. Если правила требуют человеческое ревью, одобрите созданный
PR самостоятельно: workflow не одобряет себя и не обходит запрет. После отказа
merge можно одобрить PR и перезапустить упавший finalize job. Merge queue этим
workflow с прямым squash merge не поддерживается. Правила тегов должны разрешать
workflow с прямым merge не поддерживается. Правила тегов должны разрешать
боту создавать `v*`.

Отдельный аккаунт бота позволяет отделить его права от ваших. В первой реализации
Expand Down Expand Up @@ -87,9 +87,10 @@ workflow с прямым squash merge не поддерживается. Пра
релизного commit. Номер PR также сохраняется успешным шагом CI: GitHub
может убрать связь run с PR после merge, а этот шаг позволяет проверить
повторное создание тега.
7. Повторная проверка PR и неизменности `main`, squash merge с ожидаемым head SHA.
7. Повторная проверка PR и неизменности `main`, merge с отдельным merge-коммитом и ожидаемым head SHA.
Правила GitHub продолжают действовать. Проверка принадлежности итогового
commit к `main` и точного совпадения его дерева с проверенным head.
commit к `main`, двух родителей (проверенные base и head) и точного
совпадения его дерева с проверенным head.
8. Lightweight-тег `vX.Y.Z` ставится **на фактический commit после merge**,
не на head ветки и не на более поздний случайный `main`. PAT запускает обычную
сборку по тегу. Новый GitHub Release использует те же EN/RU тексты из Git.
Expand Down
2 changes: 1 addition & 1 deletion fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ platform :android do
end
end

desc "Wait for full release PR CI, squash merge, and tag its verified merged commit"
desc "Wait for full release PR CI, create a merge commit, and tag its verified merged commit"
lane :release_finish do |options|
version = options[:version] || ENV.fetch("RELEASE_VERSION")
pr = options[:pr] || ENV.fetch("RELEASE_PR")
Expand Down
6 changes: 5 additions & 1 deletion scripts/release_automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ def finish(version, number, head):
)
result = api(
f"pulls/{number}/merge",
{"sha": head, "merge_method": "squash"},
{"sha": head, "merge_method": "merge"},
method="PUT",
)
require(
Expand All @@ -420,6 +420,10 @@ def finish(version, number, head):
require(pr["merged"], "PR is not merged; refusing tag")
merged = pr["merge_commit_sha"]
command("git", "fetch", "origin", "main")
require(
command("git", "show", "-s", "--format=%P", merged).split() == [parent, head],
"Release must have a merge commit with the verified base and head; refusing tag",
)
require(
command("git", "show", "-s", "--format=%T", merged)
== command("git", "show", "-s", "--format=%T", head),
Expand Down
18 changes: 15 additions & 3 deletions scripts/tests/test_release_automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,11 @@ def fake_api(path, payload=None, method=None):
return copy.deepcopy(pr)
if path == "pulls/7/merge":
self.assertEqual(
{"sha": pr["head"]["sha"], "merge_method": "squash"}, payload
{"sha": pr["head"]["sha"], "merge_method": "merge"}, payload
)
self.assertEqual("PUT", method)
git("switch", "-q", "main")
git("merge", "--squash", pr["head"]["sha"])
git("commit", "-qm", "Release v0.1.2")
git("merge", "--no-ff", "-qm", "Release v0.1.2", pr["head"]["sha"])
git("push", "-q", "origin", "main")
pr.update(
merged=True,
Expand Down Expand Up @@ -427,6 +426,19 @@ def advanced_main(path, payload=None, method=None):
git("rev-parse", "refs/tags/v0.1.2", cwd=remote),
)
self.assertNotEqual(head, pr["merge_commit_sha"])
self.assertEqual(
[base, head],
git(
"show", "-s", "--format=%P", pr["merge_commit_sha"]
).split(),
)
merged_commit = pr["merge_commit_sha"]
pr["merge_commit_sha"] = head
with self.assertRaisesRegex(
RuntimeError, "must have a merge commit"
):
m.finish("0.1.2", 7, head)
pr["merge_commit_sha"] = merged_commit
m.finish("0.1.2", 7, head)
self.assertEqual(1, calls.count("pulls/7/merge"))
self.assertEqual(1, calls.count("git/refs"))
Expand Down
Loading