mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-05-14 11:11:35 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b37ca8cd14 | ||
|
|
310cc7d438 | ||
|
|
ad327b0382 | ||
|
|
b737e1bb9b | ||
|
|
8711e35c8e | ||
|
|
d2b5144182 | ||
|
|
34985c89bf | ||
|
|
050a002ddc | ||
|
|
559f0c6ae8 | ||
|
|
52fa8d1462 | ||
|
|
3830af5392 | ||
|
|
9e9664013b | ||
|
|
3e377986bc | ||
|
|
c85c7327bf | ||
|
|
886ee2ddae | ||
|
|
ac751e23b5 | ||
|
|
ebe7d07b3b | ||
|
|
4f14c345a6 | ||
|
|
7e293d6ef9 | ||
|
|
f7067b939b | ||
|
|
6bf4200f26 | ||
|
|
e9a5a901ec | ||
|
|
d3c8db6229 | ||
|
|
8f811147d6 | ||
|
|
0d33c64372 |
@@ -12,12 +12,12 @@ jobs:
|
||||
check:
|
||||
runs-on: debian-trixie
|
||||
container:
|
||||
image: docker.io/library/node:24-trixie-slim@sha256:a16979bcaf12a2fd24888eb8e89874b11bd1038a3e3f1881c26a5e2b8fb92b5c
|
||||
image: docker.io/library/node:24-trixie-slim@sha256:8c8f12cedb96c3b59642cf30d713943c2b223990c9919b96a141681f62e6e292
|
||||
steps:
|
||||
- name: Check out source code
|
||||
uses: https://code.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Set up toolchain
|
||||
uses: https://code.forgejo.org/actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: https://code.forgejo.org/actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: '>=1.25.6'
|
||||
- name: Install dependencies
|
||||
@@ -42,12 +42,12 @@ jobs:
|
||||
needs: [check]
|
||||
runs-on: debian-trixie
|
||||
container:
|
||||
image: docker.io/library/node:24-trixie-slim@sha256:a16979bcaf12a2fd24888eb8e89874b11bd1038a3e3f1881c26a5e2b8fb92b5c
|
||||
image: docker.io/library/node:24-trixie-slim@sha256:8c8f12cedb96c3b59642cf30d713943c2b223990c9919b96a141681f62e6e292
|
||||
steps:
|
||||
- name: Check out source code
|
||||
uses: https://code.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Set up toolchain
|
||||
uses: https://code.forgejo.org/actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: https://code.forgejo.org/actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: '>=1.25.6'
|
||||
- name: Install dependencies
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
build linux arm64
|
||||
build darwin arm64
|
||||
- name: Create release
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@fc0488c944626f9265d87fbc4dd6c08f78014c63 # v2.7.3
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@6a9510a9ea01b8b9b435933bf3c0fa45597ad530 # v2.11.3
|
||||
with:
|
||||
tag: ${{ startsWith(forge.event.ref, 'refs/tags/v') && forge.ref_name || 'latest' }}
|
||||
release-dir: assets
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
needs: [check]
|
||||
runs-on: debian-trixie
|
||||
container:
|
||||
image: docker.io/library/node:24-trixie-slim@sha256:a16979bcaf12a2fd24888eb8e89874b11bd1038a3e3f1881c26a5e2b8fb92b5c
|
||||
image: docker.io/library/node:24-trixie-slim@sha256:8c8f12cedb96c3b59642cf30d713943c2b223990c9919b96a141681f62e6e292
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM docker.io/library/alpine:3 AS ca-certificates-builder
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
# Build supervisor.
|
||||
FROM docker.io/library/golang:1.25-alpine@sha256:e6898559d553d81b245eb8eadafcb3ca38ef320a9e26674df59d4f07a4fd0b07 AS supervisor-builder
|
||||
FROM docker.io/library/golang:1.26-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS supervisor-builder
|
||||
RUN apk --no-cache add git
|
||||
WORKDIR /build
|
||||
RUN git clone https://github.com/ochinchina/supervisord . && \
|
||||
@@ -11,12 +11,12 @@ RUN git clone https://github.com/ochinchina/supervisord . && \
|
||||
RUN GOBIN=/usr/bin go install -ldflags "-s -w"
|
||||
|
||||
# Build Caddy with S3 storage backend.
|
||||
FROM docker.io/library/caddy:2.10.2-builder@sha256:b6424b4a90e25fde5cb9fd8e1da716159a313869ac3ba1c34b11c50781acab81 AS caddy-builder
|
||||
FROM docker.io/library/caddy:2.11.2-builder@sha256:84dfc3479309c690643ada9279b3e0b4352ce56b0ec8fd802c668f42b546e98f AS caddy-builder
|
||||
RUN xcaddy build ${CADDY_VERSION} \
|
||||
--with=github.com/ss098/certmagic-s3@v0.0.0-20250922022452-8af482af5f39
|
||||
|
||||
# Build git-pages.
|
||||
FROM docker.io/library/golang:1.25-alpine@sha256:e6898559d553d81b245eb8eadafcb3ca38ef320a9e26674df59d4f07a4fd0b07 AS git-pages-builder
|
||||
FROM docker.io/library/golang:1.26-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS git-pages-builder
|
||||
RUN apk --no-cache add git
|
||||
WORKDIR /build
|
||||
COPY go.mod go.sum ./
|
||||
@@ -26,7 +26,7 @@ COPY src/ ./src/
|
||||
RUN go build -ldflags "-s -w" -o git-pages .
|
||||
|
||||
# Compose git-pages and Caddy.
|
||||
FROM docker.io/library/busybox:1.37.0-musl@sha256:03db190ed4c1ceb1c55d179a0940e2d71d42130636a780272629735893292223
|
||||
FROM docker.io/library/busybox:1.37.0-musl@sha256:19b646668802469d968a05342a601e78da4322a414a7c09b1c9ee25165042138
|
||||
COPY --from=ca-certificates-builder /etc/ssl/cert.pem /etc/ssl/cert.pem
|
||||
COPY --from=supervisor-builder /usr/bin/supervisord /bin/supervisord
|
||||
COPY --from=caddy-builder /usr/bin/caddy /bin/caddy
|
||||
|
||||
@@ -91,9 +91,10 @@ Features
|
||||
- [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.)
|
||||
- [Netlify `_headers`][_headers] file can be used to specify custom HTTP response headers (if allowlisted by configuration). In particular, this is useful to enable [CORS requests][cors]. The _git-pages_ implementation may differ from Netlify in minor ways; if you find that a `_headers` file feature does not work the same as on Netlify, please file an issue.
|
||||
* Incremental updates can be made using `PUT` or `PATCH` requests where the body contains an archive (both tar and zip are supported).
|
||||
- Any archive entry that is a symlink to `/git/pages/<git-sha256>` is replaced with an existing manifest entry for the same site whose git blob hash matches `<git-sha256>`. If there is no existing manifest entry with the specified git hash, the update fails with a `422 Unprocessable Entity`.
|
||||
- Any archive entry that is a symlink to `/git/blobs/<git-sha256>` is replaced with an existing manifest entry for the same site whose git blob hash matches `<git-sha256>`. If there is no existing manifest entry with the specified git hash, the update fails with a `422 Unprocessable Entity`.
|
||||
- For this error response only, if the negotiated content type is `application/vnd.git-pages.unresolved`, the response will contain the `<git-sha256>` of each unresolved reference, one per line.
|
||||
* Support for SHA-256 Git hashes is [limited by go-git][go-git-sha256]; once go-git implements the required features, _git-pages_ will automatically gain support for SHA-256 Git hashes. Note that shallow clones (used by _git-pages_ to conserve bandwidth if available) aren't supported yet in the Git protocol as of 2025.
|
||||
* Git LFS is not supported: it is a single-vendor specification/implementation with no stable Go API and a risk of misuse for reflected HTTP DoS attacks. A diagnostic is emitted for any files uploaded have the `filter=lfs` attribute set via `.gitattributes`.
|
||||
|
||||
[_redirects]: https://docs.netlify.com/manage/routing/redirects/overview/
|
||||
[_headers]: https://docs.netlify.com/manage/routing/headers/
|
||||
@@ -117,7 +118,7 @@ The authorization flow for content updates (`PUT`, `PATCH`, `DELETE`, `POST` req
|
||||
4. **Wildcard Match (content):** If the method is `POST`, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and (for `PUT` requests) the body contains a repository URL, and the requested clone URL is a *matching* clone URL, the request is authorized.
|
||||
- **Index repository:** If the request URL is `scheme://<user>.<host>/`, a *matching* clone URL is computed by templating `[[wildcard]].clone-url` with `<user>` and `<project>`, where `<project>` is computed by templating each element of `[[wildcard]].index-repos` with `<user>`, and `[[wildcard]]` is the section where the match occurred.
|
||||
- **Project repository:** If the request URL is `scheme://<user>.<host>/<project>/`, a *matching* clone URL is computed by templating `[[wildcard]].clone-url` with `<user>` and `<project>`, and `[[wildcard]]` is the section where the match occurred.
|
||||
5. **Forge Authorization:** If the method is `PUT` or `PATCH`, and the body contains an archive, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and `[[wildcard]].authorization` is non-empty, and the request includes a `Forge-Authorization:` header, and the header (when forwarded as `Authorization:`) grants push permissions to a repository at the *matching* clone URL (as defined above) as determined by an API call to the forge, the request is authorized. (This enables publishing a site for a private repository.)
|
||||
5. **Forge Authorization:** If the method is `PUT` or `PATCH` or `DELETE`, and (unless the method is `DELETE`) the body contains an archive, and a `[[wildcard]]` configuration section exists where the suffix of a hostname (compared label-wise) is equal to `[[wildcard]].domain`, and `[[wildcard]].authorization` is non-empty, and the request includes a `Forge-Authorization:` header, and the header (when forwarded as `Authorization:`) grants push permissions to a repository at the *matching* clone URL (as defined above) as determined by an API call to the forge, the request is authorized. (This enables publishing a site for a private repository.)
|
||||
5. **Default Deny:** Otherwise, the request is not authorized.
|
||||
|
||||
The authorization flow for metadata retrieval (`GET` requests with site paths starting with `.git-pages/`) in the following order, with the first of multiple applicable rule taking precedence:
|
||||
|
||||
@@ -12,7 +12,7 @@ metrics = "tcp/localhost:3002"
|
||||
[[wildcard]] # non-default section
|
||||
domain = "codeberg.page"
|
||||
clone-url = "https://codeberg.org/<user>/<project>.git"
|
||||
index-repos = ["<user>.codeberg.page", "pages"]
|
||||
index-repo = "pages"
|
||||
index-repo-branch = "main"
|
||||
authorization = "forgejo"
|
||||
|
||||
|
||||
33
go.mod
33
go.mod
@@ -9,23 +9,23 @@ require (
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
|
||||
github.com/creasty/defaults v1.8.0
|
||||
github.com/dghubble/trie v0.1.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/getsentry/sentry-go v0.41.0
|
||||
github.com/getsentry/sentry-go/slog v0.41.0
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/getsentry/sentry-go v0.43.0
|
||||
github.com/getsentry/sentry-go/slog v0.43.0
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260226131633-45bd0956d66f
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260320111621-ea91339c5263
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/kankanreno/go-snowflake v1.2.0
|
||||
github.com/klauspost/compress v1.18.3
|
||||
github.com/klauspost/compress v1.18.5
|
||||
github.com/maypok86/otter/v2 v2.3.0
|
||||
github.com/minio/minio-go/v7 v7.0.98
|
||||
github.com/minio/minio-go/v7 v7.0.99
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/pquerna/cachecontrol v0.2.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/samber/slog-multi v1.7.0
|
||||
github.com/samber/slog-multi v1.7.1
|
||||
github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/net v0.52.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ require (
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
@@ -43,11 +43,11 @@ require (
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.5.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/leodido/go-syslog/v4 v4.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
@@ -62,7 +62,7 @@ require (
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/samber/slog-common v0.19.0 // indirect
|
||||
github.com/samber/slog-common v0.20.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
@@ -70,8 +70,9 @@ require (
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
80
go.sum
80
go.sum
@@ -18,8 +18,8 @@ github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXye
|
||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
@@ -33,24 +33,24 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/getsentry/sentry-go v0.41.0 h1:q/dQZOlEIb4lhxQSjJhQqtRr3vwrJ6Ahe1C9zv+ryRo=
|
||||
github.com/getsentry/sentry-go v0.41.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
|
||||
github.com/getsentry/sentry-go/slog v0.41.0 h1:tjCFcH9KvG7XFufje4gCZTKVVCTxkuAdX7muwKImvD0=
|
||||
github.com/getsentry/sentry-go/slog v0.41.0/go.mod h1:YvnAFFkin7eJ8zNVsfeUC97ZTRw//P6JfeK285Aft+Y=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/getsentry/sentry-go v0.43.0 h1:XbXLpFicpo8HmBDaInk7dum18G9KSLcjZiyUKS+hLW4=
|
||||
github.com/getsentry/sentry-go v0.43.0/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
|
||||
github.com/getsentry/sentry-go/slog v0.43.0 h1:BYGiM4VFu4//S0vrTSf52MmZSmjhOikHIkBeZZw9P4Q=
|
||||
github.com/getsentry/sentry-go/slog v0.43.0/go.mod h1:EAq/2dhW43dV7fwy4OjTWSsvhZjTM9jjsck0kYt9MYE=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc h1:rhkjrnRkamkRC7woapp425E4CAH6RPcqsS9X8LA93IY=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc/go.mod h1:X1oe0Z2qMsa9hkar3AAPuL9hu4Mi3ztXEjdqRhr6fcc=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146/go.mod h1:QE/75B8tBSLNGyUUbA9tw3EGHoFtYOtypa2h8YJxsWI=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6 h1:Yo1MlE8LpvD0pr7mZ04b6hKZKQcPvLrQFgyY1jNMEyU=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6/go.mod h1:enMzPHv+9hL4B7tH7OJGQKNzCkMzXovUoaiXfsLF7Xs=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260226131633-45bd0956d66f h1:Uvbx7nITO3Sd1GdXarX0TbyYmOaSNIJP0mm4LocEyyA=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20260226131633-45bd0956d66f/go.mod h1:ZW9JC5gionMP1kv5uiaOaV23q0FFmNrVOV8VW+y/acc=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67 h1:3hutPZF+/FBjR/9MdsLJ7e1mlt9pwHgwxMW7CrbmWII=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67/go.mod h1:xKt0pNHST9tYHvbiLxSY27CQWFwgIxBJuDrOE0JvbZw=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260320111621-ea91339c5263 h1:IB2wgVZc8Q7h2l4LwdywCUEVPyO0u1WceOWoskqH1o4=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20260320111621-ea91339c5263/go.mod h1:DI8P0o+7Go2ainlUPsVcn9PMtEkg3umUXnxw6Kxppag=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
@@ -63,10 +63,10 @@ github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2E
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/kankanreno/go-snowflake v1.2.0 h1:Zx2SctsH5pivIj9vyhwyDyQS23jcDJx4iT49Bjv81kk=
|
||||
github.com/kankanreno/go-snowflake v1.2.0/go.mod h1:6CZ+10PeVsFXKZUTYyJzPiRIjn1IXbInaWLCX/LDJ0g=
|
||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/kevinburke/ssh_config v1.5.0 h1:3cPZmE54xb5j3G5xQCjSvokqNwU2uW+3ry1+PRLSPpA=
|
||||
github.com/kevinburke/ssh_config v1.5.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
@@ -83,9 +83,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-syslog/v4 v4.3.0 h1:bbSpI/41bYK9iSdlYzcwvlxuLOE8yi4VTFmedtnghdA=
|
||||
github.com/leodido/go-syslog/v4 v4.3.0/go.mod h1:eJ8rUfDN5OS6dOkCOBYlg2a+hbAg6pJa99QXXgMrd98=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/maypok86/otter/v2 v2.3.0 h1:8H8AVVFUSzJwIegKwv1uF5aGitTY+AIrtktg7OcLs8w=
|
||||
@@ -94,8 +93,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
|
||||
github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
|
||||
github.com/minio/minio-go/v7 v7.0.99 h1:2vH/byrwUkIpFQFOilvTfaUpvAX3fEFhEzO+DR3DlCE=
|
||||
github.com/minio/minio-go/v7 v7.0.99/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
@@ -122,16 +121,16 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
|
||||
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
|
||||
github.com/samber/slog-multi v1.7.0 h1:GKhbkxU3ujkyMsefkuz4qvE6EcgtSuqjFisPnfdzVLI=
|
||||
github.com/samber/slog-multi v1.7.0/go.mod h1:qTqzmKdPpT0h4PFsTN5rYRgLwom1v+fNGuIrl1Xnnts=
|
||||
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||
github.com/samber/slog-multi v1.7.1 h1:aCLXHRxgU+2v0PVlEOh7phynzM7CRo89ZgFtOwaqVEE=
|
||||
github.com/samber/slog-multi v1.7.1/go.mod h1:A4KQC99deqfkCDJcL/cO3kX6McX7FffQAx/8QHink+c=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -155,18 +154,19 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -26,8 +26,8 @@ schema = 3
|
||||
version = "v2.3.0"
|
||||
hash = "sha256-7hRlwSR+fos1kx4VZmJ/7snR7zHh8ZFKX+qqqqGcQpY="
|
||||
[mod."github.com/cloudflare/circl"]
|
||||
version = "v1.6.1"
|
||||
hash = "sha256-Dc69V12eIFnJoUNmwg6VKXHfAMijbAeEVSDe8AiOaLo="
|
||||
version = "v1.6.3"
|
||||
hash = "sha256-XZm4EastgX67Dgm5BpOEW/PY4aLcHM/O8+Xbz26PuTY="
|
||||
[mod."github.com/creasty/defaults"]
|
||||
version = "v1.8.0"
|
||||
hash = "sha256-I1LE1cfOhMS5JxB7+fWTKieefw2Gge1UhIZh+A6pa6s="
|
||||
@@ -47,23 +47,23 @@ schema = 3
|
||||
version = "v1.18.1"
|
||||
hash = "sha256-hGDKddjLj+5dn2woHtXKUdd49/3xdsqnhx7VEdCu1m4="
|
||||
[mod."github.com/fatih/color"]
|
||||
version = "v1.18.0"
|
||||
hash = "sha256-pP5y72FSbi4j/BjyVq/XbAOFjzNjMxZt2R/lFFxGWvY="
|
||||
version = "v1.19.0"
|
||||
hash = "sha256-YgMm1nid8yigNLG6aHfuMbsvMI1UYVf/Rkg44pp/NTU="
|
||||
[mod."github.com/getsentry/sentry-go"]
|
||||
version = "v0.40.0"
|
||||
hash = "sha256-mJ+EzM8WRzJ2Yp7ithDJNceU4+GbzQyi46yc8J8d13Y="
|
||||
version = "v0.43.0"
|
||||
hash = "sha256-Wu1inIhjuAw6wKburwqIlNxC0I4akunHGh/8DOqo3xg="
|
||||
[mod."github.com/getsentry/sentry-go/slog"]
|
||||
version = "v0.40.0"
|
||||
hash = "sha256-uc9TpKiWMEpRbxwV2uGQeq1DDdZi+APOgu2StVzzEkw="
|
||||
version = "v0.43.0"
|
||||
hash = "sha256-FJMx2E8anKtHknn867gCkYPjitZb9Okqp2uZ+dV7JqA="
|
||||
[mod."github.com/go-git/gcfg/v2"]
|
||||
version = "v2.0.2"
|
||||
hash = "sha256-icqMDeC/tEg/3979EuEN67Ml5KjdDA0R3QvR6iLLrSI="
|
||||
[mod."github.com/go-git/go-billy/v6"]
|
||||
version = "v6.0.0-20251217170237-e9738f50a3cd"
|
||||
hash = "sha256-b2yunYcPUiLTU+Rr8qTBdsDEfsIhZDYmyqKW5udmpFY="
|
||||
version = "v6.0.0-20260226131633-45bd0956d66f"
|
||||
hash = "sha256-s+dthtn+JewJ58R5VbvWaEoYLozDt5YpkHyXcN0xMvQ="
|
||||
[mod."github.com/go-git/go-git/v6"]
|
||||
version = "v6.0.0-20251224103503-78aff6aa5ea9"
|
||||
hash = "sha256-kYjDqH0NZ+sxQnj5K8xKfO2WOVKtQ/7tWcqY6KYqAZE="
|
||||
version = "v6.0.0-20260320111621-ea91339c5263"
|
||||
hash = "sha256-tFpnBdbzYqeNiVelhy80Ob27xONds/5reHexCeA1IsQ="
|
||||
[mod."github.com/go-ini/ini"]
|
||||
version = "v1.67.0"
|
||||
hash = "sha256-V10ahGNGT+NLRdKUyRg1dos5RxLBXBk1xutcnquc/+4="
|
||||
@@ -80,11 +80,11 @@ schema = 3
|
||||
version = "v1.2.0"
|
||||
hash = "sha256-713xGEqjwaUGIu2EHII5sldWmcquFpxZmte/7R/O6LA="
|
||||
[mod."github.com/kevinburke/ssh_config"]
|
||||
version = "v1.4.0"
|
||||
hash = "sha256-UclxB7Ll1FZCgU2SrGkiGdr4CoSRJ127MNnZtxKTsvg="
|
||||
version = "v1.5.0"
|
||||
hash = "sha256-4SijlenzNuWb5CavWrky8qoQj+6fKCJgOiQANzN5TUE="
|
||||
[mod."github.com/klauspost/compress"]
|
||||
version = "v1.18.2"
|
||||
hash = "sha256-mRa+6qEi5joqQao13ZFogmq67rOQzHCVbCCjKA+HKEc="
|
||||
version = "v1.18.5"
|
||||
hash = "sha256-H9b5iFJf4XbEnkGQCjGQAJ3aYhVDiolKrDewTbhuzQo="
|
||||
[mod."github.com/klauspost/cpuid/v2"]
|
||||
version = "v2.3.0"
|
||||
hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc="
|
||||
@@ -95,8 +95,8 @@ schema = 3
|
||||
version = "v4.3.0"
|
||||
hash = "sha256-fCJ2rgrrPR/Ey/PoAsJhd8Sl8mblAnnMAmBuoWFBTgg="
|
||||
[mod."github.com/mattn/go-colorable"]
|
||||
version = "v0.1.13"
|
||||
hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8="
|
||||
version = "v0.1.14"
|
||||
hash = "sha256-JC60PjKj7MvhZmUHTZ9p372FV72I9Mxvli3fivTbxuA="
|
||||
[mod."github.com/mattn/go-isatty"]
|
||||
version = "v0.0.20"
|
||||
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
|
||||
@@ -104,14 +104,14 @@ schema = 3
|
||||
version = "v2.3.0"
|
||||
hash = "sha256-ELzmi/s2WqDeUmzSGnfx+ys2Hs28XHqF7vlEzyRotIA="
|
||||
[mod."github.com/minio/crc64nvme"]
|
||||
version = "v1.1.0"
|
||||
hash = "sha256-OwlE70X91WO4HdbpGsOaB4w12Qrk0duCpfLeAskiqY8="
|
||||
version = "v1.1.1"
|
||||
hash = "sha256-RVVi/gWPBEQqcW4n+KIKxlA3uY5+77e2rhkVk8fFNUo="
|
||||
[mod."github.com/minio/md5-simd"]
|
||||
version = "v1.1.2"
|
||||
hash = "sha256-vykcXvy2VBBAXnJott/XsGTT0gk2UL36JzZKfJ1KAUY="
|
||||
[mod."github.com/minio/minio-go/v7"]
|
||||
version = "v7.0.97"
|
||||
hash = "sha256-IwF14tWVYjBi28jUG9iFYd4Lpbc7Fvyy0zRzEZ82UEE="
|
||||
version = "v7.0.99"
|
||||
hash = "sha256-Q2VISIvHDggBzidGWzgHbVUZrDCsSIGBPcWfMJcC39w="
|
||||
[mod."github.com/munnerz/goautoneg"]
|
||||
version = "v0.0.0-20191010083416-a7dc8b61c822"
|
||||
hash = "sha256-79URDDFenmGc9JZu+5AXHToMrtTREHb3BC84b/gym9Q="
|
||||
@@ -155,11 +155,11 @@ schema = 3
|
||||
version = "v1.52.0"
|
||||
hash = "sha256-xgMsPJv3rydHH10NZU8wz/DhK2VbbR8ymivOg1ChTp0="
|
||||
[mod."github.com/samber/slog-common"]
|
||||
version = "v0.19.0"
|
||||
hash = "sha256-OYXVbZML7c3mFClVy8GEnNoWW+4OfcBsxWDtKh1u7B8="
|
||||
version = "v0.20.0"
|
||||
hash = "sha256-aWcvt9XNyKaolLhvthcXeFDl0t6uo7Vdo8WzCducf1E="
|
||||
[mod."github.com/samber/slog-multi"]
|
||||
version = "v1.6.0"
|
||||
hash = "sha256-uebbTcvsBP2LdOUIjDptES+HZOXxThnIt3+FKL0qJy4="
|
||||
version = "v1.7.1"
|
||||
hash = "sha256-wHXt2lwFfjm1p7jnZi44SlHtjdk531BGz2O9pfiylxo="
|
||||
[mod."github.com/sergi/go-diff"]
|
||||
version = "v1.4.0"
|
||||
hash = "sha256-rs9NKpv/qcQEMRg7CmxGdP4HGuFdBxlpWf9LbA9wS4k="
|
||||
@@ -167,8 +167,8 @@ schema = 3
|
||||
version = "v1.11.1"
|
||||
hash = "sha256-sWfjkuKJyDllDEtnM8sb/pdLzPQmUYWYtmeWz/5suUc="
|
||||
[mod."github.com/tinylib/msgp"]
|
||||
version = "v1.3.0"
|
||||
hash = "sha256-PnpndO7k5Yl036vhWJGDsrcz0jsTX8sUiTqm/D3rAVw="
|
||||
version = "v1.6.1"
|
||||
hash = "sha256-R2LutHQFZ7HAqeyzHqzMeyAJHxcYc+n1x7ysyrXefmQ="
|
||||
[mod."github.com/tj/assert"]
|
||||
version = "v0.0.3"
|
||||
hash = "sha256-4xhmZcHpUafabaXejE9ucVnGxG/txomvKzBg6cbkusg="
|
||||
@@ -184,18 +184,24 @@ schema = 3
|
||||
[mod."go.yaml.in/yaml/v2"]
|
||||
version = "v2.4.2"
|
||||
hash = "sha256-oC8RWdf1zbMYCtmR0ATy/kCkhIwPR9UqFZSMOKLVF/A="
|
||||
[mod."go.yaml.in/yaml/v3"]
|
||||
version = "v3.0.4"
|
||||
hash = "sha256-NkGFiDPoCxbr3LFsI6OCygjjkY0rdmg5ggvVVwpyDQ4="
|
||||
[mod."golang.org/x/crypto"]
|
||||
version = "v0.46.0"
|
||||
hash = "sha256-I8N/spcw3/h0DFA+V1WK38HctckWIB9ep93DEVCALxU="
|
||||
version = "v0.49.0"
|
||||
hash = "sha256-OW321Q3osc/qUOM6BsZGEjOqW1DrgakbIiQACCY/qUI="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.48.0"
|
||||
hash = "sha256-oZpddsiJwWCH3Aipa+XXpy7G/xHY5fEagUSok7T0bXE="
|
||||
version = "v0.52.0"
|
||||
hash = "sha256-TQYGkhRhldpi/eGRoDGi4xObh3xh1AuuX2c7uwL/V5Q="
|
||||
[mod."golang.org/x/sync"]
|
||||
version = "v0.20.0"
|
||||
hash = "sha256-ybcjhCfK6lroUM0yswUvWooW8MOQZBXyiSqoxG6Uy0Y="
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.39.0"
|
||||
hash = "sha256-dxTBu/JAWUkPbjFIXXRFdhQWyn+YyEpIC+tWqGo0Y6U="
|
||||
version = "v0.42.0"
|
||||
hash = "sha256-LhNedvUEJbPYyR6EoU91rfOr3DBBoauLOcMcyqghEos="
|
||||
[mod."golang.org/x/text"]
|
||||
version = "v0.32.0"
|
||||
hash = "sha256-9PXtWBKKY9rG4AgjSP4N+I1DhepXhy8SF/vWSIDIoWs="
|
||||
version = "v0.35.0"
|
||||
hash = "sha256-MXhEl0aLAyS+4SjQZMipoYKgNEOWdIYnr8h7WtLdTdQ="
|
||||
[mod."google.golang.org/protobuf"]
|
||||
version = "v1.36.11"
|
||||
hash = "sha256-7W+6jntfI/awWL3JP6yQedxqP5S9o3XvPgJ2XxxsIeE="
|
||||
|
||||
@@ -102,12 +102,18 @@ func (record *AuditRecord) DescribePrincipal() string {
|
||||
if record.Principal.GetIpAddress() != "" {
|
||||
items = append(items, record.Principal.GetIpAddress())
|
||||
}
|
||||
if record.Principal.GetForgeUser() != nil {
|
||||
items = append(items, fmt.Sprintf("%s/%s(%d)",
|
||||
record.Principal.GetForgeUser().GetOrigin(),
|
||||
record.Principal.GetForgeUser().GetHandle(),
|
||||
record.Principal.GetForgeUser().GetId()))
|
||||
}
|
||||
if record.Principal.GetCliAdmin() {
|
||||
items = append(items, "<cli-admin>")
|
||||
}
|
||||
}
|
||||
if len(items) > 0 {
|
||||
return strings.Join(items, ";")
|
||||
return strings.Join(items, ",")
|
||||
} else {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
142
src/auth.go
142
src/auth.go
@@ -106,6 +106,8 @@ type Authorization struct {
|
||||
repoURLs []string
|
||||
// Only the exact branch is allowed.
|
||||
branch string
|
||||
// The authorized forge user.
|
||||
forgeUser *ForgeUser
|
||||
}
|
||||
|
||||
func authorizeDNSChallenge(r *http.Request) (*Authorization, error) {
|
||||
@@ -265,8 +267,8 @@ func authorizeWildcardMatchSite(r *http.Request, pattern *WildcardPattern) (*Aut
|
||||
}
|
||||
|
||||
if userName, found := pattern.Matches(host); found {
|
||||
repoURLs, branch := pattern.ApplyTemplate(userName, projectName)
|
||||
return &Authorization{repoURLs, branch}, nil
|
||||
repoURL, branch := pattern.ApplyTemplate(userName, projectName)
|
||||
return &Authorization{repoURLs: []string{repoURL}, branch: branch}, nil
|
||||
} else {
|
||||
return nil, AuthError{
|
||||
http.StatusUnauthorized,
|
||||
@@ -390,9 +392,6 @@ func AuthorizeMetadataRetrieval(r *http.Request) (*Authorization, error) {
|
||||
return nil, joinErrors(causes...)
|
||||
}
|
||||
|
||||
// Returns `repoURLs, err` where if `err == nil` then the request is authorized to clone from
|
||||
// any repository URL included in `repoURLs` (by case-insensitive comparison), or any URL at all
|
||||
// if `repoURLs == nil`.
|
||||
func AuthorizeUpdateFromRepository(r *http.Request) (*Authorization, error) {
|
||||
causes := []error{AuthError{http.StatusUnauthorized, "unauthorized"}}
|
||||
|
||||
@@ -609,6 +608,61 @@ func checkGogsRepositoryPushPermission(baseURL *url.URL, authorization string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gogs, Gitea, and Forgejo all support the same API here.
|
||||
func fetchGogsAuthorizedUser(baseURL *url.URL, authorization string) (*ForgeUser, error) {
|
||||
request, err := http.NewRequest("GET", baseURL.ResolveReference(&url.URL{
|
||||
Path: "/api/v1/user",
|
||||
}).String(), nil)
|
||||
if err != nil {
|
||||
panic(err) // misconfiguration
|
||||
}
|
||||
request.Header.Set("Accept", "application/json")
|
||||
request.Header.Set("Authorization", authorization)
|
||||
|
||||
httpClient := http.Client{Timeout: 5 * time.Second}
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, AuthError{
|
||||
http.StatusServiceUnavailable,
|
||||
fmt.Sprintf("cannot fetch authorized forge user: %s", err),
|
||||
}
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, AuthError{
|
||||
http.StatusServiceUnavailable,
|
||||
fmt.Sprintf(
|
||||
"cannot fetch authorized forge user: GET %s returned %s",
|
||||
request.URL,
|
||||
response.Status,
|
||||
),
|
||||
}
|
||||
}
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
var userInfo struct {
|
||||
ID int64
|
||||
Login string
|
||||
}
|
||||
if err = decoder.Decode(&userInfo); err != nil {
|
||||
return nil, errors.Join(AuthError{
|
||||
http.StatusServiceUnavailable,
|
||||
fmt.Sprintf(
|
||||
"cannot fetch authorized forge user: GET %s returned malformed JSON",
|
||||
request.URL,
|
||||
),
|
||||
}, err)
|
||||
}
|
||||
|
||||
origin := request.URL.Hostname()
|
||||
return &ForgeUser{
|
||||
Origin: &origin,
|
||||
Id: &userInfo.ID,
|
||||
Handle: &userInfo.Login,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authorizeForgeWithToken(r *http.Request) (*Authorization, error) {
|
||||
authorization := r.Header.Get("Forge-Authorization")
|
||||
if authorization == "" {
|
||||
@@ -632,25 +686,32 @@ func authorizeForgeWithToken(r *http.Request) (*Authorization, error) {
|
||||
}
|
||||
|
||||
if userName, found := pattern.Matches(host); found {
|
||||
repoURLs, branch := pattern.ApplyTemplate(userName, projectName)
|
||||
for _, repoURL := range repoURLs {
|
||||
parsedRepoURL, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
panic(err) // misconfiguration
|
||||
}
|
||||
|
||||
if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// This will actually be ignored by the caller of AuthorizeUpdateFromArchive,
|
||||
// but we return this information as it makes sense to do contextually here.
|
||||
return &Authorization{
|
||||
[]string{repoURL},
|
||||
branch,
|
||||
}, nil
|
||||
repoURL, branch := pattern.ApplyTemplate(userName, projectName)
|
||||
parsedRepoURL, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
panic(err) // misconfiguration
|
||||
}
|
||||
|
||||
if err = checkGogsRepositoryPushPermission(parsedRepoURL, authorization); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
authorizedUser, err := fetchGogsAuthorizedUser(parsedRepoURL, authorization)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
return &Authorization{
|
||||
// This will actually be ignored by the callers of AuthorizeUpdateFromArchive and
|
||||
// AuthorizeDeletion, but we return this information as it makes sense to do
|
||||
// contextually here.
|
||||
repoURLs: []string{repoURL},
|
||||
branch: branch,
|
||||
|
||||
forgeUser: authorizedUser,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,6 +762,41 @@ func AuthorizeUpdateFromArchive(r *http.Request) (*Authorization, error) {
|
||||
return nil, joinErrors(causes...)
|
||||
}
|
||||
|
||||
func AuthorizeDeletion(r *http.Request) (*Authorization, error) {
|
||||
causes := []error{AuthError{http.StatusUnauthorized, "unauthorized"}}
|
||||
|
||||
if err := CheckForbiddenDomain(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth := authorizeInsecure(r)
|
||||
if auth != nil {
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
auth, err := authorizeDNSChallenge(r)
|
||||
if err != nil && IsUnauthorized(err) {
|
||||
causes = append(causes, err)
|
||||
} else if err != nil { // bad request
|
||||
return nil, err
|
||||
} else {
|
||||
logc.Printf(r.Context(), "auth: DNS challenge: allow *\n")
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
auth, err = authorizeForgeWithToken(r)
|
||||
if err != nil && IsUnauthorized(err) {
|
||||
causes = append(causes, err)
|
||||
} else if err != nil { // bad request
|
||||
return nil, err
|
||||
} else {
|
||||
logc.Printf(r.Context(), "auth: forge token: allow\n")
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
return nil, joinErrors(causes...)
|
||||
}
|
||||
|
||||
func CheckForbiddenDomain(r *http.Request) error {
|
||||
host, err := GetHost(r)
|
||||
if err != nil {
|
||||
|
||||
@@ -84,7 +84,11 @@ func CollectTar(
|
||||
header.Typeflag = tar.TypeSymlink
|
||||
header.Mode = 0644
|
||||
header.ModTime = metadata.LastModified
|
||||
err = appendFile(&header, entry.GetData(), Transform_Identity)
|
||||
header.Linkname = string(entry.GetData())
|
||||
err = archive.WriteHeader(&header)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tar: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("CollectTar encountered invalid entry: %v, %v",
|
||||
|
||||
@@ -79,11 +79,11 @@ type ServerConfig struct {
|
||||
}
|
||||
|
||||
type WildcardConfig struct {
|
||||
Domain string `toml:"domain"`
|
||||
CloneURL string `toml:"clone-url"` // URL template, not an exact URL
|
||||
IndexRepos []string `toml:"index-repos" default:"[]"`
|
||||
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
|
||||
Authorization string `toml:"authorization"`
|
||||
Domain string `toml:"domain"`
|
||||
CloneURL string `toml:"clone-url"` // URL template, not an exact URL
|
||||
IndexRepo string `toml:"index-repo" default:"pages"`
|
||||
IndexRepoBranch string `toml:"index-repo-branch" default:"pages"`
|
||||
Authorization string `toml:"authorization"`
|
||||
}
|
||||
|
||||
type FallbackConfig struct {
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/go-git/go-git/v6/plumbing"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
@@ -51,29 +51,14 @@ func ExtractZstd(
|
||||
return next(ctx, boundArchiveStream(stream))
|
||||
}
|
||||
|
||||
const BlobReferencePrefix = "/git/blobs/"
|
||||
|
||||
type UnresolvedRefError struct {
|
||||
missing []string
|
||||
}
|
||||
|
||||
func (err UnresolvedRefError) Error() string {
|
||||
return fmt.Sprintf("%d unresolved blob references", len(err.missing))
|
||||
}
|
||||
|
||||
// Returns a map of git hash to entry. If `manifest` is nil, returns an empty map.
|
||||
func indexManifestByGitHash(manifest *Manifest) map[string]*Entry {
|
||||
index := map[string]*Entry{}
|
||||
for _, entry := range manifest.GetContents() {
|
||||
if hash := entry.GetGitHash(); hash != "" {
|
||||
if _, ok := plumbing.FromHex(hash); ok {
|
||||
index[hash] = entry
|
||||
} else {
|
||||
panic(fmt.Errorf("index: malformed hash: %s", hash))
|
||||
}
|
||||
}
|
||||
func normalizeArchiveMemberName(fileName string) string {
|
||||
// Strip the leading slash and any extraneous path segments.
|
||||
fileName = path.Clean(fileName)
|
||||
fileName = strings.TrimPrefix(fileName, "/")
|
||||
if fileName == "." {
|
||||
fileName = ""
|
||||
}
|
||||
return index
|
||||
return fileName
|
||||
}
|
||||
|
||||
func addSymlinkOrBlobReference(
|
||||
@@ -99,7 +84,7 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
|
||||
var dataBytesRecycled int64
|
||||
var dataBytesTransferred int64
|
||||
|
||||
index := indexManifestByGitHash(oldManifest)
|
||||
index := IndexManifestByGitHash(oldManifest)
|
||||
missing := []string{}
|
||||
manifest := NewManifest()
|
||||
for {
|
||||
@@ -110,15 +95,10 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For some reason, GNU tar includes any leading `.` path segments in archive filenames,
|
||||
// unless there is a `..` path segment anywhere in the input filenames.
|
||||
fileName := header.Name
|
||||
for {
|
||||
if strippedName, found := strings.CutPrefix(fileName, "./"); found {
|
||||
fileName = strippedName
|
||||
} else {
|
||||
break
|
||||
}
|
||||
fileName := normalizeArchiveMemberName(header.Name)
|
||||
if fileName == "" {
|
||||
// This must be the root directory. It will be filled in by EnsureLeadingDirectories.
|
||||
continue
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
@@ -196,12 +176,13 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
|
||||
var dataBytesRecycled int64
|
||||
var dataBytesTransferred int64
|
||||
|
||||
index := indexManifestByGitHash(oldManifest)
|
||||
index := IndexManifestByGitHash(oldManifest)
|
||||
missing := []string{}
|
||||
manifest := NewManifest()
|
||||
for _, file := range archive.File {
|
||||
normalizedName := normalizeArchiveMemberName(file.Name)
|
||||
if strings.HasSuffix(file.Name, "/") {
|
||||
AddDirectory(manifest, file.Name)
|
||||
AddDirectory(manifest, normalizedName)
|
||||
} else {
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
@@ -216,10 +197,10 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
|
||||
|
||||
if file.Mode()&os.ModeSymlink != 0 {
|
||||
entry := addSymlinkOrBlobReference(
|
||||
manifest, file.Name, string(fileData), index, &missing)
|
||||
manifest, normalizedName, string(fileData), index, &missing)
|
||||
dataBytesRecycled += entry.GetOriginalSize()
|
||||
} else {
|
||||
AddFile(manifest, file.Name, fileData)
|
||||
AddFile(manifest, normalizedName, fileData)
|
||||
dataBytesTransferred += int64(len(fileData))
|
||||
}
|
||||
}
|
||||
@@ -240,4 +221,3 @@ func ExtractZip(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
|
||||
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
|
||||
18
src/fetch.go
18
src/fetch.go
@@ -9,6 +9,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/go-git/go-billy/v6/osfs"
|
||||
@@ -209,6 +210,8 @@ func FetchRepository(
|
||||
datasize.ByteSize(dataBytesTransferred).HR(),
|
||||
)
|
||||
|
||||
warnAboutGitLFS(ctx, manifest)
|
||||
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
@@ -254,3 +257,18 @@ func readGitBlob(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func warnAboutGitLFS(ctx context.Context, manifest *Manifest) {
|
||||
gitattributes := ReadGitAttributes(ctx, manifest)
|
||||
for _, name := range slices.Sorted(maps.Keys(manifest.GetContents())) {
|
||||
entry := manifest.GetContents()[name]
|
||||
if !IsEntryRegularFile(entry) {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(name, "/")
|
||||
attrs, _ := gitattributes.Match(parts, nil)
|
||||
if attr, ok := attrs["filter"]; ok && attr.Value() == "lfs" {
|
||||
AddProblem(manifest, name, "git-pages does not support Git LFS; move this file into Git or use incremental uploads")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
src/gitattributes.go
Normal file
61
src/gitattributes.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package git_pages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v6/plumbing/format/gitattributes"
|
||||
)
|
||||
|
||||
func ReadGitAttributes(ctx context.Context, manifest *Manifest) gitattributes.Matcher {
|
||||
type entryPair struct {
|
||||
parts []string
|
||||
entry *Entry
|
||||
}
|
||||
|
||||
// Collect all .gitattributes files.
|
||||
var files []entryPair
|
||||
for name, entry := range manifest.GetContents() {
|
||||
switch entry.GetType() {
|
||||
case Type_InlineFile, Type_ExternalFile:
|
||||
parts := strings.Split(name, "/")
|
||||
if parts[len(parts)-1] == ".gitattributes" {
|
||||
files = append(files, entryPair{parts, entry})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the file list by depth, then by name.
|
||||
slices.SortFunc(files, func(a entryPair, b entryPair) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(len(a.parts), len(b.parts)),
|
||||
slices.Compare(a.parts, b.parts),
|
||||
)
|
||||
})
|
||||
|
||||
// Gather all .gitattributes rules, sorted by depth.
|
||||
var rules []gitattributes.MatchAttribute
|
||||
for _, pair := range files {
|
||||
parts, entry := pair.parts, pair.entry
|
||||
data, err := GetEntryContents(ctx, entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
dirs := parts[:len(parts)-1]
|
||||
isRoot := len(parts) == 1
|
||||
newRules, err := gitattributes.ReadAttributes(bytes.NewReader(data), dirs, isRoot)
|
||||
if err != nil {
|
||||
AddProblem(manifest, strings.Join(parts, "/"), "parsing .gitattributes: %v", err)
|
||||
continue
|
||||
}
|
||||
rules = append(rules, newRules...)
|
||||
}
|
||||
|
||||
// gitattributes.Matcher applies rules in reverse.
|
||||
slices.Reverse(rules)
|
||||
matcher := gitattributes.NewMatcher(rules)
|
||||
return matcher
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package git_pages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -85,17 +86,22 @@ func validateHeaderRule(rule headers.Rule) error {
|
||||
}
|
||||
|
||||
// Parses redirects file and injects rules into the manifest.
|
||||
func ProcessHeadersFile(manifest *Manifest) error {
|
||||
func ProcessHeadersFile(ctx context.Context, manifest *Manifest) error {
|
||||
headersEntry := manifest.Contents[HeadersFileName]
|
||||
delete(manifest.Contents, HeadersFileName)
|
||||
if headersEntry == nil {
|
||||
return nil
|
||||
} else if headersEntry.GetType() != Type_InlineFile {
|
||||
return AddProblem(manifest, HeadersFileName,
|
||||
"not a regular file")
|
||||
}
|
||||
|
||||
rules, err := headers.ParseString(string(headersEntry.GetData()))
|
||||
data, err := GetEntryContents(ctx, headersEntry)
|
||||
if errors.Is(err, ErrNotRegularFile) {
|
||||
return AddProblem(manifest, HeadersFileName,
|
||||
"not a regular file")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rules, err := headers.ParseString(string(data))
|
||||
if err != nil {
|
||||
return AddProblem(manifest, HeadersFileName,
|
||||
"syntax error: %s", err)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path"
|
||||
@@ -144,6 +145,59 @@ func AddProblem(manifest *Manifest, pathName, format string, args ...any) error
|
||||
return fmt.Errorf("%s: %s", pathName, cause)
|
||||
}
|
||||
|
||||
// Returns a map of git hash to entry. If `manifest` is nil, returns an empty map.
|
||||
func IndexManifestByGitHash(manifest *Manifest) map[string]*Entry {
|
||||
index := map[string]*Entry{}
|
||||
for _, entry := range manifest.GetContents() {
|
||||
if hash := entry.GetGitHash(); hash != "" {
|
||||
if _, ok := plumbing.FromHex(hash); ok {
|
||||
index[hash] = entry
|
||||
} else {
|
||||
panic(fmt.Errorf("index: malformed hash: %s", hash))
|
||||
}
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func IsEntryRegularFile(entry *Entry) bool {
|
||||
return entry.GetType() == Type_InlineFile ||
|
||||
entry.GetType() == Type_ExternalFile
|
||||
}
|
||||
|
||||
var ErrNotRegularFile = errors.New("not a regular file")
|
||||
|
||||
func GetEntryContents(ctx context.Context, entry *Entry) (data []byte, err error) {
|
||||
switch entry.GetType() {
|
||||
case Type_InlineFile:
|
||||
data = entry.GetData()
|
||||
case Type_ExternalFile:
|
||||
reader, _, err := backend.GetBlob(ctx, string(entry.GetData()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err = io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, ErrNotRegularFile
|
||||
}
|
||||
|
||||
switch entry.GetTransform() {
|
||||
case Transform_Identity:
|
||||
case Transform_Zstd:
|
||||
data, err = zstdDecoder.DecodeAll(data, []byte{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected transform")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EnsureLeadingDirectories adds directory entries for any parent directories
|
||||
// that are implicitly referenced by files in the manifest but don't have
|
||||
// explicit directory entries. (This can be the case if an archive is created
|
||||
@@ -151,6 +205,9 @@ func AddProblem(manifest *Manifest, pathName, format string, args ...any) error
|
||||
func EnsureLeadingDirectories(manifest *Manifest) {
|
||||
for name := range manifest.Contents {
|
||||
for dir := path.Dir(name); dir != "." && dir != ""; dir = path.Dir(dir) {
|
||||
if dir == "/" {
|
||||
panic("malformed manifest (paths must not be rooted in /)")
|
||||
}
|
||||
if _, exists := manifest.Contents[dir]; !exists {
|
||||
AddDirectory(manifest, dir)
|
||||
}
|
||||
@@ -272,7 +329,7 @@ func CompressFiles(ctx context.Context, manifest *Manifest) {
|
||||
// (Perhaps in the future they could be exposed at `.git-pages/status.txt`?)
|
||||
func PrepareManifest(ctx context.Context, manifest *Manifest) error {
|
||||
// Parse Netlify-style `_redirects`.
|
||||
if err := ProcessRedirectsFile(manifest); err != nil {
|
||||
if err := ProcessRedirectsFile(ctx, manifest); err != nil {
|
||||
logc.Printf(ctx, "redirects err: %s\n", err)
|
||||
} else if len(manifest.Redirects) > 0 {
|
||||
logc.Printf(ctx, "redirects ok: %d rules\n", len(manifest.Redirects))
|
||||
@@ -282,7 +339,7 @@ func PrepareManifest(ctx context.Context, manifest *Manifest) error {
|
||||
LintRedirects(manifest)
|
||||
|
||||
// Parse Netlify-style `_headers`.
|
||||
if err := ProcessHeadersFile(manifest); err != nil {
|
||||
if err := ProcessHeadersFile(ctx, manifest); err != nil {
|
||||
logc.Printf(ctx, "headers err: %s\n", err)
|
||||
} else if len(manifest.Headers) > 0 {
|
||||
logc.Printf(ctx, "headers ok: %d rules\n", len(manifest.Headers))
|
||||
|
||||
@@ -159,7 +159,6 @@ func InitObservability() {
|
||||
}
|
||||
|
||||
options := sentry.ClientOptions{}
|
||||
options.DisableTelemetryBuffer = !config.Feature("sentry-telemetry-buffer")
|
||||
options.Environment = environment
|
||||
options.EnableLogs = enableLogs
|
||||
options.EnableTracing = enableTracing
|
||||
|
||||
18
src/pages.go
18
src/pages.go
@@ -499,9 +499,10 @@ func putPage(w http.ResponseWriter, r *http.Request) error {
|
||||
result = UpdateFromRepository(ctx, webRoot, repoURL, branch)
|
||||
|
||||
default:
|
||||
_, err := AuthorizeUpdateFromArchive(r)
|
||||
if err != nil {
|
||||
if auth, err := AuthorizeUpdateFromArchive(r); err != nil {
|
||||
return err
|
||||
} else if auth.forgeUser != nil {
|
||||
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
|
||||
}
|
||||
|
||||
if checkDryRun(w, r) {
|
||||
@@ -531,8 +532,10 @@ func patchPage(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = AuthorizeUpdateFromArchive(r); err != nil {
|
||||
if auth, err := AuthorizeUpdateFromArchive(r); err != nil {
|
||||
return err
|
||||
} else if auth.forgeUser != nil {
|
||||
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
|
||||
}
|
||||
|
||||
if checkDryRun(w, r) {
|
||||
@@ -661,9 +664,10 @@ func deletePage(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = AuthorizeUpdateFromRepository(r)
|
||||
if err != nil {
|
||||
if auth, err := AuthorizeDeletion(r); err != nil {
|
||||
return err
|
||||
} else if auth.forgeUser != nil {
|
||||
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
|
||||
}
|
||||
|
||||
if checkDryRun(w, r) {
|
||||
@@ -681,7 +685,9 @@ func deletePage(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
|
||||
func postPage(w http.ResponseWriter, r *http.Request) error {
|
||||
// Start a timer for the request timeout immediately.
|
||||
// The HTTP requests for webhook delivery usually have a short timeout. We start the timer
|
||||
// before doing any time-consuming work so that it's closely aligned to the client's timeout and
|
||||
// we can respond before the webhook delivery is considered failed.
|
||||
requestTimeout := 3 * time.Second
|
||||
requestTimer := time.NewTimer(requestTimeout)
|
||||
|
||||
|
||||
28
src/patch.go
28
src/patch.go
@@ -30,8 +30,12 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
|
||||
children map[string]*Node
|
||||
}
|
||||
|
||||
// Index the manifest for incremental update operations.
|
||||
index := IndexManifestByGitHash(manifest)
|
||||
missing := []string{}
|
||||
|
||||
// Extract the manifest contents (which is using a flat hash map) into a directory tree
|
||||
// so that recursive delete operations have O(1) complexity. s
|
||||
// so that recursive delete operations have O(1) complexity.
|
||||
var root *Node
|
||||
sortedNames := slices.Sorted(maps.Keys(manifest.GetContents()))
|
||||
for _, name := range sortedNames {
|
||||
@@ -48,9 +52,9 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
|
||||
iter := root
|
||||
for _, segment := range segments[:len(segments)-1] {
|
||||
if iter.children == nil {
|
||||
panic("malformed manifest")
|
||||
panic("malformed manifest (not a directory)")
|
||||
} else if _, exists := iter.children[segment]; !exists {
|
||||
panic("malformed manifest")
|
||||
panic("malformed manifest (node does not exist)")
|
||||
} else {
|
||||
iter = iter.children[segment]
|
||||
}
|
||||
@@ -70,7 +74,7 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
|
||||
return err
|
||||
}
|
||||
|
||||
segments := strings.Split(strings.TrimRight(header.Name, "/"), "/")
|
||||
segments := strings.Split(normalizeArchiveMemberName(header.Name), "/")
|
||||
fileName := segments[len(segments)-1]
|
||||
node := root
|
||||
for index, segment := range segments[:len(segments)-1] {
|
||||
@@ -107,8 +111,16 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
|
||||
entry: NewManifestEntry(Type_InlineFile, fileData),
|
||||
}
|
||||
case tar.TypeSymlink:
|
||||
node.children[fileName] = &Node{
|
||||
entry: NewManifestEntry(Type_Symlink, []byte(header.Linkname)),
|
||||
if hash, found := strings.CutPrefix(header.Linkname, BlobReferencePrefix); found {
|
||||
if entry, found := index[hash]; found {
|
||||
node.children[fileName] = &Node{entry: entry}
|
||||
} else {
|
||||
missing = append(missing, hash)
|
||||
}
|
||||
} else {
|
||||
node.children[fileName] = &Node{
|
||||
entry: NewManifestEntry(Type_Symlink, []byte(header.Linkname)),
|
||||
}
|
||||
}
|
||||
case tar.TypeDir:
|
||||
node.children[fileName] = &Node{
|
||||
@@ -129,6 +141,10 @@ func ApplyTarPatch(manifest *Manifest, reader io.Reader, parents CreateParentsMo
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
return UnresolvedRefError{missing}
|
||||
}
|
||||
|
||||
// Repopulate manifest contents with the updated directory tree.
|
||||
var traverse func([]string, *Node)
|
||||
traverse = func(segments []string, node *Node) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package git_pages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -96,17 +98,22 @@ func validateRedirectRule(rule *redirects.Rule) error {
|
||||
}
|
||||
|
||||
// Parses redirects file and injects rules into the manifest.
|
||||
func ProcessRedirectsFile(manifest *Manifest) error {
|
||||
func ProcessRedirectsFile(ctx context.Context, manifest *Manifest) error {
|
||||
redirectsEntry := manifest.Contents[RedirectsFileName]
|
||||
delete(manifest.Contents, RedirectsFileName)
|
||||
if redirectsEntry == nil {
|
||||
return nil
|
||||
} else if redirectsEntry.GetType() != Type_InlineFile {
|
||||
return AddProblem(manifest, RedirectsFileName,
|
||||
"not a regular file")
|
||||
}
|
||||
|
||||
rules, err := redirects.ParseString(string(redirectsEntry.GetData()))
|
||||
data, err := GetEntryContents(ctx, redirectsEntry)
|
||||
if errors.Is(err, ErrNotRegularFile) {
|
||||
return AddProblem(manifest, RedirectsFileName,
|
||||
"not a regular file")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rules, err := redirects.ParseString(string(data))
|
||||
if err != nil {
|
||||
return AddProblem(manifest, RedirectsFileName,
|
||||
"syntax error: %s", err)
|
||||
|
||||
105
src/schema.pb.go
105
src/schema.pb.go
@@ -750,6 +750,7 @@ type Principal struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
IpAddress *string `protobuf:"bytes,1,opt,name=ip_address,json=ipAddress" json:"ip_address,omitempty"`
|
||||
CliAdmin *bool `protobuf:"varint,2,opt,name=cli_admin,json=cliAdmin" json:"cli_admin,omitempty"`
|
||||
ForgeUser *ForgeUser `protobuf:"bytes,3,opt,name=forge_user,json=forgeUser" json:"forge_user,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -798,6 +799,73 @@ func (x *Principal) GetCliAdmin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Principal) GetForgeUser() *ForgeUser {
|
||||
if x != nil {
|
||||
return x.ForgeUser
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ForgeUser struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Origin *string `protobuf:"bytes,1,opt,name=origin" json:"origin,omitempty"`
|
||||
Id *int64 `protobuf:"varint,2,opt,name=id" json:"id,omitempty"`
|
||||
Handle *string `protobuf:"bytes,3,opt,name=handle" json:"handle,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ForgeUser) Reset() {
|
||||
*x = ForgeUser{}
|
||||
mi := &file_schema_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ForgeUser) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ForgeUser) ProtoMessage() {}
|
||||
|
||||
func (x *ForgeUser) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_schema_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ForgeUser.ProtoReflect.Descriptor instead.
|
||||
func (*ForgeUser) Descriptor() ([]byte, []int) {
|
||||
return file_schema_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *ForgeUser) GetOrigin() string {
|
||||
if x != nil && x.Origin != nil {
|
||||
return *x.Origin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ForgeUser) GetId() int64 {
|
||||
if x != nil && x.Id != nil {
|
||||
return *x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ForgeUser) GetHandle() string {
|
||||
if x != nil && x.Handle != nil {
|
||||
return *x.Handle
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_schema_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_schema_proto_rawDesc = "" +
|
||||
@@ -853,11 +921,18 @@ const file_schema_proto_rawDesc = "" +
|
||||
"\x06domain\x18\n" +
|
||||
" \x01(\tR\x06domain\x12\x18\n" +
|
||||
"\aproject\x18\v \x01(\tR\aproject\x12%\n" +
|
||||
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest\"G\n" +
|
||||
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest\"r\n" +
|
||||
"\tPrincipal\x12\x1d\n" +
|
||||
"\n" +
|
||||
"ip_address\x18\x01 \x01(\tR\tipAddress\x12\x1b\n" +
|
||||
"\tcli_admin\x18\x02 \x01(\bR\bcliAdmin*V\n" +
|
||||
"\tcli_admin\x18\x02 \x01(\bR\bcliAdmin\x12)\n" +
|
||||
"\n" +
|
||||
"forge_user\x18\x03 \x01(\v2\n" +
|
||||
".ForgeUserR\tforgeUser\"K\n" +
|
||||
"\tForgeUser\x12\x16\n" +
|
||||
"\x06origin\x18\x01 \x01(\tR\x06origin\x12\x0e\n" +
|
||||
"\x02id\x18\x02 \x01(\x03R\x02id\x12\x16\n" +
|
||||
"\x06handle\x18\x03 \x01(\tR\x06handle*V\n" +
|
||||
"\x04Type\x12\x10\n" +
|
||||
"\fInvalidEntry\x10\x00\x12\r\n" +
|
||||
"\tDirectory\x10\x01\x12\x0e\n" +
|
||||
@@ -889,7 +964,7 @@ func file_schema_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||
var file_schema_proto_goTypes = []any{
|
||||
(Type)(0), // 0: Type
|
||||
(Transform)(0), // 1: Transform
|
||||
@@ -902,27 +977,29 @@ var file_schema_proto_goTypes = []any{
|
||||
(*Manifest)(nil), // 8: Manifest
|
||||
(*AuditRecord)(nil), // 9: AuditRecord
|
||||
(*Principal)(nil), // 10: Principal
|
||||
nil, // 11: Manifest.ContentsEntry
|
||||
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
|
||||
(*ForgeUser)(nil), // 11: ForgeUser
|
||||
nil, // 12: Manifest.ContentsEntry
|
||||
(*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
|
||||
}
|
||||
var file_schema_proto_depIdxs = []int32{
|
||||
0, // 0: Entry.type:type_name -> Type
|
||||
1, // 1: Entry.transform:type_name -> Transform
|
||||
5, // 2: HeaderRule.header_map:type_name -> Header
|
||||
11, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
|
||||
12, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
|
||||
4, // 4: Manifest.redirects:type_name -> RedirectRule
|
||||
6, // 5: Manifest.headers:type_name -> HeaderRule
|
||||
7, // 6: Manifest.problems:type_name -> Problem
|
||||
12, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
|
||||
13, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
|
||||
2, // 8: AuditRecord.event:type_name -> AuditEvent
|
||||
10, // 9: AuditRecord.principal:type_name -> Principal
|
||||
8, // 10: AuditRecord.manifest:type_name -> Manifest
|
||||
3, // 11: Manifest.ContentsEntry.value:type_name -> Entry
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
11, // 11: Principal.forge_user:type_name -> ForgeUser
|
||||
3, // 12: Manifest.ContentsEntry.value:type_name -> Entry
|
||||
13, // [13:13] is the sub-list for method output_type
|
||||
13, // [13:13] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_schema_proto_init() }
|
||||
@@ -936,7 +1013,7 @@ func file_schema_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_schema_proto_rawDesc), len(file_schema_proto_rawDesc)),
|
||||
NumEnums: 3,
|
||||
NumMessages: 9,
|
||||
NumMessages: 10,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -132,4 +132,11 @@ message AuditRecord {
|
||||
message Principal {
|
||||
string ip_address = 1;
|
||||
bool cli_admin = 2;
|
||||
ForgeUser forge_user = 3;
|
||||
}
|
||||
|
||||
message ForgeUser {
|
||||
string origin = 1;
|
||||
int64 id = 2;
|
||||
string handle = 3;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,16 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const BlobReferencePrefix = "/git/blobs/"
|
||||
|
||||
type UnresolvedRefError struct {
|
||||
missing []string
|
||||
}
|
||||
|
||||
func (err UnresolvedRefError) Error() string {
|
||||
return fmt.Sprintf("%d unresolved blob references", len(err.missing))
|
||||
}
|
||||
|
||||
type UpdateOutcome int
|
||||
|
||||
const (
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
type WildcardPattern struct {
|
||||
Domain []string
|
||||
CloneURL *fasttemplate.Template
|
||||
IndexRepos []*fasttemplate.Template
|
||||
IndexRepo *fasttemplate.Template
|
||||
IndexBranch string
|
||||
Authorization bool
|
||||
}
|
||||
@@ -49,27 +49,24 @@ func (pattern *WildcardPattern) Matches(host string) (string, bool) {
|
||||
return subdomain, true
|
||||
}
|
||||
|
||||
func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) ([]string, string) {
|
||||
var repoURLs []string
|
||||
func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) (string, string) {
|
||||
var repoURL string
|
||||
var branch string
|
||||
repoURLTemplate := pattern.CloneURL
|
||||
if projectName == ".index" {
|
||||
for _, indexRepoTemplate := range pattern.IndexRepos {
|
||||
indexRepo := indexRepoTemplate.ExecuteString(map[string]any{"user": userName})
|
||||
repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
|
||||
"user": userName,
|
||||
"project": indexRepo,
|
||||
}))
|
||||
}
|
||||
repoURL = repoURLTemplate.ExecuteString(map[string]any{
|
||||
"user": userName,
|
||||
"project": pattern.IndexRepo.ExecuteString(map[string]any{"user": userName}),
|
||||
})
|
||||
branch = pattern.IndexBranch
|
||||
} else {
|
||||
repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
|
||||
repoURL = repoURLTemplate.ExecuteString(map[string]any{
|
||||
"user": userName,
|
||||
"project": projectName,
|
||||
}))
|
||||
})
|
||||
branch = "pages"
|
||||
}
|
||||
return repoURLs, branch
|
||||
return repoURL, branch
|
||||
}
|
||||
|
||||
func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
|
||||
@@ -80,14 +77,10 @@ func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
|
||||
return nil, fmt.Errorf("wildcard pattern: clone URL: %w", err)
|
||||
}
|
||||
|
||||
var indexRepoTemplates []*fasttemplate.Template
|
||||
var indexRepoBranch string = config.IndexRepoBranch
|
||||
for _, indexRepo := range config.IndexRepos {
|
||||
indexRepoTemplate, err := fasttemplate.NewTemplate(indexRepo, "<", ">")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wildcard pattern: index repo: %w", err)
|
||||
}
|
||||
indexRepoTemplates = append(indexRepoTemplates, indexRepoTemplate)
|
||||
indexRepoTemplate, err := fasttemplate.NewTemplate(config.IndexRepo, "<", ">")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wildcard pattern: index repo: %w", err)
|
||||
}
|
||||
|
||||
authorization := false
|
||||
@@ -107,7 +100,7 @@ func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
|
||||
wildcardPatterns = append(wildcardPatterns, &WildcardPattern{
|
||||
Domain: strings.Split(config.Domain, "."),
|
||||
CloneURL: cloneURLTemplate,
|
||||
IndexRepos: indexRepoTemplates,
|
||||
IndexRepo: indexRepoTemplate,
|
||||
IndexBranch: indexRepoBranch,
|
||||
Authorization: authorization,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user