diff --git a/AGENTS.md b/AGENTS.md index 96fab64..6b7440a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. --- diff --git a/APPLE_DEV_BACKLOG.md b/APPLE_DEV_BACKLOG.md index 5d0e11f..17f1fc6 100644 --- a/APPLE_DEV_BACKLOG.md +++ b/APPLE_DEV_BACKLOG.md @@ -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 diff --git a/OFFEN.md b/OFFEN.md index 6582fca..53c6f9c 100644 --- a/OFFEN.md +++ b/OFFEN.md @@ -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 diff --git a/companion-app/scripts/ios-signieren.sh b/companion-app/scripts/ios-signieren.sh index 631063a..c304ce2 100755 --- a/companion-app/scripts/ios-signieren.sh +++ b/companion-app/scripts/ios-signieren.sh @@ -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."