Zeitstempel von Dateien unter Versionsverwaltung
Die Verwaltung von Dateien in einem Versionskontrollsystem (VCS) wie git hat einen Nachteil: der Zeitstempel im Dateisystem gibt nicht mehr den Zeitpunkt der letzten Änderung an dieser Datei wieder, sondern den Zeitpunkt, zu dem sie zuletzt (ggf. mit vielen anderen Dateien) ausgecheckt wurde.
Für Webseiten bedeutet das, dass man für die Ausgabe des Datums der letzten Änderung nicht mehr auf den Zeitstempel der entsprechenden Datei zurückgreifen kann. Das macht mir nichts aus, denn zum einen verwende ich mittlerweile weitgehend statische Webseiten, bei denen das ohnehin nicht ohne weiteres möglich wäre, und zum anderen bringt dieser Ansatz zwar den Vorteil der Automatisierung mit sich, aber zugleich den - aus meiner Sicht überwiegenden - Nachteil, dass jeder verbesserte Tippfehler, jede unsichtbare Änderung am Seitengerüst als “Update” vermerkt wird, obwohl der Inhalt möglicherweise weiterhin völlig veraltet ist. Ich ziehe es daher vor, selbst zu entscheiden, was als “Änderung” gilt, und dann das Datum der letzten Änderung selbst zu setzen.
So weit, so gut - aber wie sieht das mit Downloaddateien aus?
Zeitstempel von Downloadangeboten passend setzen
Jedenfalls dann, wenn man Downloadangebote nicht nur verlinkt, sondern den Downloadbereich auch als Dateiübersicht zugänglich macht - wie ich es auf meinen Webseiten tue -, ist es für den Nutzer gelinde gesagt verwirrend, wenn der angezeigte Zeitstempel der Dateien nichts mit der letzten Änderung derselben zu tun hat.
Dafür gibt es natürlich verschiedene einfache Lösungen: Zum einen könnte ich darauf verzichten, einen Downloadbereich in diesem Sinne anzuzeigen, und die Dateien einfach nur verlinken. Tatsächlich sind die meisten Downloadangebote irgendwo auf meinen Seiten verlinkt, und ich speichere für sie auch Metadaten wie bspw. das Datum der letzten Änderung, das bei dem Link angezeigt wird, wie bspw. bei meinen Downloads aus dem Bereich des Rettungswesens. Ich schätze es aber gerade, dass man in den Downloadangeboten auch “einfach so” stöbern kann. - Zum anderen könnte ich darauf verzichten, Downloads unter Versionskontrolle zu stellen, und sie in einem gesonderten Archiv speichern. Gerade für Software-Releases - und auch meine FAQs - wäre das vernünftig; beide werden ohnehin in gesonderten git-Repositories versioniert, so dass es nicht erforderlich ist, einzelne Releases nochmal im Repository meiner Webseiten zu speichern. Andererseits gibt es aber Downloads, die nicht selbst unter Versionskontrolle stehen, und - mir viel wichtiger - eine getrennte Speicherung der Downloadangebote würde bedeuten, dass ich meine Webseiten nicht “aus einem Guss” aus dem git-Repository erzeugen bzw. wiederherstellen kann; ich müsste dann den Downloadbereich getrennt sichern und einspielen.
Trotz allem denke ich regelmäßig darüber nach, die Downloadangebote aus der Versionskontrolle herauszunehmen (das ist ja ein wenig auch eine Frage der Größe des Repositories). Bis dahin wollte ich aber eine andere Lösung für das Problem der “falschen” Zeitstempel im Directory-Listing - und eigentlich kann das so schwierig ja nicht sein: immerhin habe ich Metadaten, aus denen sich für jede einzelne Datei im Downloadbereich das Datum der letzten (inhaltlichen) Änderung ergibt. Wie schwer kann es sein, den Zeitstempel - also das Datum der letzten Änderung der Datei im Dateisystem, die sog. modification time oder kurz mtime - passend zu setzen?
Wie sich herausstellt: nicht sehr schwer. Es genügt, ein paar Zeilen als postprocess
-Block in der Nanoc-Konfiguration hinzuzufügen:
- postprocess do
- # iterate over all items starting with /archives/ or /net/usenet/faqs/ with kind “file” or “faq” that have “updated_at” set
- items.select { |item| %r<^/(archives|net/usenet/faqs)/> =~ item.identifier && [ ‘file’, ‘faq’ ].include?(item[:kind]) && !item[:updated_at].nil? }.each do |item|
- # prefer the “text” representation, if available (for FAQs)
- unless item.reps[:text].nil?
- path = item.path(rep: :text)
- else
- path = item.path
- end
- # use the output files, not the source
- path = @config[:output_dir] + path
- # change the mtime to “updated_at”
- # “updated_at” is part Date (when set to 2017-10-01), part String (when set to ‘2017-10-01’),
- # so it has to be converted to strings and parsed (instead of using .to_time)
- FileUtils.touch path, :mtime => Time.parse(item[:updated_at].to_s)
- end
- end
Gut, die Zeitstempel sind jetzt immer noch auffällig, weil ich natürlich nur das Datum der letzten Änderung mitführe, nicht die Uhrzeit, und die Dateien daher scheinbar alle um Mitternacht erzeugt werden, aber damit lässt sich leben. Einziger Nachteil: Die unerwartet “alten” Dateien im Downloadbereich bringen das Dependency-Tracking von Nanoc durcheinander und werden daher bei jedem Durchlauf neu erzeugt, weil die Ausgabedateien (mit einem Zeitstempel von vor einigen Monaten oder Jahren) scheinbar älter sind als die Quelldateien (die frisch aus der Versionskontrolle ausgecheckt wurden und daher einen aktuellen Zeitstempel haben), aber damit lässt sich leben.
Symlinks erzeugen
Außerdem konnte ich so ein weiteres Problem lösen:
Die Downloads meiner Scripts (und anderer Software) tragen die Versionsnummer im Dateinamen, bspw. checkmail-0-6-1.tar.gz
für die Version 0.6.1 von checkmail
. Ich möchte aber zugleich immer die aktuellste Version unter dem generischen Namen checkmail.tar.gz
anbieten. Nichts einfacher als das: checkmail.tar.gz
muss ein Symlink auf den jeweils neuesten “versionierten” Dateinamen sein. Das lässt sich so auch in das gir-Repository einchecken … nur bearbeite ich meine Webseiten unter Windows - und da ist das Konzept von symbolic links eher nicht so bekannt. Dieses Problem hatte ich vorübergehend “gelöst”, indem ich einfach zwei identische Dateien gespeichert habe - aber das ist natürlich ein nerviger Hack, der doppelt Platz braucht und einfach nicht elegant ist.
Nachdem ich jetzt ohnehin die Dateien im Downloadbereich nach ihrer Generierung nachbehandle, lassen sich dabei auch direkt die Symlinks erzeugen - zwar immer noch aus einer Platzhalterdatei, aber dafür genügt dann eine leere Datei, die nur den passenden Namen trägt. Mein Ansatz dafür ist folgender (eingefügt in den oben bereits gezeigten Block):
- postprocess do
- […]
- # use the output files, not the source
- path = @config[:output_dir] + path
- # generate symlinks
- if item[:kind] == ‘file’ && item[:current]
- # remove placeholder and symlink generic name (‘myprog.tar.gz’)
- # to current file (‘myprog-1-2-17.tar.gz’)
- FileUtils.rm path
- FileUtils.ln_s item[:current], path
- # mtime shall be changed for the current file
- path = File.dirname(path) + ‘/’ + item[:current]
- end
- # change the mtime to “updated_at”
- […]
- end
Insgesamt fühlt sich das immer noch wie ein Hack an, aber immerhin nicht mehr ganz so krude.
Kommentare
Ansicht der Kommentare: Linear | Verschachtelt