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 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 22:01:32 +02:00
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
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
trap.** On that day `main`'s history was rewritten once - three commits at the tip, all of
which contained nothing but a document that has since moved to its own repository, were
**Nachtrag 2026-09-07: the guard repairs the clone itself where that is provably
lossless.** On that day `main`'s history was rewritten once - three commits at the tip, all
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
is now *ahead* of `origin/main`, not behind it**, so `git pull` there is exactly wrong: the
merge pulls the removed commits back into the history, and the next push restores them on
the server - without even needing `--force`. Git cannot tell that apart from genuine
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`)
and aborts. Only the plain behind-case still recommends `git pull`. Both branches verified
against throwaway repositories built to each shape, not just parsed - the first version of
this fix classified the situation as "diverged" and would have told the Mac to push.
holds commits `origin/main` does not**, so `git pull` there is exactly wrong: the merge
pulls the removed commits back into the history, and the next push restores them on the
server - without even needing `--force`.
Git cannot tell that apart from genuine unpushed work by the commit graph alone, but it can
by the **trees**: if `HEAD`'s tree equals the merge-base's tree, the local-only commits
contribute no content at all, and discarding them cannot lose a single file. That is the
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
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
git pull
```
> **Warum kein `git pull`:** am 07.09.2026 sind drei Commits am Ende von `main`
> 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
> Commits am Ende von `main` entfernt und der Branch per Force-Push
> zurückgesetzt worden. Ein älterer Klon steht damit **vor** `origin/main` und
> `git pull` ist dort das Falsche — es würde die entfernten Commits per Merge
> zurückholen, und der nächste Push stellte sie auf dem Server wieder her (er
> ginge sogar ohne `--force` durch). Richtig ist einmalig:
>
> ```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.
Ab **hier** entscheidet es nichts mehr allein: liegen lokale Commits, die **auch
den Inhalt** ändern, bricht es ab und legt dir beide Wege vor (pushen oder
verwerfen) - ungepushte Arbeit wirft es nie weg.
Ein nicht sauberer Arbeitsbaum bricht den Lauf ebenfalls ab - er ist die
Voraussetzung dafür, dass Zurechtrücken oben gefahrlos ist. Und ebenso bricht er
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
+1 -1
View File
@@ -26,7 +26,7 @@ Datei dazu.
Zwischenablage und Kalendereintrag sind in der installierten App wirkungslos.
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
(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
+36 -22
View File
@@ -45,10 +45,12 @@ ARCHIV="$AUSGABE/DataMetric360.xcarchive"
cd "$APP"
# Ohne diese Pruefung baut/signiert das Skript zuverlaessig - aber nur den
# Stand, der gerade lokal ausgecheckt ist. Ein vergessenes `git pull` waere
# damit ein stiller Veraltungsfehler wie schon mehrfach an anderer Stelle in
# diesem Projekt (Cache-Busting, OTA-Buendel-Abgleich, VERSIONIERUNG.md) - hier
# stattdessen ein lauter Abbruch statt eines optisch/inhaltlich alten Builds.
# Stand, der gerade lokal ausgecheckt ist. Ein vergessener Abgleich mit dem
# Server waere damit ein stiller Veraltungsfehler wie schon mehrfach an anderer
# Stelle in diesem Projekt (Cache-Busting, OTA-Buendel-Abgleich,
# 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"
if [ -n "$(git status --porcelain)" ]; then
echo "FEHLER: Uncommittete Aenderungen im Arbeitsverzeichnis." >&2
@@ -59,31 +61,43 @@ fi
git fetch origin
LOKAL="$(git rev-parse HEAD)"
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
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
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
# Hier liegen Commits, die origin/main nicht hat. Git kann NICHT unterscheiden,
# 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 "FEHLER: Lokaler Stand ($LOKAL) weicht von origin/main ($FERN) ab." >&2
echo "" >&2
echo "Diese Commits liegen nur hier und nicht auf origin/main:" >&2
git log --oneline "$FERN..$LOKAL" >&2
echo "Diese Commits liegen nur hier und aendern auch den Inhalt:" >&2
git log --oneline "$BASIS..$LOKAL" >&2
echo "" >&2
echo "Dafuer gibt es zwei Gruende, und sie verlangen das Gegenteil voneinander:" >&2
echo " a) eigene, noch ungepushte Arbeit" >&2
echo " -> git push" >&2
echo " b) die Gegenseite hat sie absichtlich aus der Historie entfernt" >&2
echo " -> git fetch origin && git reset --hard origin/main" >&2
echo "Das sieht nach eigener, noch ungepushter Arbeit aus - deshalb entscheidet" >&2
echo "das Skript hier nichts. Entweder pushen:" >&2
echo " git push" >&2
echo "oder verwerfen, falls die Gegenseite sie absichtlich entfernt hat:" >&2
echo " git fetch origin && git reset --hard origin/main" >&2
echo "" >&2
echo "Im Fall b) waere ein Push - und ebenso ein 'git pull' mit Merge - genau die" >&2
echo "Wiederherstellung dessen, was dort entfernt wurde. Also erst nachsehen." >&2
echo "Kein 'git pull': ein Merge haengt die Commits wieder in die Historie." >&2
exit 1
fi
exit 1
fi
echo " HEAD == origin/main ($LOKAL), aktuell."