···7070 - 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.
7171 - If the received contents is empty, performs the same action as `DELETE`.
7272* 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.
7373+* If a `Dry-Run: yes` header is provided with a `PUT`, `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.
7374* 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.
7475* Files with a certain name, when placed in the root of a site, have special functions:
7576 - [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.)
+30-7
src/pages.go
···405405 return nil
406406}
407407408408+func checkDryRun(w http.ResponseWriter, r *http.Request) bool {
409409+ // "Dry run" requests are used to non-destructively check if the request would have
410410+ // successfully been authorized.
411411+ if r.Header.Get("Dry-Run") != "" {
412412+ fmt.Fprintln(w, "dry-run ok")
413413+ return true
414414+ }
415415+ return false
416416+}
417417+408418func putPage(w http.ResponseWriter, r *http.Request) error {
409419 var result UpdateResult
410420···424434 defer cancel()
425435426436 contentType := getMediaType(r.Header.Get("Content-Type"))
427427-428428- if contentType == "application/x-www-form-urlencoded" {
437437+ switch contentType {
438438+ case "application/x-www-form-urlencoded":
429439 auth, err := AuthorizeUpdateFromRepository(r)
430440 if err != nil {
431441 return err
···450460 return err
451461 }
452462463463+ if checkDryRun(w, r) {
464464+ return nil
465465+ }
466466+453467 result = UpdateFromRepository(updateCtx, webRoot, repoURL, branch)
454454- } else {
468468+469469+ default:
455470 _, err := AuthorizeUpdateFromArchive(r)
456471 if err != nil {
457472 return err
473473+ }
474474+475475+ if checkDryRun(w, r) {
476476+ return nil
458477 }
459478460479 // request body contains archive
···518537 return err
519538 }
520539540540+ if checkDryRun(w, r) {
541541+ return nil
542542+ }
543543+521544 err = backend.DeleteManifest(r.Context(), makeWebRoot(host, projectName))
522545 if err != nil {
523546 w.WriteHeader(http.StatusInternalServerError)
···618641 return err
619642 }
620643644644+ if checkDryRun(w, r) {
645645+ return nil
646646+ }
647647+621648 resultChan := make(chan UpdateResult)
622649 go func(ctx context.Context) {
623650 ctx, cancel := context.WithTimeout(ctx, time.Duration(config.Limits.UpdateTimeout))
···645672 w.WriteHeader(http.StatusGatewayTimeout)
646673 fmt.Fprintln(w, "update timeout")
647674 case UpdateNoChange:
648648- w.WriteHeader(http.StatusOK)
649675 fmt.Fprintln(w, "unchanged")
650676 case UpdateCreated:
651651- w.WriteHeader(http.StatusOK)
652677 fmt.Fprintln(w, "created")
653678 case UpdateReplaced:
654654- w.WriteHeader(http.StatusOK)
655679 fmt.Fprintln(w, "replaced")
656680 case UpdateDeleted:
657657- w.WriteHeader(http.StatusOK)
658681 fmt.Fprintln(w, "deleted")
659682 }
660683 if result.manifest != nil {