No way to see what changed between two mirrors#721
Merged
Conversation
--update already knows which resources were new, which changed and which the server called unchanged, and throws it away: the flags reach file_notify() and go no further than a log line, while deletions exist only as a side effect of purging. --changes (-%d) keeps all of it and writes hts-changes.json plus a one-line summary in the log. "Changed" means the bytes differ, not that the server re-sent the resource. Comparing the mirrored files directly would not work: HTTrack stamps every parsed page with the crawl date via the footer, so those bytes differ on every run. Payloads are compared instead, the previous one coming from the cache for parsed pages and from the local copy sampled just before it is overwritten for everything else. The mirror-relative path, not the URL, is the accumulator's key, so a redirect and its target that share a save name are one entry; and only the first notify for a file samples its pre-run state, so a retried transfer is not counted twice. What counts as already mirrored comes from the previous run's file index rather than from the file's presence on disk: a partial left by this crawl's own failed attempt is on disk but was never part of the previous mirror. The deleted set is now computed whether or not purging is enabled; unlinking still happens only under --purge-old. Closes #714 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com>
A crawl that mirrored nothing created no accumulator, so hts_changes_close_opt returned without writing and the previous run's report stayed on disk as if it described this one. Write it whenever --changes is on. The no-data rollback is the deliberate exception, and is now documented: it restores the previous cache generation, so leaving the matching report alone is the consistent behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com>
The size shortcut compared rendered on-disk sizes even for parsed pages, whose payload digests describe something else entirely, so it decided the outcome before the payload comparison could run: a page whose payload never changed but whose rewritten links moved read as changed. It now only applies when both digests describe the file on disk. file_notify() reaches the accumulator from the FTP download thread as well as the main one, and the lazy allocation, the coucal write and the entries realloc were all unguarded. Every entry point now takes a mutex, and the HTML hook does its cache read before taking it, since that read can itself re-enter file_notify() and move the array. Two fixtures cover what nothing did: a gzipped direct-to-disk body that changes at constant length (without the pre-sample before the decoded temp is renamed, it reads as unchanged), and a page with a fixed payload behind a redirect whose target is renamed, so its file on disk changes length while its bytes do not. Both were checked against builds with the respective fix reverted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com>
Both sides appended to tests/Makefile.am's TESTS; kept master's 86_local-proxytrack-cache-longfields.test alongside 88_local-changes.test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com>
Records the second parent: the previous commit carried master's content but was committed after a stash cleared MERGE_HEAD, so git never saw it as a merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> # Conflicts: # tests/Makefile.am
…740) The feature PR added the two LANG_CHANGES* entries to lang.def with English and Francais only; every other locale fell back to English in the WebHTTrack form. Each file is written in its own declared charset. Signed-off-by: Xavier Roche <roche@httrack.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Conflict in tests/Makefile.am only: master took 87 and 90, PR #718 holds 88, so the change-report test moves to 89. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
Lock the report path against the FTP thread the crawl never joins, seal the accumulator once the report is written, key entries off the project directory so the report survives --cache=0, stop calling a file gone when the crawl only failed to re-fetch it, and skip the hook's work entirely when --changes is off. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
Adds a changes-race self-test (the FTP shape: notifier threads against the report path), fixtures for a transfer the crawl never completes, for a leftover file at a name the crawl mirrors fresh, and for a cache-off mirror, plus a pass with purging on. Registers the web GUI's --changes box with the clearing companion master's #725 now requires, and documents the degraded mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
Conflict in tests/Makefile.am only: master took 89, so the change-report test moves to 93 (PR #720 holds 92). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
A connection killed before the status line surfaces differently on macOS, where it truncates the mirrored file to zero (#748). Assert only what holds on both: it is never reported gone, and its file survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
Conflict in tests/Makefile.am only: master added 88 and 92; the change-report test keeps 93. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
xroche
added a commit
that referenced
this pull request
Jul 27, 2026
Master landed the change report (#721) and tests 88-93 while this branch was open. Every conflict was both branches appending to the same tail, so each is resolved as a union: htsopt.h keeps the change-report fields at their published offsets and appends the single-file pair after them, and the lang files, GUI templates and selftests carry both features' entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
--updatealready works out which resources are new, which changed and which the server reports unchanged, then drops it: the flags reachfile_notify()and go no further than a log line, and deletions exist only as a side effect of purging.--changes(-%d) keeps all of it and writeshts-changes.jsonnext to the log, with the four lists, their counts and a one-line summary in the log."Changed" means the bytes differ, not that the server re-sent the resource. Comparing the mirrored files does not work for that: HTTrack stamps every parsed page with the crawl date through the footer, so those bytes move on every run and the whole site reads as changed. The comparison is on payloads instead. For parsed pages the previous payload is the body the cache kept; everywhere else the mirrored file is the payload verbatim, and the previous copy is hashed just before it gets overwritten. With no cache and nothing to hash, parsed pages fall back to the 200-versus-304 signal. The cache also carries the mirror's file index, so
--cache=0leaves no previous mirror to subtract from: nothing is reported gone, andfirst_crawlcomes outnullrather than guessed.Accounting is keyed on the mirror-relative path, not the URL, so a redirect and its target that share a save name are one entry, and only the first notify for a file samples its pre-run state, so a retried transfer is not counted twice. What counts as already mirrored comes from the previous run's file index rather than from the file being on disk: a partial left by this crawl's own failed attempt is on disk but was never part of the previous mirror. A resource the crawl tried and failed to fetch drops out of that index too, but its copy is untouched, so it is reported unchanged rather than gone. The deleted set is now computed whether or not purging is on; unlinking still happens only under
--purge-old.The accumulator is locked on both sides. FTP transfers notify from a thread the crawl never joins, so one can arrive while the report is being resolved and written, and the report path used to run unlocked over an array
realloct()was moving underneath it. Writing the report also seals the accumulator: a notify that lands afterwards is dropped instead of starting a second report nobody will write. With the feature off the hooks return before doing any work, so an--updatewithout--changesperforms the same cache reads as master and produces the same mirror.htsopt.hgains two tail-appended fields onhttrackp(changes,changes_state), so this is an installed-header change and anything embedding the struct needs a rebuild. I have not touchedVERSION_INFO. Content diffs and keeping the previous copy of a changed page are out of scope on purpose: both change what a mirror directory holds and run into the purge invariant.tests/93_local-changes.testis a three-pass crawl whose routes all answer 200 with noLast-Modifiedand noETag, so a naive implementation calls the whole site changed; it asserts each bucket exactly, covers a redirect, a retried direct-to-disk transfer, a transfer that never completes, a pass with purging on and a mirror crawled with the cache off.httrack -#test=changescovers the bucket rules and the JSON escaping, invalid UTF-8 included, and-#test=changes-raceruns notifier threads against the report path for the FTP shape (clean under ThreadSanitizer, fifteen races with the lock removed). Every rule above was graded against a build with that rule mutated, to check the tests go red. The file format is documented inhtml/changes.html, and the WebHTTrack toggle, man page and command-line guide are updated; WinHTTrack and Android only need the flag added to the argv they already build.Closes #714