on ffc25da help sign in
4 files changed+5−417
since splitsnapshots
unifiedsplit
.github/workflows/build.yml+2−42 .github/workflows/release.yml−242 README.md+1−130 scripts/sync-report.py+2−3
.github/workflows/build.yml+2−42
@@ -20,10 +20,6 @@ on:
2020 apk:
2121 type: boolean
2222 default: false
23− build_number:
24− description: MultiGram build number (0 to 9), the fourth part of the version (release.yml sets it)
25− type: string
26− default: '0'
2723
2824permissions:
2925 contents: read
@@ -71,15 +67,6 @@ jobs:
7167 with:
7268 tool-cache: false
7369 android: false
74− - name: Add swap space
75− # Runners for private repositories have about 8 GB of memory; the native build and the
76− # Gradle and Kotlin daemons together can use more, and the runner dies without a log.
77− run: |
78− sudo fallocate -l 8G /multigram-swap
79− sudo chmod 600 /multigram-swap
80− sudo mkswap /multigram-swap
81− sudo swapon /multigram-swap
82− free -h
8370 - uses: actions/setup-java@v4
8471 with:
8572 distribution: temurin
@@ -94,43 +81,16 @@ jobs:
9481 env:
9582 APP_ID: ${{ secrets.MULTIGRAM_APP_ID }}
9683 APP_HASH: ${{ secrets.MULTIGRAM_APP_HASH }}
97− KEYSTORE_BASE64: ${{ secrets.MULTIGRAM_KEYSTORE_BASE64 }}
98− KEYSTORE_PASSWORD: ${{ secrets.MULTIGRAM_KEYSTORE_PASSWORD }}
99− KEY_ALIAS: ${{ secrets.MULTIGRAM_KEY_ALIAS }}
100− KEY_PASSWORD: ${{ secrets.MULTIGRAM_KEY_PASSWORD }}
101− BUILD_NUMBER: ${{ inputs.build_number }}
10284 run: |
10385 vars=gradle.properties
104− # Forkgram's versioning: versionName APP_VERSION_NAME.N and versionCode
105− # (APP_VERSION_CODE * 10 + N) * 10 + the flavor's ABI code, with N = ADDITIONAL_BUILD_NUMBER.
106− case "${BUILD_NUMBER:-0}" in [0-9]) ;; *) echo "::error::build_number must be one digit"; exit 1;; esac
107− echo "ADDITIONAL_BUILD_NUMBER=${BUILD_NUMBER:-0}" >> $vars
10886 echo "org.gradle.workers.max=1" >> $vars
109− # Forkgram asks for an 8 GB heap, sized for the bigger runners of public repositories.
110− echo "org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8" >> $vars
111− echo "kotlin.daemon.jvmargs=-Xmx2g" >> $vars
112− echo "org.gradle.parallel=false" >> $vars
11387 [ -n "$APP_ID" ] && echo "APP_ID=$APP_ID" >> $vars
11488 [ -n "$APP_HASH" ] && echo "APP_HASH=$APP_HASH" >> $vars
115− signing=()
116− if [ -n "$KEYSTORE_BASE64" ]; then
117− # Your own release key, from the MULTIGRAM_KEYSTORE_* secrets; it never touches the repository.
118− echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore"
119− signing=(
120− "-PRELEASE_KEYSTORE_FILE=$RUNNER_TEMP/release.keystore"
121− "-PRELEASE_STORE_PASSWORD=$KEYSTORE_PASSWORD"
122− "-PRELEASE_KEY_ALIAS=$KEY_ALIAS"
123− "-PRELEASE_KEY_PASSWORD=${KEY_PASSWORD:-$KEYSTORE_PASSWORD}"
124− )
125− else
126− # Without RELEASE_KEYSTORE_FILE the build signs with the public test key in
127− # TMessagesProj/config/test.keystore: fine for testing, not for an APK you give out.
128− echo "::warning::No MULTIGRAM_KEYSTORE_BASE64 secret: the APK is signed with the public test key."
129− fi
89+ echo "RELEASE_KEYSTORE_FILE=$PWD/TMessagesProj/config/release.keystore" >> $vars
13090 sudo apt update
13191 sudo apt -y install ninja-build meson nasm gperf pkg-config unzip
13292 rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
133− ./gradlew "${signing[@]}" :TMessagesProj_App:assembleAfatRelease
93+ ./gradlew :TMessagesProj_App:assembleAfatRelease
13494 - uses: actions/upload-artifact@v4
13595 with:
13696 name: multigram-apk
.github/workflows/release.ymldeleted−242
@@ -1,242 +0,0 @@
1−name: Release MultiGram
2−run-name: Release ${{ inputs.ref }}${{ inputs.dry_run && ' (dry run)' || '' }}
3−
4−# Builds the release APK of a MultiGram commit and publishes it as a GitHub Release.
5−#
6−# Version: Forkgram's own scheme, APP_VERSION_NAME.N (for example 12.10.6.1), where APP_VERSION_NAME
7−# comes from gradle.properties of the Forkgram snapshot and N (1 to 9) counts MultiGram releases on
8−# that Forkgram version. The Android versionCode is (APP_VERSION_CODE * 10 + N) * 10 + 9, so every
9−# release installs as an update over the one before, and a new Forkgram version starts again at N = 1.
10−# The tag is the version itself, like Forkgram's tags.
11−
12−on:
13− workflow_dispatch:
14− inputs:
15− ref:
16− description: Branch, tag or commit to release
17− default: multigram
18− required: true
19− number:
20− description: MultiGram build number for this Forkgram version (1 to 9). Empty picks the next free one.
21− required: false
22− default: ''
23− prerelease:
24− description: Mark it as a pre-release (always the case without your own signing key)
25− type: boolean
26− default: false
27− dry_run:
28− description: Only work out the version and the release notes; build and publish nothing
29− type: boolean
30− default: false
31−
32−permissions:
33− contents: read
34−
35−concurrency:
36− group: release
37− cancel-in-progress: false
38−
39−jobs:
40− check_secrets:
41− # A job of its own, so that no secret value is registered in the job that writes the release
42− # notes: the runner drops any job output that contains a secret's value (a key alias like
43− # "release" would otherwise blank the notes).
44− name: Check the secrets
45− runs-on: ubuntu-latest
46− outputs:
47− signed: ${{ steps.s.outputs.signed }}
48− steps:
49− - name: Check the secrets
50− id: s
51− env:
52− DRY_RUN: ${{ inputs.dry_run }}
53− APP_ID: ${{ secrets.MULTIGRAM_APP_ID }}
54− APP_HASH: ${{ secrets.MULTIGRAM_APP_HASH }}
55− KEYSTORE_BASE64: ${{ secrets.MULTIGRAM_KEYSTORE_BASE64 }}
56− KEYSTORE_PASSWORD: ${{ secrets.MULTIGRAM_KEYSTORE_PASSWORD }}
57− KEY_ALIAS: ${{ secrets.MULTIGRAM_KEY_ALIAS }}
58− run: |
59− set -euo pipefail
60− # Stop here, in seconds, rather than after the long build. A dry run only warns.
61− problem() { if [ "$DRY_RUN" = true ]; then echo "::warning::$1"; else echo "::error::$1"; exit 1; fi; }
62− if [ -z "$APP_ID" ] || [ -z "$APP_HASH" ]; then
63− problem "The MULTIGRAM_APP_ID and MULTIGRAM_APP_HASH secrets are not set, so the APK could not log in. Add them (Settings > Secrets and variables > Actions) and run this again."
64− fi
65− if [ -n "$KEYSTORE_BASE64" ]; then
66− if [ -z "$KEYSTORE_PASSWORD" ] || [ -z "$KEY_ALIAS" ]; then
67− problem "MULTIGRAM_KEYSTORE_BASE64 is set, but MULTIGRAM_KEYSTORE_PASSWORD or MULTIGRAM_KEY_ALIAS is not: signing would fail at the end of the build. Add both."
68− fi
69− echo "signed=true" >> "$GITHUB_OUTPUT"
70− else
71− echo "::warning::No MULTIGRAM_KEYSTORE_BASE64 secret: the release will be signed with Forkgram's public test key and published as a pre-release."
72− echo "signed=false" >> "$GITHUB_OUTPUT"
73− fi
74−
75− prepare:
76− name: Work out the version
77− needs: check_secrets
78− runs-on: ubuntu-latest
79− outputs:
80− sha: ${{ steps.v.outputs.sha }}
81− number: ${{ steps.v.outputs.number }}
82− version: ${{ steps.v.outputs.version }}
83− version_code: ${{ steps.v.outputs.version_code }}
84− prerelease: ${{ steps.v.outputs.prerelease }}
85− notes: ${{ steps.v.outputs.notes }}
86− steps:
87− - uses: actions/checkout@v4
88− with:
89− ref: ${{ inputs.ref }}
90− fetch-depth: 0
91− - name: Version, secrets and release notes
92− id: v
93− env:
94− NUMBER: ${{ inputs.number }}
95− PRERELEASE: ${{ inputs.prerelease }}
96− DRY_RUN: ${{ inputs.dry_run }}
97− SIGNED: ${{ needs.check_secrets.outputs.signed }}
98− run: |
99− set -euo pipefail
100− fail() { echo "::error::$1"; exit 1; }
101− sha=$(git rev-parse HEAD)
102− name=$(sed -n 's/^APP_VERSION_NAME=//p' gradle.properties | tail -n 1)
103− code=$(sed -n 's/^APP_VERSION_CODE=//p' gradle.properties | tail -n 1)
104− [[ "$name" =~ ^[0-9]+(\.[0-9]+)*$ ]] || fail "APP_VERSION_NAME in gradle.properties is '$name', not a version number."
105− [[ "$code" =~ ^[0-9]+$ ]] || fail "APP_VERSION_CODE in gradle.properties is '$code', not a number."
106−
107− # The next free N for this Forkgram version, from the tags that already exist.
108− last=$(git tag -l "$name.*" | sed -n "s/^${name//./\\.}\.\([0-9]\)$/\1/p" | sort -n | tail -n 1)
109− if [ -n "$NUMBER" ]; then
110− n=$NUMBER
111− else
112− n=$(( ${last:-0} + 1 ))
113− fi
114− [[ "$n" =~ ^[1-9]$ ]] || fail "The MultiGram build number must be 1 to 9 (got '$n'). Nine releases on Forkgram $name are the most its version codes allow; wait for the next Forkgram version."
115− version="$name.$n"
116− if git rev-parse -q --verify "refs/tags/$version" >/dev/null; then
117− fail "Tag $version already exists: MultiGram $version is already released. Leave the number empty to take the next free one."
118− fi
119− if [ -n "$last" ] && [ "$n" -le "$last" ]; then
120− echo "::warning::$name.$last is already released, so $version has a lower version code and will not install as an update over it."
121− fi
122− version_code=$(( (code * 10 + n) * 10 + 9 ))
123−
124− # What it is built on, from the snapshot commit the sync made.
125− snap=$(git log -n 1 --format=%H --grep='^Forkgram-Version: ' HEAD || true)
126− fg_version=unknown; tg_version=unknown
127− if [ -n "$snap" ]; then
128− msg=$(git log -1 --format=%B "$snap")
129− fg_version=$(printf '%s\n' "$msg" | sed -n 's/^Forkgram-Version: //p' | head -n 1)
130− tg_version=$(printf '%s\n' "$msg" | sed -n 's/^Telegram-Base: .*(update to \([0-9.]*\).*/\1/p' | head -n 1)
131− fg_version=${fg_version:-unknown}; tg_version=${tg_version:-unknown}
132− fi
133−
134− signed=${SIGNED:-false}
135− prerelease=$PRERELEASE
136− [ "$signed" = true ] || prerelease=true
137−
138− notes=$(mktemp)
139− {
140− echo "MultiGram $version is Forkgram $fg_version (Telegram $tg_version) with the MultiGram changes listed below."
141− echo
142− if [ "$signed" = true ]; then
143− echo "**Signing:** signed with the MultiGram release key. It installs as an update over earlier MultiGram releases signed with the same key."
144− else
145− echo "**Signing: public test key.** This APK is signed with Forkgram's public test key (\`TMessagesProj/config/test.keystore\`), because the repository has no \`MULTIGRAM_KEYSTORE_BASE64\` secret. Anyone can sign an \"update\" for it, and a later release signed with a real key cannot update it. Use it for testing only."
146− fi
147− echo
148− echo "**Version:** $version (Android version code $version_code). Built from $sha."
149− echo
150− echo "**MultiGram changes on top of Forkgram $fg_version:**"
151− echo
152− if [ -n "$snap" ]; then
153− git log --reverse --format='- %s (%h)' "$snap..HEAD"
154− else
155− echo "- (no Forkgram snapshot found in the history)"
156− fi
157− } > "$notes"
158−
159− {
160− echo "sha=$sha"
161− echo "number=$n"
162− echo "version=$version"
163− echo "version_code=$version_code"
164− echo "prerelease=$prerelease"
165− echo "notes<<MULTIGRAM_NOTES_EOF"
166− cat "$notes"
167− echo "MULTIGRAM_NOTES_EOF"
168− } >> "$GITHUB_OUTPUT"
169− {
170− if [ "$DRY_RUN" = true ]; then echo "## MultiGram $version (dry run: nothing built or published)"; else echo "## MultiGram $version"; fi
171− echo
172− echo "Pre-release: $prerelease"
173− echo
174− cat "$notes"
175− } >> "$GITHUB_STEP_SUMMARY"
176− cat "$notes"
177−
178− build:
179− name: Build
180− needs: [check_secrets, prepare]
181− if: ${{ !inputs.dry_run }}
182− uses: ./.github/workflows/build.yml
183− with:
184− ref: ${{ needs.prepare.outputs.sha }}
185− apk: true
186− build_number: ${{ needs.prepare.outputs.number }}
187− secrets: inherit
188−
189− publish:
190− name: Publish the release
191− needs: [prepare, build]
192− runs-on: ubuntu-latest
193− permissions:
194− contents: write
195− steps:
196− - uses: actions/download-artifact@v4
197− with:
198− name: multigram-apk
199− path: apk
200− - name: Check the APK and publish it
201− env:
202− GH_TOKEN: ${{ github.token }}
203− SHA: ${{ needs.prepare.outputs.sha }}
204− VERSION: ${{ needs.prepare.outputs.version }}
205− VERSION_CODE: ${{ needs.prepare.outputs.version_code }}
206− PRERELEASE: ${{ needs.prepare.outputs.prerelease }}
207− NOTES: ${{ needs.prepare.outputs.notes }}
208− run: |
209− set -euo pipefail
210− fail() { echo "::error::$1"; exit 1; }
211− shopt -s nullglob
212− apks=(apk/*.apk)
213− [ "${#apks[@]}" -eq 1 ] || fail "Expected one APK in the build artifact, found ${#apks[@]}."
214− file="MultiGram-$VERSION.apk"
215− mv "${apks[0]}" "$file"
216−
217− tools=$(ls -d "$ANDROID_HOME"/build-tools/* | sort -V | tail -n 1)
218− badging=$("$tools/aapt2" dump badging "$file" | head -n 1)
219− echo "$badging"
220− got_name=$(printf '%s' "$badging" | sed -n "s/.* versionName='\([^']*\)'.*/\1/p")
221− got_code=$(printf '%s' "$badging" | sed -n "s/.* versionCode='\([^']*\)'.*/\1/p")
222− [ "$got_name" = "$VERSION" ] || fail "The APK says versionName $got_name, expected $VERSION."
223− [ "$got_code" = "$VERSION_CODE" ] || fail "The APK says versionCode $got_code, expected $VERSION_CODE."
224− cert=$("$tools/apksigner" verify --print-certs "$file" | sed -n 's/^Signer #1 certificate SHA-256 digest: //p' | head -n 1)
225− [ -n "$cert" ] || fail "apksigner could not verify the APK's signature."
226− sha256sum "$file" > SHA256SUMS
227−
228− {
229− printf '%s\n' "$NOTES"
230− echo
231− echo "**Checksums:** \`$(cut -d' ' -f1 SHA256SUMS)\` ($file, SHA-256). Signing certificate SHA-256: \`$cert\`."
232− } > notes.md
233−
234− flags=()
235− if [ "$PRERELEASE" = true ]; then flags+=(--prerelease); fi
236− gh release create "$VERSION" "$file" SHA256SUMS \
237− --repo "$GITHUB_REPOSITORY" \
238− --target "$SHA" \
239− --title "MultiGram $VERSION" \
240− --notes-file notes.md \
241− "${flags[@]}"
242− echo "Published https://github.com/$GITHUB_REPOSITORY/releases/tag/$VERSION" | tee -a "$GITHUB_STEP_SUMMARY"
README.md+1−130
@@ -8,133 +8,4 @@ MultiGram is Forkgram (https://github.com/forkgram/TelegramAndroid) plus a small
88| `multigram` | The app: the latest `forkgram` snapshot plus the MultiGram commits on top. Build this branch. |
99| `main` | This README, the automation that keeps `multigram` in step with Forkgram, and helper scripts. |
1010
11−The sync also uses `multigram-next` (the next `multigram`, while it is being checked) and tags
12−the `multigram` it replaces as `multigram-before-<version>`.
13−
14−[PATCH.md](https://github.com/iko-soy/Multigram/blob/docs/PATCH.md) on the `docs` branch says what
15−to do after a Telegram update when the sync needs a hand, and lists every MultiGram change with
16−the exact code, so the stack can be re-applied by hand.
17−
18−## Build
19−
20−**Actions > Build MultiGram > Run workflow**, ref `multigram`. It compiles the Java and Kotlin
21−code (a few minutes). Tick **APK** to also build a release APK; it compiles the native code
22−too, so it takes much longer, and the APK is attached to the run as `multigram-apk`.
23−
24−## Releases
25−
26−**Actions > Release MultiGram > Run workflow** builds the release APK of `multigram` and publishes
27−it on the Releases page (about 80 minutes; tick **dry_run** to see only the version and notes).
28−Versions follow Forkgram's scheme: `12.10.6.1` is the first MultiGram release on Forkgram 12.10.6,
29−`12.10.6.2` the second, and after a Forkgram update the count starts again at 1 (at most 9 per
30−Forkgram version). The tag is the version. It needs the API id and hash secrets below. Without the
31−signing secrets the APK is signed with Forkgram's public test key and published as a pre-release
32−that says so. Details: section 10.6 of
33−[PATCH.md](https://github.com/iko-soy/Multigram/blob/docs/PATCH.md).
34−
35−## Secrets
36−
37−Set these in **Settings > Secrets and variables > Actions**:
38−
39−- `MULTIGRAM_APP_ID` and `MULTIGRAM_APP_HASH`: your own API id and hash from
40− https://my.telegram.org (API development tools). Without them the APK cannot log in.
41−- Signing (optional): `MULTIGRAM_KEYSTORE_BASE64` (your release keystore, `base64 -w0 release.keystore`),
42− `MULTIGRAM_KEYSTORE_PASSWORD`, `MULTIGRAM_KEY_ALIAS` and `MULTIGRAM_KEY_PASSWORD` (if it differs
43− from the store password). Without them the APK is signed with Forkgram's public test key
44− (`TMessagesProj/config/test.keystore`), which is fine for testing but lets anyone sign an
45− "update" for it. Keep the same key for every build you give out, or phones can't update. The
46− rebrand toolkit can make a key for you (see "Signing and updates" in `multigram/rebrand/README.md`).
47−
48−## How the sync works
49−
50−The **Sync with Forkgram** workflow runs every 6 hours (and by hand):
51−
52−1. If Forkgram's `dev` has a new tip (Forkgram rebases `dev` onto every Telegram release, so each
53− new tip is a release candidate), it adds a snapshot commit to `forkgram`: that tree without
54− `.github`, with `Forkgram-Commit`, `Forkgram-Version` and `Telegram-Base` lines in the message.
55−2. It replays the MultiGram commits (`forkgram..multigram`) onto the snapshot as `multigram-next`
56− (`git rebase --onto`).
57−3. It compiles `multigram-next` with the Build MultiGram workflow.
58−4. If that passes, it tags the old `multigram` as `multigram-before-<version>`, moves `multigram`
59− to `multigram-next`, deletes `multigram-next` and closes the sync issue.
60−5. On a conflict or a failed compile it opens an issue labelled `forkgram-sync` (or comments on the
61− open one) with the details; `multigram` stays as it was.
62−
63−It never replaces a `multigram-next` that has commits it did not make (your fix, say): it then
64−changes nothing, not even `forkgram`, and says so on the issue until you run **use_next** or
65−delete `multigram-next`. It records the `multigram-next` it built last in the ref
66−`refs/multigram-sync/next`. Keep `multigram` linear (rebase, do not merge): the replay flattens
67−merges, so a merge commit with changes of its own stops the sync.
68−
69−Run it by hand with **force** to rebuild `multigram-next` without a new release (to test the
70−pipeline, or to retry), or with **use_next** to compile and promote a `multigram-next` you fixed
71−yourself. **use_next** checks that `multigram-next` still has every commit on `multigram` (a
72−commit pushed to `multigram` after you started the fix would otherwise be lost); tick
73−**allow_drop** as well if it leaves some out on purpose.
74−
75−The work is done by `scripts/sync-forkgram.sh` (see `--help`). It also runs locally without
76−pushing anything: from a checkout of `main`, `bash scripts/sync-forkgram.sh --remote origin`
77−leaves the results in your local `forkgram` and `multigram-next` branches.
78−`bash scripts/selftest-sync.sh` tests it against fake repositories.
79−
80−rerere (git's memory of conflict fixes) only knows the fixes recorded in the clone it runs in.
81−It helps when you redo a rebase or run the script in your own clone; the scheduled sync starts
82−from a fresh clone every time, so it reports a conflict again until `multigram` carries the fix.
83−
84−## When the sync issue opens
85−
86−The issue has the exact commands; in short:
87−
88−**Conflict.** Replay the stack yourself, fix the files, push the result to `multigram-next`:
89−
90−```sh
91−git fetch origin
92−git switch -C multigram-next origin/multigram
93−git config rerere.enabled true # reuses your fixes if you redo this rebase here
94−git rebase origin/forkgram
95−# fix the files git names, then: git add <files> && git rebase --continue (repeat)
96−git push --force origin multigram-next
97−```
98−
99−**Compile failure.** Fix the build on top of `multigram-next` and fold the fix into the commit it
100−belongs to:
101−
102−```sh
103−git fetch origin
104−git switch -C multigram-next origin/multigram-next
105−# edit, then, once per MultiGram commit your fix touches (name only that commit's files):
106−git commit --fixup=<the MultiGram commit it belongs to> -- <its files>
107−GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash origin/forkgram
108−git push --force origin multigram-next
109−```
110−
111−Then **Actions > Sync with Forkgram > Run workflow** with **use_next** ticked: it compiles
112−`multigram-next` and, if that passes, moves `multigram` to it and closes the issue. If the
113−compile failed because of a runner or network hiccup, **Re-run failed jobs** on the run page
114−is enough. Until then the sync leaves your `multigram-next` alone; if you give up on it, delete
115−it (`git push origin --delete multigram-next`) and the next sync starts over from `multigram`.
116−
117−## Patches
118−
119−`bash scripts/export-patches.sh --base origin/forkgram --head origin/multigram` writes the stack
120−as numbered patches to `multigram-patches/`, with `APPLY.txt` saying how to apply them onto a
121−plain Forkgram checkout (`git am -3`).
122−
123−## What is on the multigram branch
124−
125−- `multigram/rebrand/`: the rebrand toolkit that gives a build its own name, applicationId,
126− launcher icon and signing key (instructions in its `README.md`).
127−- `multigram/hide-search-bar/`: the "Hide chat list search" option in Fork Client Settings >
128− Chat list view (off by default), which removes the search bar and search icon from the chat list.
129−- `multigram/`: the other MultiGram tools and notes, such as the per-install random style and
130− its palette check; the app code is in
131− `TMessagesProj/src/main/java/org/telegram/messenger/multigram/`.
132−
133−## Repository settings
134−
135−- `main` must be the default branch: scheduled workflows only run from it.
136−- The workflows ask for the token permissions they need, so the default workflow permissions
137− can stay read-only.
138−- Issues must be enabled: the sync reports on an issue labelled `forkgram-sync`.
139−- If you protect `forkgram`, `multigram`, `multigram-next` or `multigram-before-*` tags with
140− branch protection or rulesets, let GitHub Actions push and force-push them.
11+Setup is in progress.
scripts/sync-report.py+2−3
@@ -152,9 +152,8 @@ If the log shows a runner or network hiccup, use **Re-run failed jobs** on the r
152152```sh
153153git fetch origin
154154git switch -C multigram-next origin/multigram-next
155−# fix the build, then fold each fix into the MultiGram commit it belongs to,
156−# one fixup per commit, naming that commit's files (PATCH.md 6.3 on the docs branch):
157−git commit --fixup=<that commit> -- <its files>
155+# fix the build, then fold the fix into the MultiGram commit it belongs to:
156+git commit -a --fixup=<that commit>
158157GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash {snap}
159158git push --force origin multigram-next
160159```