mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-14 03:01:48 +00:00
Add Create-Parents: mode to PATCH method.
This acts like `mkdir -p`, making it much less annoying to deploy e.g. documentation preview generators that use deep paths. Like before, the site must already exist: we cannot do a CAS on a non-existent manifest at the moment.
This commit is contained in:
@@ -79,7 +79,8 @@ Features
|
||||
- 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.
|
||||
- A directory entry replaces any existing file or directory with the same name (if any), recursively removing the old contents.
|
||||
- A file or symlink entry replaces any existing file or directory with the same name (if any).
|
||||
- In any case, the parent of an entry must exist and be a directory.
|
||||
- If there is no `Create-Parents:` header or a `Create-Parents: no` header is present, the parent path of an entry must exist and refer to a directory.
|
||||
- If a `Create-Parents: yes` header is present, any missing segments in the parent path of an entry will be created (like `mkdir -p`). Any existing segments refer to directories.
|
||||
- The request must have a `Atomic: yes` or `Atomic: no` header. Not every backend configuration makes it possible to perform atomic compare-and-swap operations; on backends without atomic CAS support, `Atomic: yes` requests will fail, while `Atomic: no` requests will provide a best-effort approximation.
|
||||
- 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.
|
||||
- If the site has no contents after the update is applied, performs the same action as `DELETE`.
|
||||
|
||||
21
src/pages.go
21
src/pages.go
@@ -452,7 +452,7 @@ func putPage(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
updateCtx, cancel := context.WithTimeout(r.Context(), time.Duration(config.Limits.UpdateTimeout))
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(config.Limits.UpdateTimeout))
|
||||
defer cancel()
|
||||
|
||||
contentType := getMediaType(r.Header.Get("Content-Type"))
|
||||
@@ -486,7 +486,7 @@ func putPage(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
result = UpdateFromRepository(updateCtx, webRoot, repoURL, branch)
|
||||
result = UpdateFromRepository(ctx, webRoot, repoURL, branch)
|
||||
|
||||
default:
|
||||
_, err := AuthorizeUpdateFromArchive(r)
|
||||
@@ -500,7 +500,7 @@ func putPage(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
// request body contains archive
|
||||
reader := http.MaxBytesReader(w, r.Body, int64(config.Limits.MaxSiteSize.Bytes()))
|
||||
result = UpdateFromArchive(updateCtx, webRoot, contentType, reader)
|
||||
result = UpdateFromArchive(ctx, webRoot, contentType, reader)
|
||||
}
|
||||
|
||||
return reportUpdateResult(w, result)
|
||||
@@ -554,12 +554,23 @@ func patchPage(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
updateCtx, cancel := context.WithTimeout(r.Context(), time.Duration(config.Limits.UpdateTimeout))
|
||||
var parents CreateParentsMode
|
||||
switch r.Header.Get("Create-Parents") {
|
||||
case "", "no":
|
||||
parents = RequireParents
|
||||
case "yes":
|
||||
parents = CreateParents
|
||||
default:
|
||||
http.Error(w, "malformed Create-Parents: header", http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(config.Limits.UpdateTimeout))
|
||||
defer cancel()
|
||||
|
||||
contentType := getMediaType(r.Header.Get("Content-Type"))
|
||||
reader := http.MaxBytesReader(w, r.Body, int64(config.Limits.MaxSiteSize.Bytes()))
|
||||
result := PartialUpdateFromArchive(updateCtx, webRoot, contentType, reader)
|
||||
result := PartialUpdateFromArchive(ctx, webRoot, contentType, reader, parents)
|
||||
return reportUpdateResult(w, result)
|
||||
}
|
||||
|
||||
|
||||
24
src/patch.go
24
src/patch.go
@@ -12,12 +12,19 @@ import (
|
||||
|
||||
var ErrMalformedPatch = errors.New("malformed patch")
|
||||
|
||||
type CreateParentsMode int
|
||||
|
||||
const (
|
||||
RequireParents CreateParentsMode = iota
|
||||
CreateParents
|
||||
)
|
||||
|
||||
// Mutates `manifest` according to a tar stream and the following rules:
|
||||
// - A character device with major 0 and minor 0 is a "whiteout marker". When placed
|
||||
// at a given path, this path and its entire subtree (if any) are removed from the manifest.
|
||||
// - When a directory is placed at a given path, this path and its entire subtree (if any) are
|
||||
// removed from the manifest and replaced with the contents of the directory.
|
||||
func ApplyTarPatch(manifest *Manifest, reader io.Reader) error {
|
||||
func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMode) error {
|
||||
type Node struct {
|
||||
entry *Entry
|
||||
children map[string]*Node
|
||||
@@ -72,11 +79,18 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader) error {
|
||||
return fmt.Errorf("%w: %s: not a directory", ErrMalformedPatch, dirName)
|
||||
}
|
||||
if _, exists := node.children[segment]; !exists {
|
||||
nodeName := strings.Join(segments[:index+1], "/")
|
||||
return fmt.Errorf("%w: %s: path not found", ErrMalformedPatch, nodeName)
|
||||
} else {
|
||||
node = node.children[segment]
|
||||
switch parents {
|
||||
case RequireParents:
|
||||
nodeName := strings.Join(segments[:index+1], "/")
|
||||
return fmt.Errorf("%w: %s: path not found", ErrMalformedPatch, nodeName)
|
||||
case CreateParents:
|
||||
node.children[segment] = &Node{
|
||||
entry: NewManifestEntry(Type_Directory, nil),
|
||||
children: map[string]*Node{},
|
||||
}
|
||||
}
|
||||
}
|
||||
node = node.children[segment]
|
||||
}
|
||||
if node.children == nil {
|
||||
dirName := strings.Join(segments[:len(segments)-1], "/")
|
||||
|
||||
@@ -159,6 +159,7 @@ func PartialUpdateFromArchive(
|
||||
webRoot string,
|
||||
contentType string,
|
||||
reader io.Reader,
|
||||
parents CreateParentsMode,
|
||||
) (result UpdateResult) {
|
||||
var err error
|
||||
|
||||
@@ -177,7 +178,7 @@ func PartialUpdateFromArchive(
|
||||
// `*Manifest` objects, which should never be mutated.
|
||||
newManifest := &Manifest{}
|
||||
proto.Merge(newManifest, oldManifest)
|
||||
if err := ApplyTarPatch(newManifest, reader); err != nil {
|
||||
if err := ApplyTarPatch(newManifest, reader, parents); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return newManifest, nil
|
||||
|
||||
Reference in New Issue
Block a user