One of the release tasks is putting together the release notes.
This involves going back through all the merged PRs, figuring out what they did, and boiling that down to a one or two sentence summary.
This is significantly easier if the PR is self-contained. This means:
- The PR title concisely explains what it's for
- The PR description explains the problem, how it was solved, and user-visible changes because of the PR
Linking from the PR back to an issue or issues is not a substitute for this.
- The issue may not describe exactly how the problem manifests
- The issue may not describe the user-visible changes that the PR introduces in order to fix the problem.
The commit history is a mess because at the moment every PR is merged with a merge commit, so all the individual commits in that PR are merged to develop
.
To fix this PRs should be squash-merged, and a real commit message should be written for the squashed merge.
A common objection to this goes as follows:
- We use git
- Git's unit of work is a commit
- Keeping the history of every commit that makes up a PR is important
- Therefore squash merges are bad
This is tempting, but wrong.
It's wrong because while we do use git we also use GitHub.
And GitHub's unit of work is a PR.
A PR is merged in totality -- you can't choose to merge some commits from a PR and not others without creating a fresh PR that includes just the commits you want to merge.
Ideally, the PR's title and the first full comment in the description should be usable as the commit message.
- PRs from Weblate, they do bundle individual commits.
Don't mix formatting changes and technical changes in the same PR.
Generally. If it's a sweeping architectural change this might not be possible.
- Reviewing the PR is more time consuming
- Understanding the PR to write release notes is more time consuming
For example: https://github.com/tuskyapp/Tusky/pull/3421/files
As well as the core technical changes that PR also:
- Renames variables (
mediaValue
tomediaList
in multiple unrelated areas of the code) - Makes whitespace changes to string resource files
Producing the release notes could be done for a PR by the PR's author, and enforced by a pre-merge check.
For example:
- Add a PR check with the following logic:
- PR has label "no-user-visible-change", the check passes
- PR contains modifications to
CHANGELOG.md
, the check passes - The check fails
This logic:
- Sets the default expectation to "PR requires a CHANGELOG.md change"
- Allows PR authors to opt-out of this by labelling their PR appropriately
A PR label is one possible control mechanism, but there are others.
For example, we could use Conventional Commits and require anything with a fix:
or feat:
commit type to include a CHANGELOG.md
change.
- Only 1 x concurrency
- Nightly builds on every merged PR, which takes ~ 10 minutes
Github Actions has 20x concurrency, might be considerably faster. tuskyapp/Tusky#3728, if merged, would allow us to get data on whether this would be faster.
What's the policy on using GPG to sign releases? Do we need one? We've got 125+K users so perhaps a moderately attractive target for bad actors.
Or require 2FA to be enabled for anyone who can merge to the repository (https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-two-factor-authentication-for-your-organization/requiring-two-factor-authentication-in-your-organization)?
More concretely, is there a threat model we care about?
- Malicious release APKs (https://www.securityweek.com/compromised-github-account-spreads-malicious-syscoin-installers/)
- ?
We appear to be not-quite following the git-flow model for branches (https://nvie.com/posts/a-successful-git-branching-model/).
We have develop
, main
, and feature branches. But no release branches.
This is a problem.
PRs get merged to develop
first.
Releases are tagged at a particular point in time.
This has the side effect of effectively freezing most work on develop
. I was only merging PRs to develop
if I was confident they were going to go in to the release. This is a roadblock for everyone else.
I.e.,
gitGraph
commit
branch develop
commit
branch feature
commit
checkout main
merge develop tag: "rc1"
checkout feature
commit
commit
commit id: "approved" type: HIGHLIGHT
checkout develop
branch fix1
commit
checkout develop
merge fix1
checkout main
merge develop tag: "rc2"
checkout develop
branch fix2
commit
checkout develop
merge fix2
checkout main
merge develop tag: "rc3"
In this example there's a feature branch (feature
) that has a few commits, and is approved. But it can't be merged to develop
because the release process is underway, and only fixes are being merged. So the feature
branch is frozen for the life of the release.
To remove this, we could create a release branch for each release. E.g., when the release process for v23 starts, create a branch from develop
for the release (e.g., release-23.0
).
PRs can continue to be merged to develop
during this process. Any PRs that should go in to the release are merged in to release-23.0
, and a GitHub action automatically also merges it in to develop
.
Specific beta releases from the release-23.0
branch, and the final release, can be tagged as normal.
That would be the full git-flow model.
But also, after cutting over to this scheme there's no real need for the develop
branch, PRs can be merged in to main
.
That would look something like this:
gitGraph
commit
branch release-23
branch feature
checkout release-23
commit tag: "23.0-beta.1"
checkout main
merge release-23
checkout release-23
checkout feature
commit
checkout release-23
branch fix
commit
checkout release-23
merge fix tag: "23.0-beta.2"
checkout main
merge release-23
checkout feature
merge main
commit
checkout main
merge feature
branch feature2
commit
commit
checkout main
merge feature2
checkout release-23
commit tag: "23.0"
checkout main
merge release-23
With these changes, main
takes the role that develop
does now (and there is no develop
branch).
When the release process starts a release-23
branch is created, then there is the commit that sets the versionCode
, updates the changelog, etc, and 23.0-beta.1 is released. This is merged back to main
.
In the meantime, someone else starts working on a feature
branch, branched from main
.
There's a problem with beta 1, so a fix
branch is created, the fix is made there, merged in to release-23
, and tagged as a new beta release. This is also merged back to main
.
feature
continues, with occasional merges from main
. It's reviewed, approved, and merged back in to main
at the appropriate time. A second feature branch is also created for an unrelated feature, development happens, and is merged in to main
.
Beta 2 is the last beta release, so there's a final commit on the release-23
branch to set the final versionCode
, etc. Then it's tagged, and merged in to main
.
After the release has started should any changes be merged from main
to the release branch (release-23
in this example)?
Probably not. The only thing I can think of is PRs containing translations from Weblate. They can be merged in to the release branch instead of main
. Since the release branch is periodically merged in to main
those changes will end up on main pretty soon anyway.
Creating a release on GitHub has a "Create a discussion for this release" option which notes "People will be able to leave comments and reactions on this release using Discussions.". Maybe that's a good place to try and centralise commentary about the release?
When debugging it became necessary to prepare custom APKs for users to sideload.
This worked, but I'm uneasy about the level of trust this requires from the user.
So I'm wondering if it's possible to have the CI system build an APK for each PR, and attach it to a fixed comment on each PR. If new commits are made to the PR then the APK is rebuilt and reattached.
Then, if we have a fix that we want a user to try we can prep a PR and tell them to download the APK from the PR.
Bitrise is supposed to do this (I think), but it's not working, per:
If I'm reading the bitrise.yml file correctly the deploy-to-bitrise step should mean that there's an "Artifacts" entry on the Build details page for each build (per https://devcenter.bitrise.io/en/builds/managing-build-files/build-artifacts-online.html)
This would be very handy, as each PR would automatically have a signed APK built that we could point people to for individual testing.
But if I look at a build, like:
(nightly) https://app.bitrise.io/build/f8e9cdf3-cab7-475f-bd08-f15401d59e1c (primary) https://app.bitrise.io/build/8bf6e421-eb7c-48a1-ac34-b24cb48f5271?tab=details
there's no "Artifacts" tab.
https://devcenter.bitrise.io/en/deploying/bitrise-ota-app-deployment.html#the-public-install-page suggests there's a config option to the step which defaults to true (https://github.com/bitrise-io/bitrise-steplib/blob/master/steps/deploy-to-bitrise-io/2.1.8/step.yml#L179) and we don't seem to have overridden that.
I had an (unrelated) call with Bitrise Dev. Rel. a few days back, and raised this with them, so they're looking in to it.
The current release process makes a new final release available to 100% of the userbase, and they then take a few weeks to update.
If we discover anything at the last minute where we want to pause the release (which can happen, even with nightly and betas) we can't.
We could do the final rollout over a longer period, say 4 days, with increments of say 5%, 25%, 50%, 100%
It's more work (but it could be automated), and it would allow us to pause a release if we discover a problem where we want to stop and think about the best way to proceed.
There's a lot of manual work to be done on a release.
I've written something that automates a lot of it. It's not ready for review yet, but https://github.com/nikclayton/Tusky/compare/mklanguages...nikclayton:Tusky:mkrelease.
You run mkrelease start
to start a new release process, and then mkrelease beta
for each new beta version you want to release.
It walks you through the steps, automating some of them (editing build.gradle, creating release notes from the changelog, creating the GitHub release, editing the F-Droid JSON, etc), and prompts you with the manual steps for things that aren't automated yet (e.g., clicking through the Play Console to promote a release).
When the beta chain is finished you run mkrelease final
to create the non-beta release.