Signierskript richtet den Klon selbst aus - kein Handgriff mehr noetig

Der Waechter brach bisher ab und legte dem Menschen einen Befehl vor. Jetzt
bringt er den Klon selbst auf origin/main, solange das nachweislich verlustfrei
ist: reiner Rueckstand wird vorgespult; Commits, die nur lokal liegen, deren
Tree aber dem des gemeinsamen Vorfahren gleicht, tragen inhaltlich nichts bei
und werden verworfen (Reflog haelt sie 90 Tage). Nur Commits, die auch den
Inhalt aendern, fuehren weiterhin zum Abbruch mit beiden Wegen zur Wahl.

Damit entfaellt die Vorbereitung vor einem Signierlauf vollstaendig - wichtig,
weil die bisher dokumentierte Vorbereitung ausgerechnet `git pull` war, also
genau der Befehl, der auf einem Klon von vor dem Rewrite die entfernten Commits
zurueckgeholt haette.

Fuenf Lagen gegen Wegwerf-Repositories geprueft: gleich, zurueck, inhaltslos
voraus, echte ungepushte Arbeit, echte Divergenz.

APPLE_DEV_BACKLOG.md, OFFEN.md und AGENTS.md (Abschnitt AJ) nachgezogen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-07 22:01:32 +02:00
co-authored by Claude Opus 5
parent 185a044c5e
commit 5b840ea510
4 changed files with 78 additions and 57 deletions
+23 -11
View File
@@ -4916,18 +4916,30 @@ staleness bug this project keeps fixing elsewhere (cache-busting, OTA-bundle-ver
tree is clean and `HEAD` is exactly `origin/main` (fetches first, aborts with a clear message tree is clean and `HEAD` is exactly `origin/main` (fetches first, aborts with a clear message
otherwise) — added right after `cd "$APP"`, before the web build. otherwise) — added right after `cd "$APP"`, before the web build.
**Nachtrag 2026-09-07: the guard now distinguishes two cases, and the reason is a real **Nachtrag 2026-09-07: the guard repairs the clone itself where that is provably
trap.** On that day `main`'s history was rewritten once - three commits at the tip, all of lossless.** On that day `main`'s history was rewritten once - three commits at the tip, all
which contained nothing but a document that has since moved to its own repository, were of which contained nothing but a document that has since moved to its own repository, were
removed and the branch force-pushed back to `0e84248`. **Every clone made before that date removed and the branch force-pushed back to `0e84248`. **Every clone made before that date
is now *ahead* of `origin/main`, not behind it**, so `git pull` there is exactly wrong: the holds commits `origin/main` does not**, so `git pull` there is exactly wrong: the merge
merge pulls the removed commits back into the history, and the next push restores them on pulls the removed commits back into the history, and the next push restores them on the
the server - without even needing `--force`. Git cannot tell that apart from genuine server - without even needing `--force`.
unpushed work, so the script does not decide: it lists the commits that exist only locally,
names both possibilities (push them, or `git fetch origin && git reset --hard origin/main`) Git cannot tell that apart from genuine unpushed work by the commit graph alone, but it can
and aborts. Only the plain behind-case still recommends `git pull`. Both branches verified by the **trees**: if `HEAD`'s tree equals the merge-base's tree, the local-only commits
against throwaway repositories built to each shape, not just parsed - the first version of contribute no content at all, and discarding them cannot lose a single file. That is the
this fix classified the situation as "diverged" and would have told the Mac to push. test the script now runs (`git diff --quiet $(git merge-base …) HEAD`). In that case it
resets to `origin/main` itself, prints what it discarded, and carries on - the clean-tree
check one step earlier guarantees no uncommitted work is in the way, and the reflog keeps
the commits for 90 days. The same reasoning covers a clone that is merely **behind**: the
script fast-forwards it rather than demanding a `git pull`, so the documented preparation
for a signing run is now *nothing at all* - which matters, because telling a human to run
`git pull` is precisely the instruction that would have re-created the removed commits on
the wrong clone. Only when the local commits *do* change content does it abort and leave
the decision to the human, naming both routes and warning off `git pull`.
All five shapes (equal, behind, content-free ahead, real unpushed work, genuine divergence)
verified against throwaway repositories built to each shape, not just parsed - an earlier version of this fix
classified the situation as "diverged" and would have told the Mac to push.
--- ---
+18 -23
View File
@@ -53,31 +53,26 @@ danach zu prüfen ist. `AGENTS.md` ist nur für das *Warum* nötig.
### Vorher ### Vorher
Nichts vorzubereiten außer einem aktuellen Stand: **Nichts vorzubereiten** - auch kein `git pull`. Das Skript holt `origin/main`
selbst und bringt den Klon darauf, solange das nachweislich verlustfrei ist:
ein reiner Rückstand wird vorgespult, und Commits, die zwar nur lokal liegen,
gegenüber dem gemeinsamen Vorfahren aber **keine einzige Datei ändern**, werden
verworfen. Beides sagt es an; das Reflog hält Verworfenes noch 90 Tage.
```bash > **Warum kein `git pull`:** am 07.09.2026 sind drei Commits am Ende von `main`
git pull > entfernt und der Branch per Force-Push zurückgesetzt worden. Ein Klon von davor
``` > steht damit **vor** `origin/main` - dort wäre `git pull` genau das Falsche: der
> Merge hängt die entfernten Commits wieder in die Historie, und der nächste Push
> stellte sie auf dem Server wieder her (er ginge sogar ohne `--force` durch).
> Genau diese Lage räumt das Skript von sich aus auf.
> **Einmalig, für jeden Klon von vor dem 07.09.2026:** an diesem Tag sind drei Ab **hier** entscheidet es nichts mehr allein: liegen lokale Commits, die **auch
> Commits am Ende von `main` entfernt und der Branch per Force-Push den Inhalt** ändern, bricht es ab und legt dir beide Wege vor (pushen oder
> zurückgesetzt worden. Ein älterer Klon steht damit **vor** `origin/main` und verwerfen) - ungepushte Arbeit wirft es nie weg.
> `git pull` ist dort das Falsche — es würde die entfernten Commits per Merge Ein nicht sauberer Arbeitsbaum bricht den Lauf ebenfalls ab - er ist die
> zurückholen, und der nächste Push stellte sie auf dem Server wieder her (er Voraussetzung dafür, dass Zurechtrücken oben gefahrlos ist. Und ebenso bricht er
> ginge sogar ohne `--force` durch). Richtig ist einmalig: ab, wenn das Zielgerät nicht im Team eingetragen ist („Your team has no
> devices"); das ist dann eine Portal-Sache, kein Projektfehler.
> ```bash
> git fetch origin && git reset --hard origin/main
> ```
>
> Das Skript erkennt die Lage selbst, listet die betroffenen Commits auf und
> bricht ab, statt zu raten — es entscheidet aber nicht für dich, weil dieselbe
> Lage auch echte ungepushte Arbeit sein kann.
Das Skript prüft das selbst und **bricht ab**, wenn der Arbeitsbaum nicht sauber
oder `HEAD` nicht gleich `origin/main` ist - sonst signierte es einen veralteten
Stand. Ebenso bricht es ab, wenn das Zielgerät nicht im Team eingetragen ist
(„Your team has no devices"); das ist dann eine Portal-Sache, kein Projektfehler.
### Der Lauf ### Der Lauf
+1 -1
View File
@@ -26,7 +26,7 @@ Datei dazu.
Zwischenablage und Kalendereintrag sind in der installierten App wirkungslos. Zwischenablage und Kalendereintrag sind in der installierten App wirkungslos.
Beide Korrekturen liegen im Repository und warten nur auf einen Lauf. Beide Korrekturen liegen im Repository und warten nur auf einen Lauf.
*Was zu tun ist:* am Mac `git pull`, dann *Was zu tun ist:* am Mac nur
`bash companion-app/scripts/ios-signieren.sh`, danach die fertige `.ipa` pushen `bash companion-app/scripts/ios-signieren.sh`, danach die fertige `.ipa` pushen
(das Telefon lädt sie aus dem Repository). Ein Befehl - das Skript prüft sich (das Telefon lädt sie aus dem Repository). Ein Befehl - das Skript prüft sich
selbst und bricht ab, wenn etwas fehlt; von Hand bleiben das selbst und bricht ab, wenn etwas fehlt; von Hand bleiben das
+36 -22
View File
@@ -45,10 +45,12 @@ ARCHIV="$AUSGABE/DataMetric360.xcarchive"
cd "$APP" cd "$APP"
# Ohne diese Pruefung baut/signiert das Skript zuverlaessig - aber nur den # Ohne diese Pruefung baut/signiert das Skript zuverlaessig - aber nur den
# Stand, der gerade lokal ausgecheckt ist. Ein vergessenes `git pull` waere # Stand, der gerade lokal ausgecheckt ist. Ein vergessener Abgleich mit dem
# damit ein stiller Veraltungsfehler wie schon mehrfach an anderer Stelle in # Server waere damit ein stiller Veraltungsfehler wie schon mehrfach an anderer
# diesem Projekt (Cache-Busting, OTA-Buendel-Abgleich, VERSIONIERUNG.md) - hier # Stelle in diesem Projekt (Cache-Busting, OTA-Buendel-Abgleich,
# stattdessen ein lauter Abbruch statt eines optisch/inhaltlich alten Builds. # VERSIONIERUNG.md). Deshalb holt das Skript den Serverstand selbst und rueckt
# den Klon zurecht, wo das verlustfrei ist - und bricht sonst laut ab, statt
# einen optisch/inhaltlich alten Build auszuliefern.
echo "==> Git-Stand pruefen" echo "==> Git-Stand pruefen"
if [ -n "$(git status --porcelain)" ]; then if [ -n "$(git status --porcelain)" ]; then
echo "FEHLER: Uncommittete Aenderungen im Arbeitsverzeichnis." >&2 echo "FEHLER: Uncommittete Aenderungen im Arbeitsverzeichnis." >&2
@@ -59,32 +61,44 @@ fi
git fetch origin git fetch origin
LOKAL="$(git rev-parse HEAD)" LOKAL="$(git rev-parse HEAD)"
FERN="$(git rev-parse origin/main)" FERN="$(git rev-parse origin/main)"
# Ziel dieses Blocks: der Klon steht danach auf origin/main - oder das Skript
# bricht ab. Zurechtruecken darf es sich selbst, solange dabei nachweislich
# nichts verlorengeht; der saubere Arbeitsbaum ist oben bereits Bedingung.
if [ "$LOKAL" != "$FERN" ]; then if [ "$LOKAL" != "$FERN" ]; then
echo "FEHLER: Lokaler Stand ($LOKAL) weicht von origin/main ($FERN) ab." >&2 BASIS="$(git merge-base "$LOKAL" "$FERN")"
if git merge-base --is-ancestor "$LOKAL" "$FERN"; then if git merge-base --is-ancestor "$LOKAL" "$FERN"; then
echo "Dieser Klon ist nur zurueck. 'git pull' holt den Rest, dann erneut versuchen." >&2 echo " Klon war zurueck - wird vorgespult."
git merge --ff-only "$FERN" >/dev/null
LOKAL="$(git rev-parse HEAD)"
elif git diff --quiet "$BASIS" "$LOKAL"; then
# Hier liegen Commits, die origin/main nicht hat - aber ihr Tree ist derselbe
# wie der des gemeinsamen Vorfahren, sie tragen inhaltlich also NICHTS bei.
# Genau so sieht ein Klon aus, der noch vor einem Rewrite der Gegenseite steht
# (am 07.09.2026 einmal geschehen). Ein 'git pull' waere hier falsch: der Merge
# holt die entfernten Commits zurueck, und der naechste Push stellt sie auf dem
# Server wieder her - sogar ohne --force. Verwerfen kostet dagegen keine einzige
# Datei; das Reflog haelt sie noch 90 Tage.
echo " Nur hier vorhanden, inhaltlich ohne Wirkung - wird verworfen:"
git log --oneline "$BASIS..$LOKAL" | sed "s/^/ /"
git reset --hard "$FERN" >/dev/null
LOKAL="$(git rev-parse HEAD)"
echo " Zurueckgesetzt auf origin/main. Rueckholbar ueber: git reflog"
else else
# Hier liegen Commits, die origin/main nicht hat. Git kann NICHT unterscheiden, echo "FEHLER: Lokaler Stand ($LOKAL) weicht von origin/main ($FERN) ab." >&2
# ob das eigene ungepushte Arbeit ist oder ob die Gegenseite sie absichtlich aus
# der Historie geworfen hat - beides sieht gleich aus. Am 07.09.2026 war es der
# zweite Fall, und dort waere ein Push (er ginge sogar ohne --force durch) die
# stille Wiederherstellung genau dessen, was entfernt werden sollte. Deshalb
# entscheidet das Skript hier nichts, sondern legt beide Moeglichkeiten vor.
echo "" >&2 echo "" >&2
echo "Diese Commits liegen nur hier und nicht auf origin/main:" >&2 echo "Diese Commits liegen nur hier und aendern auch den Inhalt:" >&2
git log --oneline "$FERN..$LOKAL" >&2 git log --oneline "$BASIS..$LOKAL" >&2
echo "" >&2 echo "" >&2
echo "Dafuer gibt es zwei Gruende, und sie verlangen das Gegenteil voneinander:" >&2 echo "Das sieht nach eigener, noch ungepushter Arbeit aus - deshalb entscheidet" >&2
echo " a) eigene, noch ungepushte Arbeit" >&2 echo "das Skript hier nichts. Entweder pushen:" >&2
echo " -> git push" >&2 echo " git push" >&2
echo " b) die Gegenseite hat sie absichtlich aus der Historie entfernt" >&2 echo "oder verwerfen, falls die Gegenseite sie absichtlich entfernt hat:" >&2
echo " -> git fetch origin && git reset --hard origin/main" >&2 echo " git fetch origin && git reset --hard origin/main" >&2
echo "" >&2 echo "" >&2
echo "Im Fall b) waere ein Push - und ebenso ein 'git pull' mit Merge - genau die" >&2 echo "Kein 'git pull': ein Merge haengt die Commits wieder in die Historie." >&2
echo "Wiederherstellung dessen, was dort entfernt wurde. Also erst nachsehen." >&2
fi
exit 1 exit 1
fi fi
fi
echo " HEAD == origin/main ($LOKAL), aktuell." echo " HEAD == origin/main ($LOKAL), aktuell."
echo "==> Webbuendel bauen" echo "==> Webbuendel bauen"