[mirror] Scalable static site server for Git forges (like GitHub Pages)

Stabilize features `patch` and `archive-site`.

The PATCH method has been tested by myself and on Codeberg and found
to work satisfactorily.

Because using PATCH causes the git-pages server to store state that
is not necessarily easily reproducible from any single specific source
(i.e. it stores a composition of many disparate requests), it may be
necessary to back it up. For this, the feature `archive-site` is also
stabilized. It has not seen much use but not providing a backup method
would be a disservice.

Changed files
+4 -9
src
+3 -3
README.md
··· 68 68 - Site URLs that have a path starting with `.git-pages/...` are reserved for _git-pages_ itself. 69 69 - The `.git-pages/health` URL returns `ok` with the `Last-Modified:` header set to the manifest modification time. 70 70 - The `.git-pages/manifest.json` URL returns a [ProtoJSON](https://protobuf.dev/programming-guides/json/) representation of the deployed site manifest with the `Last-Modified:` header set to the manifest modification time. It enumerates site structure, redirect rules, and errors that were not severe enough to abort publishing. Note that **the manifest JSON format is not stable and will change without notice**. 71 - - **With feature `archive-site`:** The `.git-pages/archive.tar` URL returns a tar archive of all site contents, including `_redirects` and `_headers` files (reconstructed from the manifest), with the `Last-Modified:` header set to the manifest modification time. Compression can be enabled using the `Accept-Encoding:` HTTP header (only). 71 + - The `.git-pages/archive.tar` URL returns a tar archive of all site contents, including `_redirects` and `_headers` files (reconstructed from the manifest), with the `Last-Modified:` header set to the manifest modification time. Compression can be enabled using the `Accept-Encoding:` HTTP header (only). 72 72 * In response to a `PUT` or `POST` request, the server updates a site with new content. The URL of the request must be the root URL of the site that is being published. 73 73 - If the `PUT` method receives an `application/x-www-form-urlencoded` body, it contains a repository URL to be shallowly cloned. The `Branch` header contains the branch to be checked out; the `pages` branch is used if the header is absent. 74 74 - If the `PUT` method receives an `application/x-tar`, `application/x-tar+gzip`, `application/x-tar+zstd`, or `application/zip` body, it contains an archive to be extracted. 75 75 - The `POST` method requires an `application/json` body containing a Forgejo/Gitea/Gogs/GitHub webhook event payload. Requests where the `ref` key contains anything other than `refs/heads/pages` are ignored, and only the `pages` branch is used. The `repository.clone_url` key contains a repository URL to be shallowly cloned. 76 76 - If the received contents is empty, performs the same action as `DELETE`. 77 - * **With feature `patch`:** In response to a `PATCH` request, the server partially updates a site with new content. The URL of the request must be the root URL of the site that is being published. 77 + * In response to a `PATCH` request, the server partially updates a site with new content. The URL of the request must be the root URL of the site that is being published. 78 78 - The request must have a `application/x-tar`, `application/x-tar+gzip`, or `application/x-tar+zstd` body, whose contents is *merged* with the existing site contents as follows: 79 79 - A character device entry with major 0 and minor 0 is treated as a "whiteout marker" (following [unionfs][whiteout]): it causes any existing file or directory with the same name to be deleted. 80 80 - A directory entry replaces any existing file or directory with the same name (if any), recursively removing the old contents. ··· 85 85 - If a `PATCH` request loses a race against another content update request, it may return `409 Conflict`. This is true regardless of the `Atomic:` header value. Whenever this happens, resubmit the request as-is. 86 86 - If the site has no contents after the update is applied, performs the same action as `DELETE`. 87 87 * In response to a `DELETE` request, the server unpublishes a site. The URL of the request must be the root URL of the site that is being unpublished. Site data remains stored for an indeterminate period of time, but becomes completely inaccessible. 88 - * If a `Dry-Run: yes` header is provided with a `PUT`, `PATCH`, `DELETE`, or `POST` request, only the authorization checks are run; no destructive updates are made. Note that this functionality was added in _git-pages_ v0.2.0. 88 + * If a `Dry-Run: yes` header is provided with a `PUT`, `PATCH`, `DELETE`, or `POST` request, only the authorization checks are run; no destructive updates are made. 89 89 * All updates to site content are atomic (subject to consistency guarantees of the storage backend). That is, there is an instantaneous moment during an update before which the server will return the old content and after which it will return the new content. 90 90 * Files with a certain name, when placed in the root of a site, have special functions: 91 91 - [Netlify `_redirects`][_redirects] file can be used to specify HTTP redirect and rewrite rules. The _git-pages_ implementation currently does not support placeholders, query parameters, or conditions, and may differ from Netlify in other minor ways. If you find that a supported `_redirects` file feature does not work the same as on Netlify, please file an issue. (Note that _git-pages_ does not perform URL normalization; `/foo` and `/foo/` are *not* the same, unlike with Netlify.)
+1 -6
src/pages.go
··· 205 205 w.Write(ManifestJSON(manifest)) 206 206 return nil 207 207 208 - case metadataPath == "archive.tar" && config.Feature("archive-site"): 208 + case metadataPath == "archive.tar": 209 209 // same as above 210 210 _, err := AuthorizeMetadataRetrieval(r) 211 211 if err != nil { ··· 507 507 } 508 508 509 509 func patchPage(w http.ResponseWriter, r *http.Request) error { 510 - if !config.Feature("patch") { 511 - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 512 - return nil 513 - } 514 - 515 510 for _, header := range []string{ 516 511 "If-Modified-Since", "If-Unmodified-Since", "If-Match", "If-None-Match", 517 512 } {