Unpublish site when pushing an empty repository.

This commit is contained in:
Catherine
2025-09-19 05:09:29 +00:00
parent da212dcb89
commit df6ca018a5
4 changed files with 43 additions and 15 deletions

View File

@@ -42,6 +42,7 @@ Features
* In response to a `PUT` or `POST` request, the server performs a shallow clone of the indicated git repository into a temporary location, checks out the `HEAD` commit, and atomically updates a site. The URL of the request must be the root URL of the site that is being published.
- The `PUT` method requires an `application/x-www-form-urlencoded` body. The body contains the repository URL to be cloned.
- 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. The `repository.clone_url` key contains the repository URL to be cloned.
- If the `HEAD` commit is empty, performs the same action as `DELETE`.
* 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.
* 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.

View File

@@ -12,6 +12,18 @@ import (
"google.golang.org/protobuf/proto"
)
func IsManifestEmpty(manifest *Manifest) bool {
if len(manifest.Tree) > 1 {
return false
}
for name, entry := range manifest.Tree {
if name == "" && entry.Type == Type_Directory {
return true
}
}
panic(fmt.Errorf("malformed manifest %v", manifest))
}
// Returns `true` if `left` and `right` contain the same files with the same types and data.
func CompareManifest(left *Manifest, right *Manifest) bool {
if len(left.Tree) != len(right.Tree) {

View File

@@ -192,21 +192,19 @@ func putPage(w http.ResponseWriter, r *http.Request) error {
ctx, cancel := context.WithTimeout(r.Context(), updateTimeout)
defer cancel()
result := Update(ctx, webRoot, repoURL, branch)
if result.manifest != nil {
w.Header().Add("Content-Location", r.URL.String())
}
switch result.outcome {
case UpdateError:
w.WriteHeader(http.StatusServiceUnavailable)
case UpdateTimeout:
w.WriteHeader(http.StatusGatewayTimeout)
// HTTP prescribes these response codes to be used
case UpdateNoChange:
w.WriteHeader(http.StatusNoContent)
w.Header().Add("X-Pages-Outcome", "no-change")
case UpdateCreated:
w.WriteHeader(http.StatusCreated)
w.Header().Add("X-Pages-Outcome", "created")
case UpdateReplaced:
w.WriteHeader(http.StatusOK)
w.Header().Add("X-Pages-Outcome", "replaced")
case UpdateDeleted:
w.Header().Add("X-Pages-Outcome", "deleted")
}
if result.manifest != nil {
fmt.Fprintln(w, result.manifest.Commit)
@@ -331,6 +329,9 @@ func postPage(w http.ResponseWriter, r *http.Request) error {
case UpdateReplaced:
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "replaced")
case UpdateDeleted:
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "deleted")
}
return nil
}

View File

@@ -14,6 +14,7 @@ const (
UpdateTimeout
UpdateCreated
UpdateReplaced
UpdateDeleted
UpdateNoChange
)
@@ -41,14 +42,25 @@ func Update(
err = fmt.Errorf("update timeout")
} else if err == nil {
oldManifest, _ = backend.GetManifest(webRoot)
newManifest, err = StoreManifest(webRoot, fetchManifest)
if err == nil {
if oldManifest == nil {
outcome = UpdateCreated
} else if CompareManifest(oldManifest, newManifest) {
outcome = UpdateNoChange
} else {
outcome = UpdateReplaced
if IsManifestEmpty(fetchManifest) {
newManifest, err = fetchManifest, backend.DeleteManifest(webRoot)
if err == nil {
if oldManifest == nil {
outcome = UpdateNoChange
} else {
outcome = UpdateDeleted
}
}
} else {
newManifest, err = StoreManifest(webRoot, fetchManifest)
if err == nil {
if oldManifest == nil {
outcome = UpdateCreated
} else if CompareManifest(oldManifest, newManifest) {
outcome = UpdateNoChange
} else {
outcome = UpdateReplaced
}
}
}
}
@@ -60,6 +72,8 @@ func Update(
status = "created"
case UpdateReplaced:
status = "replaced"
case UpdateDeleted:
status = "deleted"
case UpdateNoChange:
status = "unchanged"
}