hammer-editor/docs/HOW-TO-RELEASE.md
Adam Brown d1ea053c28
Bake the changelog into the app instead of checking GitHub on load (#850)
The app called the GitHub releases API from ProjectSelectionComponent.init
to decide whether to show an "update available" dialog. That is an
unprompted network request before the user has done anything, which has
been raised as a privacy concern.

The release notes are already in the repo at release time, so bake them
in instead. prepareForRelease writes the new CHANGELOG.md entry to
common/src/commonMain/composeResources/files/changelog.md and commits it
alongside the version bump, so the resource cannot drift from the version
in libs.versions.toml.

On load, ChangelogRepository compares the baked entry's version against
lastSeenChangelogVersion in global settings and shows a "What's New"
dialog when they differ. Dismissing records the version. Fresh installs
are seeded as already-seen, so only upgrading users get the popup;
the old lastDismissedUpdateVersion key is dropped, which is safe because
the TOML serializer ignores unknown keys.

About drops its update check entirely and gains two buttons: Changes,
which reopens the dialog with no network, and GitHub Release, which opens
the browser.

VersionCheckRepository stays for the protocol mismatch dialog, which only
appears after the user has connected to a sync server and exists to tell
them which version to get. It keeps its automatic check and is now that
repository's only consumer.

Links in the notes are clickable via linkifyChangelog, which handles
[label](url) and bare urls, including urls containing parentheses. It is
deliberately not a markdown renderer: entries lead with [New] / [Fix]
tags that a real parser mangles.
2026-08-04 19:04:59 -07:00

3.5 KiB

How to Release

  • Make sure your local repository is in a clean state, nothing outstanding
  • Change branch to develop
  • When develop is ready to release, run: ./gradlew prepareForRelease
    • This will prepare your repo by doing the following:
      • Increment app version app in libs.versions.toml
      • Add new changelog in fastlane\metadata\android\en-US\changelogs called n.txt where n is the android version code
      • Bake the release notes into the app at common/src/commonMain/composeResources/files/changelog.md — this is what the in-app "What's New" dialog shows, so never edit it by hand
      • Merge develop into release
      • Tag the latest commit to make the release from in the semvar format of v1.1.1
      • Push to origin
  • This will trigger the release action on GitHub which will create a new Release, and build all the artifacts
  • Once the release action is complete open the new Release on GitHub
  • Click Edit
  • Uncheck "Set as a pre-release" and instead check "Set as the latest release"
  • Click the Publish Release button
  • This will trigger the publish action which will upload artifacts to stores, deploy to hammer.ink, and notify the Discord channel of a new release
  • All done!

Partial (single-store) releases

Use this when one store needs a hotfix and you don't want to ship the whole matrix.

  • The Tag: being pushed will have the suffix of the stores to publish to:
    • Full release: v1.2.4
    • Single store: v1.2.4+google-play
    • Subset: v1.2.4+google-play+fdroid
    • Server only: v1.2.4+server
  • Server only ships no client store at all. The server distribution is built and attached to the release like always (and deployed to hammer.ink out of band); the +server tag simply matches none of the per-store publish jobs, so no app store upload runs.
  • The global version in libs.versions.toml is still bumped every time, so stores not included this cycle catch up on the next full release (their patch number will skip forward, which Apple/Google both accept since each store sees a monotonically increasing version code).

Backing out a release (before remote was updated)

If you ran prepareForRelease and it failed, leaving things in a half prepared state, then this ones for you. Run:

./gradlew backoutLastRelease
  • Resets develop back to before the "Prepared for release" commit.
  • Resets the local release branch to origin/release.
  • Deletes every local tag for this version — both the bare vX.Y.Z and any vX.Y.Z+platform+... partial-release variants.
  • Does NOT touch the remote. If the push already succeeded, use revertLastRelease instead.

Reverting a release that was already pushed

If the prepare push hit origin and you need to undo it:

./gradlew revertLastRelease
  • Force-pushes release back to its pre-merge state.
  • Force-pushes develop back to before the "Prepared for release" commit.
  • Deletes the version's tags from both remote and local — bare vX.Y.Z plus any vX.Y.Z+platform+... partial-release variants. Remote is deleted first so a failure there doesn't leave a stale local tag that could re-push later.
  • Anyone who pulled in between will need to reset their local clone.
  • Does NOT undo store uploads. If the publish workflow already shipped a build to a store, you have to roll that back through the store's UI (or by shipping a higher version code with the fix).