Skip to content

Commit 9389f6d

Browse files
committed
fix: Keep mtimes of unchanged JSON exports stable
1 parent d4146d4 commit 9389f6d

1 file changed

Lines changed: 34 additions & 7 deletions

File tree

scripts/bone-export.clj

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,18 @@
217217
"Recursively copy `src` into `dst`, creating `dst` if needed.
218218
Used to seed staging subdirectories with the previous export so an
219219
incremental run does not lose files it chooses not to rewrite (see
220-
the call site). Relies on `file-seq`'s pre-order traversal so each
220+
the call site). Copies file attributes so a seeded file the run
221+
keeps retains its mtime -- the web server derives ETag/Last-Modified
222+
from it, and conditional GETs would otherwise re-download unchanged
223+
files. Relies on `file-seq`'s pre-order traversal so each
221224
directory is created before its children are copied."
222225
[^java.io.File src ^java.io.File dst]
223226
(when (.exists src)
224227
(let [src-path (.toPath src)
225228
dst-path (.toPath dst)
226229
opts (into-array java.nio.file.CopyOption
227-
[java.nio.file.StandardCopyOption/REPLACE_EXISTING])]
230+
[java.nio.file.StandardCopyOption/REPLACE_EXISTING
231+
java.nio.file.StandardCopyOption/COPY_ATTRIBUTES])]
228232
(doseq [^java.io.File f (file-seq src)
229233
:let [target (.resolve dst-path (.relativize src-path (.toPath f)))]]
230234
(if (.isDirectory f)
@@ -834,8 +838,28 @@
834838
;; Per-source export functions
835839
;; ---------------------------------------------------------------------------
836840

841+
(defn- json-unchanged?
842+
"True when `file` already holds the JSON we are about to write.
843+
Incremental staging is seeded from the previous export with mtimes
844+
preserved, so leaving an identical file untouched keeps its mtime
845+
and thus the ETag/Last-Modified the web server derives from it.
846+
When `envelope` carries the per-run :generated stamp, compare the
847+
parsed data without it: skipped runs already leave the stamp stale,
848+
so it effectively tracks the last time the data changed."
849+
[file json-str envelope]
850+
(let [f (io/file file)]
851+
(and (.exists f)
852+
(let [prev (slurp f)]
853+
(if (contains? envelope :generated)
854+
(try (= (dissoc (json/parse-string prev true) :generated)
855+
(dissoc (json/parse-string json-str true) :generated))
856+
(catch Exception _ false))
857+
(= prev json-str))))))
858+
837859
(defn dump-json!
838-
"Dump reports as JSON for a single source."
860+
"Dump reports as JSON for a single source.
861+
Leaves a file whose content did not change untouched (see
862+
`json-unchanged?`)."
839863
([reports out-dir source-name source-map maintainers-map]
840864
(dump-json! reports out-dir source-name source-map maintainers-map "all.json" nil))
841865
([reports out-dir source-name source-map maintainers-map basename]
@@ -848,10 +872,13 @@
848872
:reports data}
849873
(seq meta) (merge meta)
850874
(seq extra-meta) (merge extra-meta))
851-
filename (str out-dir "/" basename)]
852-
(spit filename (json/generate-string envelope {:pretty true}))
853-
(when (seq data)
854-
(log/info "Wrote" (count data) "reports to" filename)))))
875+
filename (str out-dir "/" basename)
876+
json-str (json/generate-string envelope {:pretty true})]
877+
(if (json-unchanged? filename json-str envelope)
878+
(log/info "Unchanged" filename)
879+
(do (spit filename json-str)
880+
(when (seq data)
881+
(log/info "Wrote" (count data) "reports to" filename)))))))
855882

856883
(def ^:private rfc822-formatter
857884
;; DateTimeFormatter is immutable and thread-safe. Pattern matches

0 commit comments

Comments
 (0)