Neustart-Erkennung: Zeilenenden zaehlen nicht als Aenderung
Am echten Update-Weg gemessen: die installierte Fassung lag mit CRLF auf der Platte, das Gitea-Archiv liefert LF. Drei .py-Dateien galten dadurch als geaendert, obwohl sie nach Entfernen der CR byteweise identisch waren - die Erkennung verlangte einen Neustart fuer einen rein kosmetischen Unterschied. Textdateien werden jetzt vor dem Hashen auf LF normalisiert. Binaerdateien (Schriften, Bilder, das OTA-Zip) bleiben roh, dort waere 0x0D0A echter Inhalt. Drei neue Testfaelle: reiner Zeilenenden-Unterschied, echte Aenderung trotz verschiedener Zeilenenden, Binaerdatei. 26 Tests der Suite gruen. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -217,6 +217,37 @@ def version_von_platte() -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Endungen, bei denen Zeilenenden nichts bedeuten.
|
||||||
|
#
|
||||||
|
# Am 05.09.2026 am echten Update-Weg gemessen: die installierte Fassung lag mit
|
||||||
|
# CRLF auf der Platte (Windows-Arbeitsstand), das Gitea-Archiv liefert LF -
|
||||||
|
# geokodierung.py, screening.py und veroeffentlichung.py galten dadurch als
|
||||||
|
# geaendert, obwohl sie nach Entfernen der CR byteweise identisch waren. Die
|
||||||
|
# Erkennung meldete "Neustart noetig" fuer einen rein kosmetischen Unterschied.
|
||||||
|
#
|
||||||
|
# Binaerdateien (Schriften, Bilder, das OTA-Zip) bleiben bewusst aussen vor:
|
||||||
|
# dort waere 0x0D0A echter Inhalt, und ein normalisierter Vergleich koennte
|
||||||
|
# einen echten Unterschied verschlucken.
|
||||||
|
TEXTENDUNGEN = (".py", ".json", ".yaml", ".yml", ".md", ".txt", ".js", ".css", ".html", ".svg")
|
||||||
|
|
||||||
|
|
||||||
|
def _dateihash(pfad: str) -> str:
|
||||||
|
"""sha256 einer Datei - bei Textdateien ueber auf LF normalisierten Inhalt."""
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
if pfad.lower().endswith(TEXTENDUNGEN):
|
||||||
|
try:
|
||||||
|
with open(pfad, "rb") as datei:
|
||||||
|
roh = datei.read()
|
||||||
|
hasher.update(roh.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
|
return hasher.hexdigest()
|
||||||
|
except OSError:
|
||||||
|
raise
|
||||||
|
with open(pfad, "rb") as datei:
|
||||||
|
for block in iter(lambda: datei.read(65536), b""):
|
||||||
|
hasher.update(block)
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def _dateihashes(ordner: str) -> dict[str, str]:
|
def _dateihashes(ordner: str) -> dict[str, str]:
|
||||||
"""Alle Dateien unter `ordner` als {relativer Pfad: sha256}.
|
"""Alle Dateien unter `ordner` als {relativer Pfad: sha256}.
|
||||||
|
|
||||||
@@ -228,11 +259,7 @@ def _dateihashes(ordner: str) -> dict[str, str]:
|
|||||||
for name in dateien:
|
for name in dateien:
|
||||||
pfad = os.path.join(wurzel, name)
|
pfad = os.path.join(wurzel, name)
|
||||||
rel = os.path.relpath(pfad, ordner).replace(os.sep, "/")
|
rel = os.path.relpath(pfad, ordner).replace(os.sep, "/")
|
||||||
hasher = hashlib.sha256()
|
aus[rel] = _dateihash(pfad)
|
||||||
with open(pfad, "rb") as datei:
|
|
||||||
for block in iter(lambda: datei.read(65536), b""):
|
|
||||||
hasher.update(block)
|
|
||||||
aus[rel] = hasher.hexdigest()
|
|
||||||
return aus
|
return aus
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "audi_dashboard",
|
"domain": "audi_dashboard",
|
||||||
"name": "Audi Dashboard",
|
"name": "Audi Dashboard",
|
||||||
"version": "2026.9.5.24",
|
"version": "2026.9.5.25",
|
||||||
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
|
|||||||
@@ -253,6 +253,34 @@ class NeustartNoetig(unittest.TestCase):
|
|||||||
self._schreiben(self.neu, "__pycache__/x.cpython-312.pyc", "egal")
|
self._schreiben(self.neu, "__pycache__/x.cpython-312.pyc", "egal")
|
||||||
self.assertFalse(a.neustart_noetig(self.alt, self.neu))
|
self.assertFalse(a.neustart_noetig(self.alt, self.neu))
|
||||||
|
|
||||||
|
def test_zeilenenden_zaehlen_nicht_als_aenderung(self):
|
||||||
|
"""Am 05.09.2026 am echten Update-Weg gemessen.
|
||||||
|
|
||||||
|
Die installierte Fassung lag mit CRLF auf der Platte, das Gitea-Archiv
|
||||||
|
liefert LF - drei .py-Dateien galten dadurch als geaendert, obwohl sie
|
||||||
|
nach Entfernen der CR byteweise identisch waren, und die Erkennung
|
||||||
|
verlangte einen Neustart fuer einen rein kosmetischen Unterschied."""
|
||||||
|
with open(os.path.join(self.alt, "__init__.py"), "w", newline="\r\n", encoding="utf-8") as datei:
|
||||||
|
datei.write("x = 1\ny = 2\n")
|
||||||
|
with open(os.path.join(self.neu, "__init__.py"), "w", newline="\n", encoding="utf-8") as datei:
|
||||||
|
datei.write("x = 1\ny = 2\n")
|
||||||
|
self.assertFalse(a.neustart_noetig(self.alt, self.neu))
|
||||||
|
|
||||||
|
def test_echte_aenderung_bleibt_erkannt_trotz_zeilenenden(self):
|
||||||
|
with open(os.path.join(self.alt, "__init__.py"), "w", newline="\r\n", encoding="utf-8") as datei:
|
||||||
|
datei.write("x = 1\n")
|
||||||
|
with open(os.path.join(self.neu, "__init__.py"), "w", newline="\n", encoding="utf-8") as datei:
|
||||||
|
datei.write("x = 2\n")
|
||||||
|
self.assertTrue(a.neustart_noetig(self.alt, self.neu))
|
||||||
|
|
||||||
|
def test_binaerdatei_wird_nicht_normalisiert(self):
|
||||||
|
# Bei einer Schriftdatei ist 0x0D0A echter Inhalt - hier darf nicht
|
||||||
|
# normalisiert werden, sonst verschwaende ein echter Unterschied.
|
||||||
|
for ordner, inhalt in ((self.alt, b"\x00\r\n\x01"), (self.neu, b"\x00\n\x01")):
|
||||||
|
with open(os.path.join(ordner, "schrift.woff2"), "wb") as datei:
|
||||||
|
datei.write(inhalt)
|
||||||
|
self.assertTrue(a.neustart_noetig(self.alt, self.neu))
|
||||||
|
|
||||||
def test_unlesbarer_ordner_gilt_als_neustart(self):
|
def test_unlesbarer_ordner_gilt_als_neustart(self):
|
||||||
self.assertTrue(a.neustart_noetig(os.path.join(self.basis, "gibtsnicht"), self.neu))
|
self.assertTrue(a.neustart_noetig(os.path.join(self.basis, "gibtsnicht"), self.neu))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user