forked from toolshed/abra
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3943d25d1 | |||
| 9859ff46f5 | |||
| 3109a0da82 | |||
| b1146a707e | |||
| 6d13d14d75 | |||
| 427ec22cab | |||
| 1111b69f12 | |||
| 1541e6aa6a | |||
| 14ee80582a | |||
| e5c65b8fa0 | |||
| e1b10f6020 | |||
|
8e4ed7b689
|
|||
|
a44fde2df2
|
|||
| e623f55852 | |||
| 11e6f28a60 | |||
| acec067d76 | |||
| f50a57af7a | |||
| 776693acc0 | |||
| 5dea5f7746 | |||
| 1d9a289888 | |||
| c7bd55e371 | |||
| 4276337b0f | |||
| 90ca856b64 | |||
| f2dd65491d | |||
| 0e902ed897 | |||
| db001c1ba4 | |||
| e4215c09aa | |||
| e0e6dcb710 | |||
| e7ddb74a08 | |||
| 24a5e6334f | |||
| 9d8eb2317e | |||
| 5945ea8e1b | |||
| e170d1c971 | |||
| 5eba3abb1b | |||
| df5a38e887 |
+11
-6
@@ -8,12 +8,17 @@ steps:
|
|||||||
- make check
|
- make check
|
||||||
|
|
||||||
- name: xgettext-go
|
- name: xgettext-go
|
||||||
image: git.coopcloud.tech/toolshed/drone-xgettext-go:latest
|
image: golang:1.26
|
||||||
settings:
|
environment:
|
||||||
keyword: i18n.G
|
GOPRIVATE: coopcloud.tech
|
||||||
keyword_ctx: i18n.GC
|
commands:
|
||||||
out: pkg/i18n/locales/abra.pot
|
- go run git.coopcloud.tech/toolshed/xgettext-go@latest
|
||||||
comments_tag: translators
|
-o pkg/i18n/locales/abra.pot
|
||||||
|
--keyword=i18n.G
|
||||||
|
--keyword-ctx=i18n.GC
|
||||||
|
--sort-output
|
||||||
|
--add-comments-tag="translators"
|
||||||
|
$(find . -name "*.go" -not -path "*vendor*" | sort)
|
||||||
depends_on:
|
depends_on:
|
||||||
- make check
|
- make check
|
||||||
when:
|
when:
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN CGO_ENABLED=0 make build
|
RUN CGO_ENABLED=0 make build
|
||||||
|
|
||||||
FROM alpine:3.22
|
FROM alpine:3.24
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ABRA := ./cmd/abra
|
ABRA := ./cmd/abra
|
||||||
XGETTEXT := ./bin/xgettext-go
|
XGETTEXT := go run git.coopcloud.tech/toolshed/xgettext-go@latest
|
||||||
COMMIT := $(shell git rev-list -1 HEAD)
|
COMMIT := $(shell git rev-list -1 HEAD)
|
||||||
GOPATH := $(shell go env GOPATH)
|
GOPATH := $(shell go env GOPATH)
|
||||||
GOVERSION := 1.26
|
GOVERSION := 1.26
|
||||||
@@ -62,8 +62,8 @@ update-po:
|
|||||||
done
|
done
|
||||||
|
|
||||||
.PHONY: update-pot
|
.PHONY: update-pot
|
||||||
update-pot: $(XGETTEXT)
|
update-pot:
|
||||||
@${XGETTEXT} \
|
@$(XGETTEXT) \
|
||||||
-o pkg/i18n/locales/$(DOMAIN).pot \
|
-o pkg/i18n/locales/$(DOMAIN).pot \
|
||||||
--keyword=i18n.G \
|
--keyword=i18n.G \
|
||||||
--keyword-ctx=i18n.GC \
|
--keyword-ctx=i18n.GC \
|
||||||
@@ -71,11 +71,6 @@ update-pot: $(XGETTEXT)
|
|||||||
--add-comments-tag="translators" \
|
--add-comments-tag="translators" \
|
||||||
$$(find . -name "*.go" -not -path "*vendor*" | sort)
|
$$(find . -name "*.go" -not -path "*vendor*" | sort)
|
||||||
|
|
||||||
${XGETTEXT}:
|
|
||||||
@mkdir -p ./bin && \
|
|
||||||
wget -O ./bin/xgettext-go https://git.coopcloud.tech/toolshed/xgettext-go/raw/branch/main/xgettext-go && \
|
|
||||||
chmod +x ./bin/xgettext-go
|
|
||||||
|
|
||||||
.PHONY: update-pot-po-metadata
|
.PHONY: update-pot-po-metadata
|
||||||
update-pot-po-metadata:
|
update-pot-po-metadata:
|
||||||
@sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot
|
@sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot
|
||||||
|
|||||||
+6
-5
@@ -151,11 +151,12 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
|
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
deployOpts := stack.Deploy{
|
deployOpts := stack.Deploy{
|
||||||
Composefiles: composeFiles,
|
Composefiles: composeFiles,
|
||||||
Namespace: stackName,
|
Namespace: stackName,
|
||||||
Prune: false,
|
Prune: false,
|
||||||
ResolveImage: stack.ResolveImageAlways,
|
ResolveImage: stack.ResolveImageAlways,
|
||||||
Detach: false,
|
Detach: false,
|
||||||
|
SendRegistryAuth: true,
|
||||||
}
|
}
|
||||||
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+5
-1
@@ -112,7 +112,11 @@ var AppNewCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recipeVersions) > 0 {
|
if recipeVersion != "" {
|
||||||
|
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else if len(recipeVersions) > 0 {
|
||||||
latest := recipeVersions[len(recipeVersions)-1]
|
latest := recipeVersions[len(recipeVersions)-1]
|
||||||
for tag := range latest {
|
for tag := range latest {
|
||||||
recipeVersion = tag
|
recipeVersion = tag
|
||||||
|
|||||||
+6
-5
@@ -166,11 +166,12 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
|
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
deployOpts := stack.Deploy{
|
deployOpts := stack.Deploy{
|
||||||
Composefiles: composeFiles,
|
Composefiles: composeFiles,
|
||||||
Namespace: stackName,
|
Namespace: stackName,
|
||||||
Prune: false,
|
Prune: false,
|
||||||
ResolveImage: stack.ResolveImageAlways,
|
ResolveImage: stack.ResolveImageAlways,
|
||||||
Detach: false,
|
Detach: false,
|
||||||
|
SendRegistryAuth: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
||||||
|
|||||||
+6
-5
@@ -178,11 +178,12 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
|
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
deployOpts := stack.Deploy{
|
deployOpts := stack.Deploy{
|
||||||
Composefiles: composeFiles,
|
Composefiles: composeFiles,
|
||||||
Namespace: stackName,
|
Namespace: stackName,
|
||||||
Prune: false,
|
Prune: false,
|
||||||
ResolveImage: stack.ResolveImageAlways,
|
ResolveImage: stack.ResolveImageAlways,
|
||||||
Detach: false,
|
Detach: false,
|
||||||
|
SendRegistryAuth: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
||||||
|
|||||||
Generated
+3
-3
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777954456,
|
"lastModified": 1778443072,
|
||||||
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
"narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module coopcloud.tech/abra
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
|
coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977
|
||||||
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
|
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
@@ -13,14 +13,14 @@ require (
|
|||||||
github.com/docker/cli v28.4.0+incompatible
|
github.com/docker/cli v28.4.0+incompatible
|
||||||
github.com/docker/docker v28.5.2+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/go-git/go-git/v5 v5.17.2
|
github.com/go-git/go-git/v5 v5.19.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/leonelquinteros/gotext v1.7.2
|
github.com/leonelquinteros/gotext v1.7.2
|
||||||
github.com/moby/sys/signal v0.7.1
|
github.com/moby/sys/signal v0.7.1
|
||||||
github.com/moby/term v0.5.2
|
github.com/moby/term v0.5.2
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/schollz/progressbar/v3 v3.19.0
|
github.com/schollz/progressbar/v3 v3.19.0
|
||||||
golang.org/x/term v0.41.0
|
golang.org/x/term v0.44.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gotest.tools/v3 v3.5.2
|
gotest.tools/v3 v3.5.2
|
||||||
)
|
)
|
||||||
@@ -60,7 +60,7 @@ require (
|
|||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.8.0 // indirect
|
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
@@ -96,7 +96,7 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/runc v1.1.13 // indirect
|
github.com/opencontainers/runc v1.1.13 // indirect
|
||||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.67.5 // indirect
|
github.com/prometheus/common v0.67.5 // indirect
|
||||||
@@ -124,10 +124,10 @@ require (
|
|||||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
golang.org/x/crypto v0.50.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||||
golang.org/x/net v0.52.0 // indirect
|
golang.org/x/net v0.53.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
golang.org/x/time v0.15.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||||
@@ -155,9 +155,9 @@ require (
|
|||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
golang.org/x/sys v0.42.0
|
golang.org/x/sys v0.46.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
|
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
|
||||||
|
|
||||||
replace github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2-i18n
|
replace github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
|
|||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca h1:gSD53tBAsbIGq4SnFfq+mEep6foekQ2a5ea7b38qkm0=
|
coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977 h1:J7I0HFjwVAj/kkX6lwSTHmlXDRjQRsdIFNUUqu55ADY=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
@@ -308,8 +308,8 @@ github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjI
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/decentral1se/cobra v1.10.2-i18n h1:XR+6AHHfnf4k5NM9f09oLMrEVwz3rkQIAIcqgL8R08g=
|
github.com/decentral1se/cobra v1.10.2 h1:MZ8Ifi/jRels9sZrpSccDbUlK++3b2HlBODfv0Bh6x0=
|
||||||
github.com/decentral1se/cobra v1.10.2-i18n/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
github.com/decentral1se/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/decentral1se/passgen v1.0.1 h1:j2AxK/kHKxDHWZZfkJj8Wgae9+O+DYEqR5sjKthIYKA=
|
github.com/decentral1se/passgen v1.0.1 h1:j2AxK/kHKxDHWZZfkJj8Wgae9+O+DYEqR5sjKthIYKA=
|
||||||
github.com/decentral1se/passgen v1.0.1/go.mod h1:530V+lNoPhKtkrX2fIVsIfLhkl47CuiOM7HRgi7C+SU=
|
github.com/decentral1se/passgen v1.0.1/go.mod h1:530V+lNoPhKtkrX2fIVsIfLhkl47CuiOM7HRgi7C+SU=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
@@ -389,12 +389,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
|
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||||
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
|
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104=
|
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
|
||||||
github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
|
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@@ -753,8 +753,8 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh
|
|||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -966,8 +966,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -978,8 +978,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@@ -1043,8 +1043,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -1140,13 +1140,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -1156,8 +1156,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|||||||
+11
-4
@@ -23,11 +23,18 @@ buildGo126Module rec {
|
|||||||
];
|
];
|
||||||
|
|
||||||
env.CGO_ENABLED = 0;
|
env.CGO_ENABLED = 0;
|
||||||
ldflags = [
|
buildPhase = ''
|
||||||
"-s -w -X 'main.Commit=${rev}' -X 'main.Version=${version}'"
|
runHook preBuild
|
||||||
];
|
go build -ldflags="-s -w -X 'main.Commit=${rev}' -X 'main.Version=${version}'" ./cmd/abra
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
install -D abra $out/bin/abra
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
doCheck = false;
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
export ABRA_DIR="$out"
|
export ABRA_DIR="$out"
|
||||||
$out/bin/abra autocomplete bash >abra.bash
|
$out/bin/abra autocomplete bash >abra.bash
|
||||||
|
|||||||
+83
-83
@@ -7,7 +7,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr "Project-Id-Version: \n"
|
msgstr "Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||||
"POT-Creation-Date: 2026-04-11 11:34+0200\n"
|
"POT-Creation-Date: 2026-06-14 17:56+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -214,7 +214,7 @@ msgstr ""
|
|||||||
msgid "%s already exists?"
|
msgid "%s already exists?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:202
|
#: ./cli/app/new.go:206
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s created (version: %s)"
|
msgid "%s created (version: %s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -299,7 +299,7 @@ msgstr ""
|
|||||||
msgid "%s has no published versions?"
|
msgid "%s has no published versions?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:311
|
#: ./cli/app/new.go:315
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s has no secrets to generate, skipping..."
|
msgid "%s has no secrets to generate, skipping..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -339,17 +339,17 @@ msgstr ""
|
|||||||
msgid "%s is missing the TYPE env var?"
|
msgid "%s is missing the TYPE env var?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:309 ./cli/app/rollback.go:313
|
#: ./cli/app/rollback.go:310 ./cli/app/rollback.go:314
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not a downgrade for %s?"
|
msgid "%s is not a downgrade for %s?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:429 ./cli/app/upgrade.go:433
|
#: ./cli/app/upgrade.go:430 ./cli/app/upgrade.go:434
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not an upgrade for %s?"
|
msgid "%s is not an upgrade for %s?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:450
|
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:451
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not deployed?"
|
msgid "%s is not deployed?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -369,7 +369,7 @@ msgstr ""
|
|||||||
msgid "%s missing context, run \"abra server add %s\"?"
|
msgid "%s missing context, run \"abra server add %s\"?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:194 ./cli/app/upgrade.go:210
|
#: ./cli/app/deploy.go:195 ./cli/app/upgrade.go:211
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s missing from %s.env"
|
msgid "%s missing from %s.env"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -404,17 +404,17 @@ msgstr ""
|
|||||||
msgid "%s removed from pass store"
|
msgid "%s removed from pass store"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:220
|
#: ./cli/app/new.go:224
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\""
|
msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:224
|
#: ./cli/app/new.go:228
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s requires secret insertion before deploy (#generate=false)"
|
msgid "%s requires secret insertion before deploy (#generate=false)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:151
|
#: ./cli/app/new.go:155
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s sanitised as %s for new app"
|
msgid "%s sanitised as %s for new app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -549,12 +549,12 @@ msgstr ""
|
|||||||
msgid "%s: waiting %d seconds before next retry"
|
msgid "%s: waiting %d seconds before next retry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:424
|
#: ./cli/app/upgrade.go:425
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "'%s' is not a known version"
|
msgid "'%s' is not a known version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:304 ./cli/app/upgrade.go:419
|
#: ./cli/app/rollback.go:305 ./cli/app/upgrade.go:420
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "'%s' is not a known version for %s"
|
msgid "'%s' is not a known version for %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -626,7 +626,7 @@ msgstr ""
|
|||||||
msgid "Both local recipe and live deployment labels are shown."
|
msgid "Both local recipe and live deployment labels are shown."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:414 ./cli/app/labels.go:143 ./cli/app/list.go:335 ./cli/app/new.go:407 ./cli/app/ps.go:213 ./cli/app/restart.go:163 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
|
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:415 ./cli/app/labels.go:143 ./cli/app/list.go:335 ./cli/app/new.go:411 ./cli/app/ps.go:213 ./cli/app/restart.go:163 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
|
||||||
msgid "C"
|
msgid "C"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -767,7 +767,7 @@ msgid "Creates a new app from a default recipe.\n"
|
|||||||
"on your $PATH."
|
"on your $PATH."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:430 ./cli/app/new.go:383 ./cli/app/rollback.go:361 ./cli/app/upgrade.go:470
|
#: ./cli/app/deploy.go:431 ./cli/app/new.go:387 ./cli/app/rollback.go:362 ./cli/app/upgrade.go:471
|
||||||
msgid "D"
|
msgid "D"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -878,7 +878,7 @@ msgid "Generate a report of all managed apps.\n"
|
|||||||
"Use \"--status/-S\" flag to query all servers for the live deployment status."
|
"Use \"--status/-S\" flag to query all servers for the live deployment status."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:317
|
#: ./cli/app/new.go:321
|
||||||
msgid "Generate app secrets?"
|
msgid "Generate app secrets?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1257,7 +1257,7 @@ msgstr ""
|
|||||||
msgid "Run app commands"
|
msgid "Run app commands"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:303 ./cli/app/list.go:296 ./cli/app/logs.go:109 ./cli/app/new.go:399
|
#: ./cli/app/backup.go:303 ./cli/app/list.go:296 ./cli/app/logs.go:109 ./cli/app/new.go:403
|
||||||
msgid "S"
|
msgid "S"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1265,7 +1265,7 @@ msgstr ""
|
|||||||
msgid "SECRETS"
|
msgid "SECRETS"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:234
|
#: ./cli/app/new.go:238
|
||||||
msgid "SECRETS OVERVIEW"
|
msgid "SECRETS OVERVIEW"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1298,7 +1298,7 @@ msgstr ""
|
|||||||
msgid "STATUS"
|
msgid "STATUS"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:342
|
#: ./cli/app/new.go:346
|
||||||
msgid "Select app server:"
|
msgid "Select app server:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1330,7 +1330,7 @@ msgstr ""
|
|||||||
msgid "Specify a server name"
|
msgid "Specify a server name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:293
|
#: ./cli/app/new.go:297
|
||||||
msgid "Specify app domain"
|
msgid "Specify app domain"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1462,7 +1462,7 @@ msgid "To load completions:\n"
|
|||||||
" # and source this file from your PowerShell profile."
|
" # and source this file from your PowerShell profile."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:454 ./cli/app/rollback.go:377 ./cli/app/upgrade.go:494
|
#: ./cli/app/deploy.go:455 ./cli/app/rollback.go:378 ./cli/app/upgrade.go:495
|
||||||
msgid "U"
|
msgid "U"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1835,7 +1835,7 @@ msgstr ""
|
|||||||
msgid "attempting to run %s"
|
msgid "attempting to run %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:283 ./cli/app/upgrade.go:296
|
#: ./cli/app/deploy.go:284 ./cli/app/upgrade.go:297
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "attempting to run post deploy commands, saw: %s"
|
msgid "attempting to run post deploy commands, saw: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1865,7 +1865,7 @@ msgstr ""
|
|||||||
msgid "autocomplete failed: %s"
|
msgid "autocomplete failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:401
|
#: ./cli/app/new.go:405
|
||||||
msgid "automatically generate secrets"
|
msgid "automatically generate secrets"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1915,7 +1915,7 @@ msgstr ""
|
|||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#. translators: `abra app cp` aliases. use a comma separated list of aliases with
|
#. translators: `abra app cp` aliases. use a comma separated list of aliases with
|
||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:438 ./cli/app/rollback.go:369 ./cli/app/upgrade.go:478
|
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:439 ./cli/app/rollback.go:370 ./cli/app/upgrade.go:479
|
||||||
msgid "c"
|
msgid "c"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1971,7 +1971,7 @@ msgstr ""
|
|||||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:388
|
#: ./cli/app/deploy.go:389
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
|
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
|
||||||
" to return to a regular release, specify a release tag, commit SHA or use \"--latest\""
|
" to return to a regular release, specify a release tag, commit SHA or use \"--latest\""
|
||||||
@@ -1990,7 +1990,7 @@ msgstr ""
|
|||||||
msgid "cannot use '[secret] [version]' and '--all' together"
|
msgid "cannot use '[secret] [version]' and '--all' together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:330
|
#: ./cli/app/deploy.go:331
|
||||||
msgid "cannot use --chaos and --latest together"
|
msgid "cannot use --chaos and --latest together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2014,11 +2014,11 @@ msgstr ""
|
|||||||
msgid "cannot use [service] and --all-services/-a together"
|
msgid "cannot use [service] and --all-services/-a together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:322 ./cli/app/new.go:80
|
#: ./cli/app/deploy.go:323 ./cli/app/new.go:80
|
||||||
msgid "cannot use [version] and --chaos together"
|
msgid "cannot use [version] and --chaos together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:326
|
#: ./cli/app/deploy.go:327
|
||||||
msgid "cannot use [version] and --latest together"
|
msgid "cannot use [version] and --latest together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2054,7 +2054,7 @@ msgstr ""
|
|||||||
msgid "cfg"
|
msgid "cfg"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:413 ./cli/app/labels.go:142 ./cli/app/list.go:334 ./cli/app/new.go:406 ./cli/app/ps.go:212 ./cli/app/restart.go:162 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
|
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:414 ./cli/app/labels.go:142 ./cli/app/list.go:334 ./cli/app/new.go:410 ./cli/app/ps.go:212 ./cli/app/restart.go:162 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
|
||||||
msgid "chaos"
|
msgid "chaos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2068,7 +2068,7 @@ msgstr ""
|
|||||||
msgid "check <domain> [flags]"
|
msgid "check <domain> [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:95 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:442
|
#: ./cli/app/deploy.go:95 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:443
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "checking whether %s is already deployed"
|
msgid "checking whether %s is already deployed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2371,7 +2371,7 @@ msgstr ""
|
|||||||
msgid "critical errors present in %s config"
|
msgid "critical errors present in %s config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:299
|
#: ./cli/app/rollback.go:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "current deployment '%s' is not a known version for %s"
|
msgid "current deployment '%s' is not a known version for %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2422,7 +2422,7 @@ msgstr ""
|
|||||||
msgid "deploy labels stanza present"
|
msgid "deploy labels stanza present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:448
|
#: ./cli/app/deploy.go:449
|
||||||
msgid "deploy latest recipe version"
|
msgid "deploy latest recipe version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2524,11 +2524,11 @@ msgstr ""
|
|||||||
msgid "dirty: %v, "
|
msgid "dirty: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:440 ./cli/app/rollback.go:371 ./cli/app/upgrade.go:480
|
#: ./cli/app/deploy.go:441 ./cli/app/rollback.go:372 ./cli/app/upgrade.go:481
|
||||||
msgid "disable converge logic checks"
|
msgid "disable converge logic checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:432 ./cli/app/rollback.go:363 ./cli/app/upgrade.go:472
|
#: ./cli/app/deploy.go:433 ./cli/app/rollback.go:364 ./cli/app/upgrade.go:473
|
||||||
msgid "disable public DNS checks"
|
msgid "disable public DNS checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2544,11 +2544,11 @@ msgstr ""
|
|||||||
msgid "docker: is the daemon running / your user has docker permissions?"
|
msgid "docker: is the daemon running / your user has docker permissions?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:382
|
#: ./cli/app/new.go:386
|
||||||
msgid "domain"
|
msgid "domain"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:385
|
#: ./cli/app/new.go:389
|
||||||
msgid "domain name for app"
|
msgid "domain name for app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2737,7 +2737,7 @@ msgstr ""
|
|||||||
|
|
||||||
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: ./cli/app/deploy.go:422 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:353 ./cli/app/secret.go:593 ./cli/app/upgrade.go:462 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
|
#: ./cli/app/deploy.go:423 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:354 ./cli/app/secret.go:593 ./cli/app/upgrade.go:463 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
|
||||||
msgid "f"
|
msgid "f"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2898,7 +2898,7 @@ msgstr ""
|
|||||||
msgid "failed to resize tty, using default size"
|
msgid "failed to resize tty, using default size"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:134
|
#: ./cli/app/new.go:138
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to retrieve latest commit for %s: %s"
|
msgid "failed to retrieve latest commit for %s: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2988,7 +2988,7 @@ msgstr ""
|
|||||||
msgid "final merged env values for %s are: %s"
|
msgid "final merged env values for %s are: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:421 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:352 ./cli/app/upgrade.go:461 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
|
#: ./cli/app/deploy.go:422 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:353 ./cli/app/upgrade.go:462 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
|
||||||
msgid "force"
|
msgid "force"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3202,7 +3202,7 @@ msgstr ""
|
|||||||
msgid "id: %s, "
|
msgid "id: %s, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:416 ./cli/app/labels.go:145 ./cli/app/list.go:337 ./cli/app/new.go:409 ./cli/app/ps.go:215 ./cli/app/restart.go:165 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
|
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:417 ./cli/app/labels.go:145 ./cli/app/list.go:337 ./cli/app/new.go:413 ./cli/app/ps.go:215 ./cli/app/restart.go:165 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
|
||||||
msgid "ignore uncommitted recipes changes"
|
msgid "ignore uncommitted recipes changes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3400,7 +3400,7 @@ msgstr ""
|
|||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#. translators: `abra recipe lint` aliases. use a comma separated list of
|
#. translators: `abra recipe lint` aliases. use a comma separated list of
|
||||||
#. aliases with no spaces in between
|
#. aliases with no spaces in between
|
||||||
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:446 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
|
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:447 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
|
||||||
msgid "l"
|
msgid "l"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3415,7 +3415,7 @@ msgstr ""
|
|||||||
msgid "labels <domain> [flags]"
|
msgid "labels <domain> [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:445 ./cli/app/list.go:186
|
#: ./cli/app/deploy.go:446 ./cli/app/list.go:186
|
||||||
msgid "latest"
|
msgid "latest"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3752,7 +3752,7 @@ msgstr ""
|
|||||||
msgid "no containers matching the %v filter found?"
|
msgid "no containers matching the %v filter found?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:302 ./cli/internal/validate.go:129
|
#: ./cli/app/new.go:306 ./cli/internal/validate.go:129
|
||||||
msgid "no domain provided"
|
msgid "no domain provided"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3779,7 +3779,7 @@ msgstr ""
|
|||||||
msgid "no recipe name provided"
|
msgid "no recipe name provided"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:241
|
#: ./cli/app/upgrade.go:242
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "no release notes for upgrading from %s to %s"
|
msgid "no release notes for upgrading from %s to %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -3810,7 +3810,7 @@ msgstr ""
|
|||||||
msgid "no secrets to remove?"
|
msgid "no secrets to remove?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:351 ./cli/internal/validate.go:167
|
#: ./cli/app/new.go:355 ./cli/internal/validate.go:167
|
||||||
msgid "no server provided"
|
msgid "no server provided"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3868,11 +3868,11 @@ msgstr ""
|
|||||||
msgid "no volumes to remove"
|
msgid "no volumes to remove"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:437 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477
|
#: ./cli/app/deploy.go:438 ./cli/app/rollback.go:369 ./cli/app/upgrade.go:478
|
||||||
msgid "no-converge-checks"
|
msgid "no-converge-checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:429 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469
|
#: ./cli/app/deploy.go:430 ./cli/app/rollback.go:361 ./cli/app/upgrade.go:470
|
||||||
msgid "no-domain-checks"
|
msgid "no-domain-checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3940,7 +3940,7 @@ msgstr ""
|
|||||||
msgid "only show errors"
|
msgid "only show errors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:488
|
#: ./cli/app/upgrade.go:489
|
||||||
msgid "only show release notes"
|
msgid "only show release notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3952,7 +3952,7 @@ msgstr ""
|
|||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#. translators: `abra server prune` aliases. use a comma separated list of
|
#. translators: `abra server prune` aliases. use a comma separated list of
|
||||||
#. aliases with no spaces in between
|
#. aliases with no spaces in between
|
||||||
#: ./cli/app/backup.go:295 ./cli/app/new.go:391 ./cli/app/ps.go:29 ./cli/app/secret.go:561 ./cli/app/secret.go:585 ./cli/app/secret.go:625 ./cli/app/undeploy.go:169 ./cli/catalogue/catalogue.go:294 ./cli/recipe/list.go:112 ./cli/server/prune.go:18
|
#: ./cli/app/backup.go:295 ./cli/app/new.go:395 ./cli/app/ps.go:29 ./cli/app/secret.go:561 ./cli/app/secret.go:585 ./cli/app/secret.go:625 ./cli/app/undeploy.go:169 ./cli/catalogue/catalogue.go:294 ./cli/recipe/list.go:112 ./cli/server/prune.go:18
|
||||||
msgid "p"
|
msgid "p"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3971,27 +3971,27 @@ msgstr ""
|
|||||||
msgid "parsed following command arguments: %s"
|
msgid "parsed following command arguments: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:345
|
#: ./cli/app/upgrade.go:346
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing chosen upgrade version failed: %s"
|
msgid "parsing chosen upgrade version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:389
|
#: ./cli/app/upgrade.go:390
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing deployed version failed: %s"
|
msgid "parsing deployed version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:350
|
#: ./cli/app/upgrade.go:351
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing deployment version failed: %s"
|
msgid "parsing deployment version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:356 ./cli/app/upgrade.go:395
|
#: ./cli/app/upgrade.go:357 ./cli/app/upgrade.go:396
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing recipe version failed: %s"
|
msgid "parsing recipe version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:390 ./cli/app/secret.go:560 ./cli/app/secret.go:584 ./cli/app/secret.go:624
|
#: ./cli/app/new.go:394 ./cli/app/secret.go:560 ./cli/app/secret.go:584 ./cli/app/secret.go:624
|
||||||
msgid "pass"
|
msgid "pass"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4011,7 +4011,7 @@ msgstr ""
|
|||||||
msgid "pattern"
|
msgid "pattern"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:424 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:355 ./cli/app/upgrade.go:464 ./cli/app/volume.go:219
|
#: ./cli/app/deploy.go:425 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:356 ./cli/app/upgrade.go:465 ./cli/app/volume.go:219
|
||||||
msgid "perform action without further prompt"
|
msgid "perform action without further prompt"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4021,22 +4021,22 @@ msgstr ""
|
|||||||
msgid "pl,p"
|
msgid "pl,p"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:267
|
#: ./cli/app/rollback.go:268
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select a downgrade (version: %s):"
|
msgid "please select a downgrade (version: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:272
|
#: ./cli/app/rollback.go:273
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select a downgrade (version: %s, chaos: %s):"
|
msgid "please select a downgrade (version: %s, chaos: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:312
|
#: ./cli/app/upgrade.go:313
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select an upgrade (version: %s):"
|
msgid "please select an upgrade (version: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:317
|
#: ./cli/app/upgrade.go:318
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select an upgrade (version: %s, chaos: %s):"
|
msgid "please select an upgrade (version: %s, chaos: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4119,7 +4119,7 @@ msgstr ""
|
|||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#. translators: `abra recipe` aliases. use a comma separated list of aliases
|
#. translators: `abra recipe` aliases. use a comma separated list of aliases
|
||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: ./cli/app/backup.go:327 ./cli/app/list.go:304 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:486 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:624
|
#: ./cli/app/backup.go:327 ./cli/app/list.go:304 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:487 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:624
|
||||||
msgid "r"
|
msgid "r"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4239,7 +4239,7 @@ msgstr ""
|
|||||||
msgid "release failed. any changes made have been reverted"
|
msgid "release failed. any changes made have been reverted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:485
|
#: ./cli/app/upgrade.go:486
|
||||||
msgid "releasenotes"
|
msgid "releasenotes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4543,7 +4543,7 @@ msgstr ""
|
|||||||
msgid "run command locally"
|
msgid "run command locally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:281 ./cli/app/upgrade.go:293
|
#: ./cli/app/deploy.go:282 ./cli/app/upgrade.go:294
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "run the following post-deploy commands: %s"
|
msgid "run the following post-deploy commands: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4584,7 +4584,7 @@ msgstr ""
|
|||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#. translators: `abra server` aliases. use a comma separated list of aliases
|
#. translators: `abra server` aliases. use a comma separated list of aliases
|
||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: ./cli/app/backup.go:198 ./cli/app/backup.go:263 ./cli/app/backup.go:287 ./cli/app/env.go:333 ./cli/app/list.go:327 ./cli/app/logs.go:101 ./cli/app/new.go:368 ./cli/app/restore.go:114 ./cli/app/secret.go:535 ./cli/catalogue/catalogue.go:27 ./cli/catalogue/catalogue.go:310 ./cli/recipe/fetch.go:130 ./cli/server/server.go:12
|
#: ./cli/app/backup.go:198 ./cli/app/backup.go:263 ./cli/app/backup.go:287 ./cli/app/env.go:333 ./cli/app/list.go:327 ./cli/app/logs.go:101 ./cli/app/new.go:372 ./cli/app/restore.go:114 ./cli/app/secret.go:535 ./cli/catalogue/catalogue.go:27 ./cli/catalogue/catalogue.go:310 ./cli/recipe/fetch.go:130 ./cli/server/server.go:12
|
||||||
msgid "s"
|
msgid "s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4626,12 +4626,12 @@ msgstr ""
|
|||||||
msgid "secret not found: %s"
|
msgid "secret not found: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:358
|
#: ./cli/app/deploy.go:359
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secret not generated: %s"
|
msgid "secret not generated: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:356
|
#: ./cli/app/deploy.go:357
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secret not inserted (#generate=false): %s"
|
msgid "secret not inserted (#generate=false): %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4641,27 +4641,27 @@ msgstr ""
|
|||||||
msgid "secret: %s removed"
|
msgid "secret: %s removed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:302 ./cli/app/new.go:398
|
#: ./cli/app/backup.go:302 ./cli/app/new.go:402
|
||||||
msgid "secrets"
|
msgid "secrets"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:238
|
#: ./cli/app/new.go:242
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secrets are %s shown again, please save them %s"
|
msgid "secrets are %s shown again, please save them %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:306
|
#: ./cli/app/deploy.go:307
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "selected latest recipe version: %s (from %d available versions)"
|
msgid "selected latest recipe version: %s (from %d available versions)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:121
|
#: ./cli/app/new.go:125
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "selected recipe version: %s (from %d available versions)"
|
msgid "selected recipe version: %s (from %d available versions)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. translators: `abra server` command for autocompletion
|
#. translators: `abra server` command for autocompletion
|
||||||
#: ./cli/app/env.go:332 ./cli/app/env.go:339 ./cli/app/list.go:326 ./cli/app/list.go:341 ./cli/app/new.go:367 ./cli/app/new.go:374 ./cli/run.go:101
|
#: ./cli/app/env.go:332 ./cli/app/env.go:339 ./cli/app/list.go:326 ./cli/app/list.go:341 ./cli/app/new.go:371 ./cli/app/new.go:378 ./cli/run.go:101
|
||||||
msgid "server"
|
msgid "server"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4775,7 +4775,7 @@ msgstr ""
|
|||||||
msgid "severity"
|
msgid "severity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:456 ./cli/app/rollback.go:379 ./cli/app/upgrade.go:496
|
#: ./cli/app/deploy.go:457 ./cli/app/rollback.go:380 ./cli/app/upgrade.go:497
|
||||||
msgid "show all configs & images, including unchanged ones"
|
msgid "show all configs & images, including unchanged ones"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4799,7 +4799,7 @@ msgstr ""
|
|||||||
msgid "show debug messages"
|
msgid "show debug messages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:453 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493
|
#: ./cli/app/deploy.go:454 ./cli/app/rollback.go:377 ./cli/app/upgrade.go:494
|
||||||
msgid "show-unchanged"
|
msgid "show-unchanged"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4807,7 +4807,7 @@ msgstr ""
|
|||||||
msgid "since"
|
msgid "since"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:336
|
#: ./cli/app/new.go:340
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "single server detected, choosing %s automatically"
|
msgid "single server detected, choosing %s automatically"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4847,11 +4847,11 @@ msgstr ""
|
|||||||
msgid "skipping converge logic checks"
|
msgid "skipping converge logic checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:208
|
#: ./cli/app/deploy.go:209
|
||||||
msgid "skipping domain checks"
|
msgid "skipping domain checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:205
|
#: ./cli/app/deploy.go:206
|
||||||
msgid "skipping domain checks, no DOMAIN=... configured"
|
msgid "skipping domain checks, no DOMAIN=... configured"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4907,7 +4907,7 @@ msgstr ""
|
|||||||
msgid "specify secret value"
|
msgid "specify secret value"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:370
|
#: ./cli/app/new.go:374
|
||||||
msgid "specify server for new app"
|
msgid "specify server for new app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4965,7 +4965,7 @@ msgstr ""
|
|||||||
msgid "store generated secrets in a local pass store"
|
msgid "store generated secrets in a local pass store"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:393
|
#: ./cli/app/new.go:397
|
||||||
msgid "store secrets in a local pass store"
|
msgid "store secrets in a local pass store"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5115,7 +5115,7 @@ msgstr ""
|
|||||||
msgid "trim input"
|
msgid "trim input"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/new.go:263 ./pkg/app/app.go:141
|
#: ./cli/app/new.go:267 ./pkg/app/app.go:141
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "trimming %s to %s to avoid runtime limits"
|
msgid "trimming %s to %s to avoid runtime limits"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5653,27 +5653,27 @@ msgstr ""
|
|||||||
msgid "version wiped from %s.env"
|
msgid "version wiped from %s.env"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:372
|
#: ./cli/app/deploy.go:373
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking chaos version: %s"
|
msgid "version: taking chaos version: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:398
|
#: ./cli/app/deploy.go:399
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking deployed version: %s"
|
msgid "version: taking deployed version: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:403
|
#: ./cli/app/deploy.go:404
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking new recipe version: %s"
|
msgid "version: taking new recipe version: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:392
|
#: ./cli/app/deploy.go:393
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking version from .env file: %s"
|
msgid "version: taking version from .env file: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:378
|
#: ./cli/app/deploy.go:379
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking version from cli arg: %s"
|
msgid "version: taking version from cli arg: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5801,7 +5801,7 @@ msgstr ""
|
|||||||
msgid "writer: %v, "
|
msgid "writer: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:288 ./cli/app/new.go:251 ./cli/app/rollback.go:256 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:301
|
#: ./cli/app/deploy.go:289 ./cli/app/new.go:255 ./cli/app/rollback.go:257 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:302
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "writing recipe version failed: %s"
|
msgid "writing recipe version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
+92
-92
@@ -2,7 +2,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||||
"POT-Creation-Date: 2026-04-11 11:34+0200\n"
|
"POT-Creation-Date: 2026-06-14 17:56+0200\n"
|
||||||
"PO-Revision-Date: 2026-02-28 13:52+0000\n"
|
"PO-Revision-Date: 2026-02-28 13:52+0000\n"
|
||||||
"Last-Translator: chasqui <chasqui@cryptolab.net>\n"
|
"Last-Translator: chasqui <chasqui@cryptolab.net>\n"
|
||||||
"Language-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\n"
|
"Language-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-cloud/abra/es/>\n"
|
||||||
@@ -318,7 +318,7 @@ msgstr "%s ya existe"
|
|||||||
msgid "%s already exists?"
|
msgid "%s already exists?"
|
||||||
msgstr "%s ¿ya existe?"
|
msgstr "%s ¿ya existe?"
|
||||||
|
|
||||||
#: cli/app/new.go:202
|
#: cli/app/new.go:206
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s created (version: %s)"
|
msgid "%s created (version: %s)"
|
||||||
msgstr "%s creado (versión: %s)"
|
msgstr "%s creado (versión: %s)"
|
||||||
@@ -403,7 +403,7 @@ msgstr "¿%s no tiene una aplicación principal?"
|
|||||||
msgid "%s has no published versions?"
|
msgid "%s has no published versions?"
|
||||||
msgstr "¿%s no tiene versiones publicadas?"
|
msgstr "¿%s no tiene versiones publicadas?"
|
||||||
|
|
||||||
#: cli/app/new.go:311
|
#: cli/app/new.go:315
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s has no secrets to generate, skipping..."
|
msgid "%s has no secrets to generate, skipping..."
|
||||||
msgstr "%s no tiene secretos para generar, omitiendo..."
|
msgstr "%s no tiene secretos para generar, omitiendo..."
|
||||||
@@ -443,19 +443,19 @@ msgstr "¿%s no tiene un archivo compose.yml o compose.*.yml?"
|
|||||||
msgid "%s is missing the TYPE env var?"
|
msgid "%s is missing the TYPE env var?"
|
||||||
msgstr "¿A %s le falta la variable de entorno TYPE?"
|
msgstr "¿A %s le falta la variable de entorno TYPE?"
|
||||||
|
|
||||||
#: cli/app/rollback.go:309 cli/app/rollback.go:313
|
#: cli/app/rollback.go:310 cli/app/rollback.go:314
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not a downgrade for %s?"
|
msgid "%s is not a downgrade for %s?"
|
||||||
msgstr "¿%s no es un downgrade para %s?"
|
msgstr "¿%s no es un downgrade para %s?"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:429 cli/app/upgrade.go:433
|
#: cli/app/upgrade.go:430 cli/app/upgrade.go:434
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not an upgrade for %s?"
|
msgid "%s is not an upgrade for %s?"
|
||||||
msgstr "¿%s no es una actualización para %s?"
|
msgstr "¿%s no es una actualización para %s?"
|
||||||
|
|
||||||
#: cli/app/env.go:146 cli/app/logs.go:65 cli/app/ps.go:62
|
#: cli/app/env.go:146 cli/app/logs.go:65 cli/app/ps.go:62
|
||||||
#: cli/app/restart.go:100 cli/app/services.go:55 cli/app/undeploy.go:66
|
#: cli/app/restart.go:100 cli/app/services.go:55 cli/app/undeploy.go:66
|
||||||
#: cli/app/upgrade.go:450
|
#: cli/app/upgrade.go:451
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not deployed?"
|
msgid "%s is not deployed?"
|
||||||
msgstr "¿%s no está desplegado?"
|
msgstr "¿%s no está desplegado?"
|
||||||
@@ -475,7 +475,7 @@ msgstr "%s todavía está desplegado. Ejecuta \"abra aplicacion plegar %s\""
|
|||||||
msgid "%s missing context, run \"abra server add %s\"?"
|
msgid "%s missing context, run \"abra server add %s\"?"
|
||||||
msgstr "falta contexto %s, ¿ejecutar \"abra servidor agregar %s\"?"
|
msgstr "falta contexto %s, ¿ejecutar \"abra servidor agregar %s\"?"
|
||||||
|
|
||||||
#: cli/app/deploy.go:194 cli/app/upgrade.go:210
|
#: cli/app/deploy.go:195 cli/app/upgrade.go:211
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s missing from %s.env"
|
msgid "%s missing from %s.env"
|
||||||
msgstr "falta %s de %s.env"
|
msgstr "falta %s de %s.env"
|
||||||
@@ -510,17 +510,17 @@ msgstr "notas de la versión %s:"
|
|||||||
msgid "%s removed from pass store"
|
msgid "%s removed from pass store"
|
||||||
msgstr "%s eliminado del almacén de contraseñas"
|
msgstr "%s eliminado del almacén de contraseñas"
|
||||||
|
|
||||||
#: cli/app/new.go:220
|
#: cli/app/new.go:224
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\""
|
msgid "%s requires secret generation before deploy, run \"abra app secret generate %s --all\""
|
||||||
msgstr "%s requiere generación de secretos antes del despliegue, ejecuta \"abra aplicacion secreto generar %s --all\""
|
msgstr "%s requiere generación de secretos antes del despliegue, ejecuta \"abra aplicacion secreto generar %s --all\""
|
||||||
|
|
||||||
#: cli/app/new.go:224
|
#: cli/app/new.go:228
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s requires secret insertion before deploy (#generate=false)"
|
msgid "%s requires secret insertion before deploy (#generate=false)"
|
||||||
msgstr "%s requiere inserción de secretos antes del despliegue (#generate=false)"
|
msgstr "%s requiere inserción de secretos antes del despliegue (#generate=false)"
|
||||||
|
|
||||||
#: cli/app/new.go:151
|
#: cli/app/new.go:155
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s sanitised as %s for new app"
|
msgid "%s sanitised as %s for new app"
|
||||||
msgstr "%s sanitisado como %s para nueva aplicación"
|
msgstr "%s sanitisado como %s para nueva aplicación"
|
||||||
@@ -655,12 +655,12 @@ msgstr "%s: no se puede resolver la dirección IPv4: %s"
|
|||||||
msgid "%s: waiting %d seconds before next retry"
|
msgid "%s: waiting %d seconds before next retry"
|
||||||
msgstr "%s: esperando %d segundos antes del siguiente reintento"
|
msgstr "%s: esperando %d segundos antes del siguiente reintento"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:424
|
#: cli/app/upgrade.go:425
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "'%s' is not a known version"
|
msgid "'%s' is not a known version"
|
||||||
msgstr "'%s' no es una versión conocida"
|
msgstr "'%s' no es una versión conocida"
|
||||||
|
|
||||||
#: cli/app/rollback.go:304 cli/app/upgrade.go:419
|
#: cli/app/rollback.go:305 cli/app/upgrade.go:420
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "'%s' is not a known version for %s"
|
msgid "'%s' is not a known version for %s"
|
||||||
msgstr "'%s' no es una versión conocida para %s"
|
msgstr "'%s' no es una versión conocida para %s"
|
||||||
@@ -759,8 +759,8 @@ msgid "Both local recipe and live deployment labels are shown."
|
|||||||
msgstr "Se muestran tanto la receta local como las etiquetas del despliegue en vivo."
|
msgstr "Se muestran tanto la receta local como las etiquetas del despliegue en vivo."
|
||||||
|
|
||||||
#: cli/app/backup.go:319 cli/app/backup.go:335 cli/app/check.go:95
|
#: cli/app/backup.go:319 cli/app/backup.go:335 cli/app/check.go:95
|
||||||
#: cli/app/cmd.go:285 cli/app/cp.go:385 cli/app/deploy.go:414
|
#: cli/app/cmd.go:285 cli/app/cp.go:385 cli/app/deploy.go:415
|
||||||
#: cli/app/labels.go:143 cli/app/list.go:335 cli/app/new.go:407
|
#: cli/app/labels.go:143 cli/app/list.go:335 cli/app/new.go:411
|
||||||
#: cli/app/ps.go:213 cli/app/restart.go:163 cli/app/restore.go:138
|
#: cli/app/ps.go:213 cli/app/restart.go:163 cli/app/restore.go:138
|
||||||
#: cli/app/secret.go:569 cli/app/secret.go:609 cli/app/secret.go:633
|
#: cli/app/secret.go:569 cli/app/secret.go:609 cli/app/secret.go:633
|
||||||
#: cli/app/secret.go:641 cli/catalogue/catalogue.go:318 cli/recipe/lint.go:137
|
#: cli/app/secret.go:641 cli/catalogue/catalogue.go:318 cli/recipe/lint.go:137
|
||||||
@@ -959,8 +959,8 @@ msgstr ""
|
|||||||
"en un almacén pass (ver passwordstore.org). El comando pass debe estar \n"
|
"en un almacén pass (ver passwordstore.org). El comando pass debe estar \n"
|
||||||
"disponible en tu $PATH."
|
"disponible en tu $PATH."
|
||||||
|
|
||||||
#: cli/app/deploy.go:430 cli/app/new.go:383 cli/app/rollback.go:361
|
#: cli/app/deploy.go:431 cli/app/new.go:387 cli/app/rollback.go:362
|
||||||
#: cli/app/upgrade.go:470
|
#: cli/app/upgrade.go:471
|
||||||
msgid "D"
|
msgid "D"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1112,7 +1112,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Usa la opción \"--estado/-S\" para consultar en todos los servidores sobre el estado de despliegue."
|
"Usa la opción \"--estado/-S\" para consultar en todos los servidores sobre el estado de despliegue."
|
||||||
|
|
||||||
#: cli/app/new.go:317
|
#: cli/app/new.go:321
|
||||||
msgid "Generate app secrets?"
|
msgid "Generate app secrets?"
|
||||||
msgstr "¿Generar secretos de la aplicación?"
|
msgstr "¿Generar secretos de la aplicación?"
|
||||||
|
|
||||||
@@ -1579,7 +1579,7 @@ msgid "Run app commands"
|
|||||||
msgstr "Ejecutar 💻 comando dentro una 🚀aplicación"
|
msgstr "Ejecutar 💻 comando dentro una 🚀aplicación"
|
||||||
|
|
||||||
#: cli/app/backup.go:303 cli/app/list.go:296 cli/app/logs.go:109
|
#: cli/app/backup.go:303 cli/app/list.go:296 cli/app/logs.go:109
|
||||||
#: cli/app/new.go:399
|
#: cli/app/new.go:403
|
||||||
msgid "S"
|
msgid "S"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1587,7 +1587,7 @@ msgstr ""
|
|||||||
msgid "SECRETS"
|
msgid "SECRETS"
|
||||||
msgstr "🥷 SECRETOS"
|
msgstr "🥷 SECRETOS"
|
||||||
|
|
||||||
#: cli/app/new.go:234
|
#: cli/app/new.go:238
|
||||||
msgid "SECRETS OVERVIEW"
|
msgid "SECRETS OVERVIEW"
|
||||||
msgstr "RESUMEN DE LOS 🥷 SECRETOS"
|
msgstr "RESUMEN DE LOS 🥷 SECRETOS"
|
||||||
|
|
||||||
@@ -1621,7 +1621,7 @@ msgstr ""
|
|||||||
msgid "STATUS"
|
msgid "STATUS"
|
||||||
msgstr "🩻 ESTADO"
|
msgstr "🩻 ESTADO"
|
||||||
|
|
||||||
#: cli/app/new.go:342
|
#: cli/app/new.go:346
|
||||||
msgid "Select app server:"
|
msgid "Select app server:"
|
||||||
msgstr "Seleccionar servidor para la aplicación:"
|
msgstr "Seleccionar servidor para la aplicación:"
|
||||||
|
|
||||||
@@ -1659,7 +1659,7 @@ msgstr "Especifica un 🌐 nombre de dominio (DNS)"
|
|||||||
msgid "Specify a server name"
|
msgid "Specify a server name"
|
||||||
msgstr "Especifica un nombre de servidor"
|
msgstr "Especifica un nombre de servidor"
|
||||||
|
|
||||||
#: cli/app/new.go:293
|
#: cli/app/new.go:297
|
||||||
msgid "Specify app domain"
|
msgid "Specify app domain"
|
||||||
msgstr "Especifica un 🌐 nombre de dominio (DNS) para la aplicación"
|
msgstr "Especifica un 🌐 nombre de dominio (DNS) para la aplicación"
|
||||||
|
|
||||||
@@ -1881,7 +1881,7 @@ msgstr ""
|
|||||||
" PS> abra autocomplete powershell > abra.ps1\n"
|
" PS> abra autocomplete powershell > abra.ps1\n"
|
||||||
" # y fuente (source) ese archivo desde tu perfil de PowerShell."
|
" # y fuente (source) ese archivo desde tu perfil de PowerShell."
|
||||||
|
|
||||||
#: cli/app/deploy.go:454 cli/app/rollback.go:377 cli/app/upgrade.go:494
|
#: cli/app/deploy.go:455 cli/app/rollback.go:378 cli/app/upgrade.go:495
|
||||||
msgid "U"
|
msgid "U"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2368,7 +2368,7 @@ msgstr "Intentando generar y almacenar %s en %s"
|
|||||||
msgid "attempting to run %s"
|
msgid "attempting to run %s"
|
||||||
msgstr "Intentando ejecutar %s"
|
msgstr "Intentando ejecutar %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:283 cli/app/upgrade.go:296
|
#: cli/app/deploy.go:284 cli/app/upgrade.go:297
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "attempting to run post deploy commands, saw: %s"
|
msgid "attempting to run post deploy commands, saw: %s"
|
||||||
msgstr "Intentando ejecutar los comandos posteriores al despliegue: %s"
|
msgstr "Intentando ejecutar los comandos posteriores al despliegue: %s"
|
||||||
@@ -2404,7 +2404,7 @@ msgstr "autocompletar [bash|zsh|fish|powershell]"
|
|||||||
msgid "autocomplete failed: %s"
|
msgid "autocomplete failed: %s"
|
||||||
msgstr "autocompletar falló: %s"
|
msgstr "autocompletar falló: %s"
|
||||||
|
|
||||||
#: cli/app/new.go:401
|
#: cli/app/new.go:405
|
||||||
msgid "automatically generate secrets"
|
msgid "automatically generate secrets"
|
||||||
msgstr "generar secretos automáticamente"
|
msgstr "generar secretos automáticamente"
|
||||||
|
|
||||||
@@ -2454,8 +2454,8 @@ msgstr "enlace symlink roto en tus carpetas de configuración de abra: %s"
|
|||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#. translators: `abra app cp` aliases. use a comma separated list of aliases with
|
#. translators: `abra app cp` aliases. use a comma separated list of aliases with
|
||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#: cli/app/backup.go:148 cli/app/cp.go:30 cli/app/deploy.go:438
|
#: cli/app/backup.go:148 cli/app/cp.go:30 cli/app/deploy.go:439
|
||||||
#: cli/app/rollback.go:369 cli/app/upgrade.go:478
|
#: cli/app/rollback.go:370 cli/app/upgrade.go:479
|
||||||
msgid "c"
|
msgid "c"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2511,7 +2511,7 @@ msgstr "no se puede obtener la etiqueta %s para %s"
|
|||||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||||
msgstr "no se puede redeplegar la versión anterior de caos (%s), ¿Era tu intención usar \"--caos\"?"
|
msgstr "no se puede redeplegar la versión anterior de caos (%s), ¿Era tu intención usar \"--caos\"?"
|
||||||
|
|
||||||
#: cli/app/deploy.go:388
|
#: cli/app/deploy.go:389
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
|
"cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
|
||||||
@@ -2533,7 +2533,7 @@ msgstr "no se puede especificar una etiqueta y un tipo de incremento al mismo ti
|
|||||||
msgid "cannot use '[secret] [version]' and '--all' together"
|
msgid "cannot use '[secret] [version]' and '--all' together"
|
||||||
msgstr "no se puede usar '[secreto] [versión]' y '--todos' juntos"
|
msgstr "no se puede usar '[secreto] [versión]' y '--todos' juntos"
|
||||||
|
|
||||||
#: cli/app/deploy.go:330
|
#: cli/app/deploy.go:331
|
||||||
msgid "cannot use --chaos and --latest together"
|
msgid "cannot use --chaos and --latest together"
|
||||||
msgstr "no se puede usar --caos y --latest juntos"
|
msgstr "no se puede usar --caos y --latest juntos"
|
||||||
|
|
||||||
@@ -2557,11 +2557,11 @@ msgstr "no se puede usar [server] y --local al mismo tiempo"
|
|||||||
msgid "cannot use [service] and --all-services/-a together"
|
msgid "cannot use [service] and --all-services/-a together"
|
||||||
msgstr "no se puede usar [servicio] y --todos-los-servicios/-a juntos"
|
msgstr "no se puede usar [servicio] y --todos-los-servicios/-a juntos"
|
||||||
|
|
||||||
#: cli/app/deploy.go:322 cli/app/new.go:80
|
#: cli/app/deploy.go:323 cli/app/new.go:80
|
||||||
msgid "cannot use [version] and --chaos together"
|
msgid "cannot use [version] and --chaos together"
|
||||||
msgstr "no se puede usar [versión] y --caos juntos"
|
msgstr "no se puede usar [versión] y --caos juntos"
|
||||||
|
|
||||||
#: cli/app/deploy.go:326
|
#: cli/app/deploy.go:327
|
||||||
msgid "cannot use [version] and --latest together"
|
msgid "cannot use [version] and --latest together"
|
||||||
msgstr "no se puede usar [versión] y --latest juntos"
|
msgstr "no se puede usar [versión] y --latest juntos"
|
||||||
|
|
||||||
@@ -2598,8 +2598,8 @@ msgid "cfg"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: cli/app/backup.go:318 cli/app/backup.go:334 cli/app/check.go:94
|
#: cli/app/backup.go:318 cli/app/backup.go:334 cli/app/check.go:94
|
||||||
#: cli/app/cmd.go:284 cli/app/cp.go:384 cli/app/deploy.go:413
|
#: cli/app/cmd.go:284 cli/app/cp.go:384 cli/app/deploy.go:414
|
||||||
#: cli/app/labels.go:142 cli/app/list.go:334 cli/app/new.go:406
|
#: cli/app/labels.go:142 cli/app/list.go:334 cli/app/new.go:410
|
||||||
#: cli/app/ps.go:212 cli/app/restart.go:162 cli/app/restore.go:137
|
#: cli/app/ps.go:212 cli/app/restart.go:162 cli/app/restore.go:137
|
||||||
#: cli/app/secret.go:568 cli/app/secret.go:608 cli/app/secret.go:632
|
#: cli/app/secret.go:568 cli/app/secret.go:608 cli/app/secret.go:632
|
||||||
#: cli/app/secret.go:640 cli/catalogue/catalogue.go:317 cli/recipe/lint.go:136
|
#: cli/app/secret.go:640 cli/catalogue/catalogue.go:317 cli/recipe/lint.go:136
|
||||||
@@ -2616,7 +2616,7 @@ msgstr ""
|
|||||||
msgid "check <domain> [flags]"
|
msgid "check <domain> [flags]"
|
||||||
msgstr "verificar <aplicacion> [opciones]"
|
msgstr "verificar <aplicacion> [opciones]"
|
||||||
|
|
||||||
#: cli/app/deploy.go:95 cli/app/undeploy.go:58 cli/app/upgrade.go:442
|
#: cli/app/deploy.go:95 cli/app/undeploy.go:58 cli/app/upgrade.go:443
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "checking whether %s is already deployed"
|
msgid "checking whether %s is already deployed"
|
||||||
msgstr "verificando si %s ya está desplegado"
|
msgstr "verificando si %s ya está desplegado"
|
||||||
@@ -2919,7 +2919,7 @@ msgstr "crítico"
|
|||||||
msgid "critical errors present in %s config"
|
msgid "critical errors present in %s config"
|
||||||
msgstr "errores críticos presentes en la configuración de %s"
|
msgstr "errores críticos presentes en la configuración de %s"
|
||||||
|
|
||||||
#: cli/app/rollback.go:299
|
#: cli/app/rollback.go:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "current deployment '%s' is not a known version for %s"
|
msgid "current deployment '%s' is not a known version for %s"
|
||||||
msgstr "el despliegue actual '%s' no es una versión conocida para %s"
|
msgstr "el despliegue actual '%s' no es una versión conocida para %s"
|
||||||
@@ -2971,7 +2971,7 @@ msgstr "despliegue en proceso 🟠"
|
|||||||
msgid "deploy labels stanza present"
|
msgid "deploy labels stanza present"
|
||||||
msgstr "stanza de etiquetas de despliegue presente"
|
msgstr "stanza de etiquetas de despliegue presente"
|
||||||
|
|
||||||
#: cli/app/deploy.go:448
|
#: cli/app/deploy.go:449
|
||||||
msgid "deploy latest recipe version"
|
msgid "deploy latest recipe version"
|
||||||
msgstr "desplegar la última versión de la receta"
|
msgstr "desplegar la última versión de la receta"
|
||||||
|
|
||||||
@@ -3073,11 +3073,11 @@ msgstr "el directorio está vacío: %s"
|
|||||||
msgid "dirty: %v, "
|
msgid "dirty: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: cli/app/deploy.go:440 cli/app/rollback.go:371 cli/app/upgrade.go:480
|
#: cli/app/deploy.go:441 cli/app/rollback.go:372 cli/app/upgrade.go:481
|
||||||
msgid "disable converge logic checks"
|
msgid "disable converge logic checks"
|
||||||
msgstr "desactivar comprobaciones de lógica de convergencia"
|
msgstr "desactivar comprobaciones de lógica de convergencia"
|
||||||
|
|
||||||
#: cli/app/deploy.go:432 cli/app/rollback.go:363 cli/app/upgrade.go:472
|
#: cli/app/deploy.go:433 cli/app/rollback.go:364 cli/app/upgrade.go:473
|
||||||
msgid "disable public DNS checks"
|
msgid "disable public DNS checks"
|
||||||
msgstr "desactivar comprobaciones de DNS público"
|
msgstr "desactivar comprobaciones de DNS público"
|
||||||
|
|
||||||
@@ -3093,11 +3093,11 @@ msgstr "no solicitar un TTY"
|
|||||||
msgid "docker: is the daemon running / your user has docker permissions?"
|
msgid "docker: is the daemon running / your user has docker permissions?"
|
||||||
msgstr "docker: ¿está corriendo el daemon / tu usuario tiene permisos de Docker?"
|
msgstr "docker: ¿está corriendo el daemon / tu usuario tiene permisos de Docker?"
|
||||||
|
|
||||||
#: cli/app/new.go:382
|
#: cli/app/new.go:386
|
||||||
msgid "domain"
|
msgid "domain"
|
||||||
msgstr "dominio"
|
msgstr "dominio"
|
||||||
|
|
||||||
#: cli/app/new.go:385
|
#: cli/app/new.go:389
|
||||||
msgid "domain name for app"
|
msgid "domain name for app"
|
||||||
msgstr "nombre de dominio para la aplicación"
|
msgstr "nombre de dominio para la aplicación"
|
||||||
|
|
||||||
@@ -3288,8 +3288,8 @@ msgstr "extrayendo secreto %s en %s"
|
|||||||
|
|
||||||
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: cli/app/deploy.go:422 cli/app/env.go:325 cli/app/remove.go:163
|
#: cli/app/deploy.go:423 cli/app/env.go:325 cli/app/remove.go:163
|
||||||
#: cli/app/rollback.go:353 cli/app/secret.go:593 cli/app/upgrade.go:462
|
#: cli/app/rollback.go:354 cli/app/secret.go:593 cli/app/upgrade.go:463
|
||||||
#: cli/app/volume.go:217 cli/recipe/fetch.go:20 cli/recipe/fetch.go:138
|
#: cli/app/volume.go:217 cli/recipe/fetch.go:20 cli/recipe/fetch.go:138
|
||||||
msgid "f"
|
msgid "f"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -3451,7 +3451,7 @@ msgstr "🛑 No se pudo eliminar algunos recursos del stock: %s"
|
|||||||
msgid "failed to resize tty, using default size"
|
msgid "failed to resize tty, using default size"
|
||||||
msgstr "🛑 No se pudo redimensionar el TTY; se usará el tamaño predeterminado"
|
msgstr "🛑 No se pudo redimensionar el TTY; se usará el tamaño predeterminado"
|
||||||
|
|
||||||
#: cli/app/new.go:134
|
#: cli/app/new.go:138
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to retrieve latest commit for %s: %s"
|
msgid "failed to retrieve latest commit for %s: %s"
|
||||||
msgstr "🛑 No se pudo obtener el último commit de %s: %s"
|
msgstr "🛑 No se pudo obtener el último commit de %s: %s"
|
||||||
@@ -3541,8 +3541,8 @@ msgstr "Filtrar por receta"
|
|||||||
msgid "final merged env values for %s are: %s"
|
msgid "final merged env values for %s are: %s"
|
||||||
msgstr "Los valores finales combinados de entorno para %s son: %s"
|
msgstr "Los valores finales combinados de entorno para %s son: %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:421 cli/app/env.go:324 cli/app/remove.go:162
|
#: cli/app/deploy.go:422 cli/app/env.go:324 cli/app/remove.go:162
|
||||||
#: cli/app/rollback.go:352 cli/app/upgrade.go:461 cli/app/volume.go:216
|
#: cli/app/rollback.go:353 cli/app/upgrade.go:462 cli/app/volume.go:216
|
||||||
#: cli/recipe/fetch.go:137
|
#: cli/recipe/fetch.go:137
|
||||||
msgid "force"
|
msgid "force"
|
||||||
msgstr "forzar"
|
msgstr "forzar"
|
||||||
@@ -3758,8 +3758,8 @@ msgid "id: %s, "
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: cli/app/backup.go:321 cli/app/backup.go:337 cli/app/check.go:97
|
#: cli/app/backup.go:321 cli/app/backup.go:337 cli/app/check.go:97
|
||||||
#: cli/app/cmd.go:287 cli/app/cp.go:387 cli/app/deploy.go:416
|
#: cli/app/cmd.go:287 cli/app/cp.go:387 cli/app/deploy.go:417
|
||||||
#: cli/app/labels.go:145 cli/app/list.go:337 cli/app/new.go:409
|
#: cli/app/labels.go:145 cli/app/list.go:337 cli/app/new.go:413
|
||||||
#: cli/app/ps.go:215 cli/app/restart.go:165 cli/app/restore.go:140
|
#: cli/app/ps.go:215 cli/app/restart.go:165 cli/app/restore.go:140
|
||||||
#: cli/app/secret.go:571 cli/app/secret.go:611 cli/app/secret.go:635
|
#: cli/app/secret.go:571 cli/app/secret.go:611 cli/app/secret.go:635
|
||||||
#: cli/app/secret.go:643 cli/catalogue/catalogue.go:320 cli/recipe/lint.go:139
|
#: cli/app/secret.go:643 cli/catalogue/catalogue.go:320 cli/recipe/lint.go:139
|
||||||
@@ -3960,7 +3960,7 @@ msgstr "versión %s especificada inválida"
|
|||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#. translators: `abra recipe lint` aliases. use a comma separated list of
|
#. translators: `abra recipe lint` aliases. use a comma separated list of
|
||||||
#. aliases with no spaces in between
|
#. aliases with no spaces in between
|
||||||
#: cli/app/cmd.go:261 cli/app/deploy.go:446 cli/app/logs.go:20
|
#: cli/app/cmd.go:261 cli/app/deploy.go:447 cli/app/logs.go:20
|
||||||
#: cli/recipe/lint.go:17 cli/server/add.go:207
|
#: cli/recipe/lint.go:17 cli/server/add.go:207
|
||||||
msgid "l"
|
msgid "l"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -3976,7 +3976,7 @@ msgstr ""
|
|||||||
msgid "labels <domain> [flags]"
|
msgid "labels <domain> [flags]"
|
||||||
msgstr "etiquetas <aplicacion> [opciones]"
|
msgstr "etiquetas <aplicacion> [opciones]"
|
||||||
|
|
||||||
#: cli/app/deploy.go:445 cli/app/list.go:186
|
#: cli/app/deploy.go:446 cli/app/list.go:186
|
||||||
msgid "latest"
|
msgid "latest"
|
||||||
msgstr "ultima"
|
msgstr "ultima"
|
||||||
|
|
||||||
@@ -4325,7 +4325,7 @@ msgstr "no hay configuraciones para eliminar"
|
|||||||
msgid "no containers matching the %v filter found?"
|
msgid "no containers matching the %v filter found?"
|
||||||
msgstr "no se encontraron contenedores que coincidan con el filtro %v?"
|
msgstr "no se encontraron contenedores que coincidan con el filtro %v?"
|
||||||
|
|
||||||
#: cli/app/new.go:302 cli/internal/validate.go:129
|
#: cli/app/new.go:306 cli/internal/validate.go:129
|
||||||
msgid "no domain provided"
|
msgid "no domain provided"
|
||||||
msgstr "no se proporcionó dominio"
|
msgstr "no se proporcionó dominio"
|
||||||
|
|
||||||
@@ -4352,7 +4352,7 @@ msgstr "¿no existe la receta '%s'?"
|
|||||||
msgid "no recipe name provided"
|
msgid "no recipe name provided"
|
||||||
msgstr "no se proporcionó el nombre de la receta"
|
msgstr "no se proporcionó el nombre de la receta"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:241
|
#: cli/app/upgrade.go:242
|
||||||
#, fuzzy, c-format
|
#, fuzzy, c-format
|
||||||
msgid "no release notes for upgrading from %s to %s"
|
msgid "no release notes for upgrading from %s to %s"
|
||||||
msgstr "ejecución de prueba: mover la nota de la versión de 'next' a %s"
|
msgstr "ejecución de prueba: mover la nota de la versión de 'next' a %s"
|
||||||
@@ -4383,7 +4383,7 @@ msgstr "no hay secretos para eliminar"
|
|||||||
msgid "no secrets to remove?"
|
msgid "no secrets to remove?"
|
||||||
msgstr "¿No hay secretos para eliminar?"
|
msgstr "¿No hay secretos para eliminar?"
|
||||||
|
|
||||||
#: cli/app/new.go:351 cli/internal/validate.go:167
|
#: cli/app/new.go:355 cli/internal/validate.go:167
|
||||||
msgid "no server provided"
|
msgid "no server provided"
|
||||||
msgstr "no se proporcionó servidor"
|
msgstr "no se proporcionó servidor"
|
||||||
|
|
||||||
@@ -4441,11 +4441,11 @@ msgstr "no se eliminaron volúmenes"
|
|||||||
msgid "no volumes to remove"
|
msgid "no volumes to remove"
|
||||||
msgstr "no hay volúmenes para eliminar"
|
msgstr "no hay volúmenes para eliminar"
|
||||||
|
|
||||||
#: cli/app/deploy.go:437 cli/app/rollback.go:368 cli/app/upgrade.go:477
|
#: cli/app/deploy.go:438 cli/app/rollback.go:369 cli/app/upgrade.go:478
|
||||||
msgid "no-converge-checks"
|
msgid "no-converge-checks"
|
||||||
msgstr "sin-verificaciones-de-convergencia"
|
msgstr "sin-verificaciones-de-convergencia"
|
||||||
|
|
||||||
#: cli/app/deploy.go:429 cli/app/rollback.go:360 cli/app/upgrade.go:469
|
#: cli/app/deploy.go:430 cli/app/rollback.go:361 cli/app/upgrade.go:470
|
||||||
msgid "no-domain-checks"
|
msgid "no-domain-checks"
|
||||||
msgstr "sin-verificaciones-de-dominio"
|
msgstr "sin-verificaciones-de-dominio"
|
||||||
|
|
||||||
@@ -4513,7 +4513,7 @@ msgstr "solo se usaron etiquetas anotadas para la versión de la receta"
|
|||||||
msgid "only show errors"
|
msgid "only show errors"
|
||||||
msgstr "mostrar solo errores"
|
msgstr "mostrar solo errores"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:488
|
#: cli/app/upgrade.go:489
|
||||||
msgid "only show release notes"
|
msgid "only show release notes"
|
||||||
msgstr "solo mostrar notas de la versión"
|
msgstr "solo mostrar notas de la versión"
|
||||||
|
|
||||||
@@ -4525,7 +4525,7 @@ msgstr "solo cola de stderr"
|
|||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#. translators: `abra server prune` aliases. use a comma separated list of
|
#. translators: `abra server prune` aliases. use a comma separated list of
|
||||||
#. aliases with no spaces in between
|
#. aliases with no spaces in between
|
||||||
#: cli/app/backup.go:295 cli/app/new.go:391 cli/app/ps.go:29
|
#: cli/app/backup.go:295 cli/app/new.go:395 cli/app/ps.go:29
|
||||||
#: cli/app/secret.go:561 cli/app/secret.go:585 cli/app/secret.go:625
|
#: cli/app/secret.go:561 cli/app/secret.go:585 cli/app/secret.go:625
|
||||||
#: cli/app/undeploy.go:169 cli/catalogue/catalogue.go:294
|
#: cli/app/undeploy.go:169 cli/catalogue/catalogue.go:294
|
||||||
#: cli/recipe/list.go:112 cli/server/prune.go:18
|
#: cli/recipe/list.go:112 cli/server/prune.go:18
|
||||||
@@ -4547,27 +4547,27 @@ msgstr "parseado %s de %s"
|
|||||||
msgid "parsed following command arguments: %s"
|
msgid "parsed following command arguments: %s"
|
||||||
msgstr "parseados los siguientes argumentos del comando: %s"
|
msgstr "parseados los siguientes argumentos del comando: %s"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:345
|
#: cli/app/upgrade.go:346
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing chosen upgrade version failed: %s"
|
msgid "parsing chosen upgrade version failed: %s"
|
||||||
msgstr "falló la actualización de versión elegida para el parsing: %s"
|
msgstr "falló la actualización de versión elegida para el parsing: %s"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:389
|
#: cli/app/upgrade.go:390
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing deployed version failed: %s"
|
msgid "parsing deployed version failed: %s"
|
||||||
msgstr "parsing fallido de la versión desplegada: %s"
|
msgstr "parsing fallido de la versión desplegada: %s"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:350
|
#: cli/app/upgrade.go:351
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing deployment version failed: %s"
|
msgid "parsing deployment version failed: %s"
|
||||||
msgstr "parsing fallido de la versión de despliegue: %s"
|
msgstr "parsing fallido de la versión de despliegue: %s"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:356 cli/app/upgrade.go:395
|
#: cli/app/upgrade.go:357 cli/app/upgrade.go:396
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing recipe version failed: %s"
|
msgid "parsing recipe version failed: %s"
|
||||||
msgstr "parsing fallido de la versión de la receta: %s"
|
msgstr "parsing fallido de la versión de la receta: %s"
|
||||||
|
|
||||||
#: cli/app/new.go:390 cli/app/secret.go:560 cli/app/secret.go:584
|
#: cli/app/new.go:394 cli/app/secret.go:560 cli/app/secret.go:584
|
||||||
#: cli/app/secret.go:624
|
#: cli/app/secret.go:624
|
||||||
msgid "pass"
|
msgid "pass"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4590,8 +4590,8 @@ msgstr "ruta"
|
|||||||
msgid "pattern"
|
msgid "pattern"
|
||||||
msgstr "patrón"
|
msgstr "patrón"
|
||||||
|
|
||||||
#: cli/app/deploy.go:424 cli/app/env.go:327 cli/app/remove.go:165
|
#: cli/app/deploy.go:425 cli/app/env.go:327 cli/app/remove.go:165
|
||||||
#: cli/app/rollback.go:355 cli/app/upgrade.go:464 cli/app/volume.go:219
|
#: cli/app/rollback.go:356 cli/app/upgrade.go:465 cli/app/volume.go:219
|
||||||
msgid "perform action without further prompt"
|
msgid "perform action without further prompt"
|
||||||
msgstr "realizar acción sin más avisos"
|
msgstr "realizar acción sin más avisos"
|
||||||
|
|
||||||
@@ -4601,22 +4601,22 @@ msgstr "realizar acción sin más avisos"
|
|||||||
msgid "pl,p"
|
msgid "pl,p"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: cli/app/rollback.go:267
|
#: cli/app/rollback.go:268
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select a downgrade (version: %s):"
|
msgid "please select a downgrade (version: %s):"
|
||||||
msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s):"
|
msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s):"
|
||||||
|
|
||||||
#: cli/app/rollback.go:272
|
#: cli/app/rollback.go:273
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select a downgrade (version: %s, chaos: %s):"
|
msgid "please select a downgrade (version: %s, chaos: %s):"
|
||||||
msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s, caos: %s):"
|
msgstr "por favor, selecciona una versión anterior (downgrade) (versión: %s, caos: %s):"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:312
|
#: cli/app/upgrade.go:313
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select an upgrade (version: %s):"
|
msgid "please select an upgrade (version: %s):"
|
||||||
msgstr "por favor, selecciona una actualización (versión: %s):"
|
msgstr "por favor, selecciona una actualización (versión: %s):"
|
||||||
|
|
||||||
#: cli/app/upgrade.go:317
|
#: cli/app/upgrade.go:318
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select an upgrade (version: %s, chaos: %s):"
|
msgid "please select an upgrade (version: %s, chaos: %s):"
|
||||||
msgstr "por favor, selecciona una actualización (versión: %s, caos: %s):"
|
msgstr "por favor, selecciona una actualización (versión: %s, caos: %s):"
|
||||||
@@ -4702,7 +4702,7 @@ msgstr "consultando servidores remotos..."
|
|||||||
#. translators: `abra recipe` aliases. use a comma separated list of aliases
|
#. translators: `abra recipe` aliases. use a comma separated list of aliases
|
||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: cli/app/backup.go:327 cli/app/list.go:304 cli/app/move.go:350
|
#: cli/app/backup.go:327 cli/app/list.go:304 cli/app/move.go:350
|
||||||
#: cli/app/run.go:23 cli/app/upgrade.go:486 cli/catalogue/catalogue.go:302
|
#: cli/app/run.go:23 cli/app/upgrade.go:487 cli/catalogue/catalogue.go:302
|
||||||
#: cli/recipe/recipe.go:12 cli/recipe/release.go:624
|
#: cli/recipe/recipe.go:12 cli/recipe/release.go:624
|
||||||
msgid "r"
|
msgid "r"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -4827,7 +4827,7 @@ msgstr "publicar <receta> [version] [opciones]"
|
|||||||
msgid "release failed. any changes made have been reverted"
|
msgid "release failed. any changes made have been reverted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: cli/app/upgrade.go:485
|
#: cli/app/upgrade.go:486
|
||||||
msgid "releasenotes"
|
msgid "releasenotes"
|
||||||
msgstr "notas de la versión"
|
msgstr "notas de la versión"
|
||||||
|
|
||||||
@@ -5132,7 +5132,7 @@ msgstr "ejecutar comando como usuario"
|
|||||||
msgid "run command locally"
|
msgid "run command locally"
|
||||||
msgstr "ejecutar comando localmente"
|
msgstr "ejecutar comando localmente"
|
||||||
|
|
||||||
#: cli/app/deploy.go:281 cli/app/upgrade.go:293
|
#: cli/app/deploy.go:282 cli/app/upgrade.go:294
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "run the following post-deploy commands: %s"
|
msgid "run the following post-deploy commands: %s"
|
||||||
msgstr "Ejecutar los siguientes comandos después del despliegue: %s"
|
msgstr "Ejecutar los siguientes comandos después del despliegue: %s"
|
||||||
@@ -5175,7 +5175,7 @@ msgstr "ejecutando el comando posterior '%s %s' en el contenedor %s"
|
|||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: cli/app/backup.go:198 cli/app/backup.go:263 cli/app/backup.go:287
|
#: cli/app/backup.go:198 cli/app/backup.go:263 cli/app/backup.go:287
|
||||||
#: cli/app/env.go:333 cli/app/list.go:327 cli/app/logs.go:101
|
#: cli/app/env.go:333 cli/app/list.go:327 cli/app/logs.go:101
|
||||||
#: cli/app/new.go:368 cli/app/restore.go:114 cli/app/secret.go:535
|
#: cli/app/new.go:372 cli/app/restore.go:114 cli/app/secret.go:535
|
||||||
#: cli/catalogue/catalogue.go:27 cli/catalogue/catalogue.go:310
|
#: cli/catalogue/catalogue.go:27 cli/catalogue/catalogue.go:310
|
||||||
#: cli/recipe/fetch.go:130 cli/server/server.go:12
|
#: cli/recipe/fetch.go:130 cli/server/server.go:12
|
||||||
msgid "s"
|
msgid "s"
|
||||||
@@ -5219,12 +5219,12 @@ msgstr "datos del secreto no proporcionados en la línea de comandos o stdin, so
|
|||||||
msgid "secret not found: %s"
|
msgid "secret not found: %s"
|
||||||
msgstr "secreto no encontrado: %s"
|
msgstr "secreto no encontrado: %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:358
|
#: cli/app/deploy.go:359
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secret not generated: %s"
|
msgid "secret not generated: %s"
|
||||||
msgstr "secreto no generado: %s"
|
msgstr "secreto no generado: %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:356
|
#: cli/app/deploy.go:357
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secret not inserted (#generate=false): %s"
|
msgid "secret not inserted (#generate=false): %s"
|
||||||
msgstr "secreto no insertado (#generate=false): %s"
|
msgstr "secreto no insertado (#generate=false): %s"
|
||||||
@@ -5234,28 +5234,28 @@ msgstr "secreto no insertado (#generate=false): %s"
|
|||||||
msgid "secret: %s removed"
|
msgid "secret: %s removed"
|
||||||
msgstr "secreto: %s eliminado"
|
msgstr "secreto: %s eliminado"
|
||||||
|
|
||||||
#: cli/app/backup.go:302 cli/app/new.go:398
|
#: cli/app/backup.go:302 cli/app/new.go:402
|
||||||
msgid "secrets"
|
msgid "secrets"
|
||||||
msgstr "secretos"
|
msgstr "secretos"
|
||||||
|
|
||||||
#: cli/app/new.go:238
|
#: cli/app/new.go:242
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secrets are %s shown again, please save them %s"
|
msgid "secrets are %s shown again, please save them %s"
|
||||||
msgstr "los secretos se muestran %s nuevamente, por favor guárdalos %s"
|
msgstr "los secretos se muestran %s nuevamente, por favor guárdalos %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:306
|
#: cli/app/deploy.go:307
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "selected latest recipe version: %s (from %d available versions)"
|
msgid "selected latest recipe version: %s (from %d available versions)"
|
||||||
msgstr "versión de receta más reciente seleccionada: %s (de %d versiones disponibles)"
|
msgstr "versión de receta más reciente seleccionada: %s (de %d versiones disponibles)"
|
||||||
|
|
||||||
#: cli/app/new.go:121
|
#: cli/app/new.go:125
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "selected recipe version: %s (from %d available versions)"
|
msgid "selected recipe version: %s (from %d available versions)"
|
||||||
msgstr "versión de receta seleccionada: %s (de %d versiones disponibles)"
|
msgstr "versión de receta seleccionada: %s (de %d versiones disponibles)"
|
||||||
|
|
||||||
#. translators: `abra server` command for autocompletion
|
#. translators: `abra server` command for autocompletion
|
||||||
#: cli/app/env.go:332 cli/app/env.go:339 cli/app/list.go:326
|
#: cli/app/env.go:332 cli/app/env.go:339 cli/app/list.go:326
|
||||||
#: cli/app/list.go:341 cli/app/new.go:367 cli/app/new.go:374 cli/run.go:101
|
#: cli/app/list.go:341 cli/app/new.go:371 cli/app/new.go:378 cli/run.go:101
|
||||||
msgid "server"
|
msgid "server"
|
||||||
msgstr "servidor"
|
msgstr "servidor"
|
||||||
|
|
||||||
@@ -5369,7 +5369,7 @@ msgstr "establecer referencia: %s"
|
|||||||
msgid "severity"
|
msgid "severity"
|
||||||
msgstr "gravedad"
|
msgstr "gravedad"
|
||||||
|
|
||||||
#: cli/app/deploy.go:456 cli/app/rollback.go:379 cli/app/upgrade.go:496
|
#: cli/app/deploy.go:457 cli/app/rollback.go:380 cli/app/upgrade.go:497
|
||||||
msgid "show all configs & images, including unchanged ones"
|
msgid "show all configs & images, including unchanged ones"
|
||||||
msgstr "mostrar todas las configuraciones e imágenes, incluidas las no cambiadas"
|
msgstr "mostrar todas las configuraciones e imágenes, incluidas las no cambiadas"
|
||||||
|
|
||||||
@@ -5393,7 +5393,7 @@ msgstr "mostrar aplicaciones de un servidor específico"
|
|||||||
msgid "show debug messages"
|
msgid "show debug messages"
|
||||||
msgstr "mostrar mensajes para el debugeo"
|
msgstr "mostrar mensajes para el debugeo"
|
||||||
|
|
||||||
#: cli/app/deploy.go:453 cli/app/rollback.go:376 cli/app/upgrade.go:493
|
#: cli/app/deploy.go:454 cli/app/rollback.go:377 cli/app/upgrade.go:494
|
||||||
msgid "show-unchanged"
|
msgid "show-unchanged"
|
||||||
msgstr "mostrar-sin-cambios"
|
msgstr "mostrar-sin-cambios"
|
||||||
|
|
||||||
@@ -5401,7 +5401,7 @@ msgstr "mostrar-sin-cambios"
|
|||||||
msgid "since"
|
msgid "since"
|
||||||
msgstr "desde"
|
msgstr "desde"
|
||||||
|
|
||||||
#: cli/app/new.go:336
|
#: cli/app/new.go:340
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "single server detected, choosing %s automatically"
|
msgid "single server detected, choosing %s automatically"
|
||||||
msgstr "se ha detectado un único servidor, eligiendo %s automáticamente"
|
msgstr "se ha detectado un único servidor, eligiendo %s automáticamente"
|
||||||
@@ -5441,11 +5441,11 @@ msgstr "omitiendo según lo solicitado, plegado aún está en progreso 🟠"
|
|||||||
msgid "skipping converge logic checks"
|
msgid "skipping converge logic checks"
|
||||||
msgstr "omitiendo comprobaciones de lógica de convergencia"
|
msgstr "omitiendo comprobaciones de lógica de convergencia"
|
||||||
|
|
||||||
#: cli/app/deploy.go:208
|
#: cli/app/deploy.go:209
|
||||||
msgid "skipping domain checks"
|
msgid "skipping domain checks"
|
||||||
msgstr "omitiendo comprobaciones de dominio"
|
msgstr "omitiendo comprobaciones de dominio"
|
||||||
|
|
||||||
#: cli/app/deploy.go:205
|
#: cli/app/deploy.go:206
|
||||||
msgid "skipping domain checks, no DOMAIN=... configured"
|
msgid "skipping domain checks, no DOMAIN=... configured"
|
||||||
msgstr "omitiendo comprobaciones de dominio, no DOMAIN=... configurado"
|
msgstr "omitiendo comprobaciones de dominio, no DOMAIN=... configurado"
|
||||||
|
|
||||||
@@ -5501,7 +5501,7 @@ msgstr "especificar archivo de secreto"
|
|||||||
msgid "specify secret value"
|
msgid "specify secret value"
|
||||||
msgstr "especificar valor de secreto"
|
msgstr "especificar valor de secreto"
|
||||||
|
|
||||||
#: cli/app/new.go:370
|
#: cli/app/new.go:374
|
||||||
msgid "specify server for new app"
|
msgid "specify server for new app"
|
||||||
msgstr "especificar servidor para la nueva aplicación"
|
msgstr "especificar servidor para la nueva aplicación"
|
||||||
|
|
||||||
@@ -5559,7 +5559,7 @@ msgstr ""
|
|||||||
msgid "store generated secrets in a local pass store"
|
msgid "store generated secrets in a local pass store"
|
||||||
msgstr "almacenar secretos generados en un almacén de contraseñas local"
|
msgstr "almacenar secretos generados en un almacén de contraseñas local"
|
||||||
|
|
||||||
#: cli/app/new.go:393
|
#: cli/app/new.go:397
|
||||||
msgid "store secrets in a local pass store"
|
msgid "store secrets in a local pass store"
|
||||||
msgstr "almacenar secretos en un almacén de contraseñas local"
|
msgstr "almacenar secretos en un almacén de contraseñas local"
|
||||||
|
|
||||||
@@ -5710,7 +5710,7 @@ msgstr "recortar"
|
|||||||
msgid "trim input"
|
msgid "trim input"
|
||||||
msgstr "recortar entrada"
|
msgstr "recortar entrada"
|
||||||
|
|
||||||
#: cli/app/new.go:263 pkg/app/app.go:141
|
#: cli/app/new.go:267 pkg/app/app.go:141
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "trimming %s to %s to avoid runtime limits"
|
msgid "trimming %s to %s to avoid runtime limits"
|
||||||
msgstr "recortando %s a %s para evitar límites de tiempo de ejecución"
|
msgstr "recortando %s a %s para evitar límites de tiempo de ejecución"
|
||||||
@@ -6255,27 +6255,27 @@ msgstr "la versión parece inválida: %s"
|
|||||||
msgid "version wiped from %s.env"
|
msgid "version wiped from %s.env"
|
||||||
msgstr "versión eliminada de %s.env"
|
msgstr "versión eliminada de %s.env"
|
||||||
|
|
||||||
#: cli/app/deploy.go:372
|
#: cli/app/deploy.go:373
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking chaos version: %s"
|
msgid "version: taking chaos version: %s"
|
||||||
msgstr "versión: tomando la versión de caos: %s"
|
msgstr "versión: tomando la versión de caos: %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:398
|
#: cli/app/deploy.go:399
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking deployed version: %s"
|
msgid "version: taking deployed version: %s"
|
||||||
msgstr "versión: tomando la versión desplegada: %s"
|
msgstr "versión: tomando la versión desplegada: %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:403
|
#: cli/app/deploy.go:404
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking new recipe version: %s"
|
msgid "version: taking new recipe version: %s"
|
||||||
msgstr "versión: tomando la nueva versión de la receta: %s"
|
msgstr "versión: tomando la nueva versión de la receta: %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:392
|
#: cli/app/deploy.go:393
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking version from .env file: %s"
|
msgid "version: taking version from .env file: %s"
|
||||||
msgstr "versión: tomando la versión desde el archivo .env: %s"
|
msgstr "versión: tomando la versión desde el archivo .env: %s"
|
||||||
|
|
||||||
#: cli/app/deploy.go:378
|
#: cli/app/deploy.go:379
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking version from cli arg: %s"
|
msgid "version: taking version from cli arg: %s"
|
||||||
msgstr "versión: tomando la versión desde el argumento de la línea de comandos: %s"
|
msgstr "versión: tomando la versión desde el argumento de la línea de comandos: %s"
|
||||||
@@ -6406,8 +6406,8 @@ msgstr "el directorio de trabajo no está limpio en %s, abortando"
|
|||||||
msgid "writer: %v, "
|
msgid "writer: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: cli/app/deploy.go:288 cli/app/new.go:251 cli/app/rollback.go:256
|
#: cli/app/deploy.go:289 cli/app/new.go:255 cli/app/rollback.go:257
|
||||||
#: cli/app/undeploy.go:120 cli/app/upgrade.go:301
|
#: cli/app/undeploy.go:120 cli/app/upgrade.go:302
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "writing recipe version failed: %s"
|
msgid "writing recipe version failed: %s"
|
||||||
msgstr "escritura de la versión de la receta fallida: %s"
|
msgstr "escritura de la versión de la receta fallida: %s"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: nginx:1.21.0
|
image: nginx:1.31.2
|
||||||
secrets:
|
secrets:
|
||||||
- test_pass_one
|
- test_pass_one
|
||||||
- test_pass_two
|
- test_pass_two
|
||||||
|
|||||||
@@ -61,6 +61,16 @@ teardown(){
|
|||||||
run grep -q "TYPE=$TEST_RECIPE:0.3.0+1.21.0" \
|
run grep -q "TYPE=$TEST_RECIPE:0.3.0+1.21.0" \
|
||||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
|
# 0.3.0-only
|
||||||
|
run grep -q "NETWORK_WITH_COMMENT=BAZ" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
# latest-only
|
||||||
|
run grep -q "TIMEOUT=120" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_failure
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "ensure recipe is up-to-date" {
|
@test "ensure recipe is up-to-date" {
|
||||||
@@ -100,6 +110,16 @@ teardown(){
|
|||||||
run grep -q "TYPE=$TEST_RECIPE:${tagHash}" \
|
run grep -q "TYPE=$TEST_RECIPE:${tagHash}" \
|
||||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
|
# 0.3.0-only
|
||||||
|
run grep -q "NETWORK_WITH_COMMENT=BAZ" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
# latest-only
|
||||||
|
run grep -q "TIMEOUT=120" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_failure
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "does not overwrite existing env files" {
|
@test "does not overwrite existing env files" {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: nginx:1.29.0
|
image: nginx:1.31.2
|
||||||
networks:
|
networks:
|
||||||
- proxy
|
- proxy
|
||||||
deploy:
|
deploy:
|
||||||
|
|||||||
+3
-3
@@ -3,16 +3,16 @@ kind: pipeline
|
|||||||
name: coopcloud.tech/tagcmp
|
name: coopcloud.tech/tagcmp
|
||||||
steps:
|
steps:
|
||||||
- name: gofmt
|
- name: gofmt
|
||||||
image: golang:1.21
|
image: golang:1.26
|
||||||
commands:
|
commands:
|
||||||
- test -z "$(gofmt -l .)"
|
- test -z "$(gofmt -l .)"
|
||||||
|
|
||||||
- name: go build
|
- name: go build
|
||||||
image: golang:1.21
|
image: golang:1.26
|
||||||
commands:
|
commands:
|
||||||
- go build -v .
|
- go build -v .
|
||||||
|
|
||||||
- name: go test
|
- name: go test
|
||||||
image: golang:1.21
|
image: golang:1.26
|
||||||
commands:
|
commands:
|
||||||
- go test . -cover
|
- go test . -cover
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.20.8
|
ARG GO_VERSION=1.20.8
|
||||||
ARG ALPINE_VERSION=3.18
|
ARG ALPINE_VERSION=3.24
|
||||||
ARG XX_VERSION=1.2.1
|
ARG XX_VERSION=1.2.1
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
|||||||
+4
@@ -5,6 +5,10 @@ Billy implements an interface based on the `os` standard library, allowing to de
|
|||||||
|
|
||||||
Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project.
|
Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project.
|
||||||
|
|
||||||
|
## Version support
|
||||||
|
|
||||||
|
go-billy v5 is in maintenance mode. Users should upgrade to [go-billy v6](https://pkg.go.dev/github.com/go-git/go-billy/v6) where possible.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
+198
-10
@@ -3,19 +3,25 @@ package chroot
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
"github.com/go-git/go-billy/v5/helper/polyfill"
|
"github.com/go-git/go-billy/v5/helper/polyfill"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChrootHelper is a helper to implement billy.Chroot.
|
// ChrootHelper is a helper to implement billy.Chroot.
|
||||||
|
// It is not a security boundary, callers that need containment should use a
|
||||||
|
// filesystem implementation that enforces paths at the OS boundary instead.
|
||||||
type ChrootHelper struct {
|
type ChrootHelper struct {
|
||||||
underlying billy.Filesystem
|
underlying billy.Filesystem
|
||||||
base string
|
base string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxFollowedSymlinks = 8 // Aligns with POSIX_SYMLOOP_MAX
|
||||||
|
|
||||||
// New creates a new filesystem wrapping up the given 'fs'.
|
// New creates a new filesystem wrapping up the given 'fs'.
|
||||||
// The created filesystem has its base in the given ChrootHelperectory of the
|
// The created filesystem has its base in the given ChrootHelperectory of the
|
||||||
// underlying filesystem.
|
// underlying filesystem.
|
||||||
@@ -34,15 +40,184 @@ func (fs *ChrootHelper) underlyingPath(filename string) (string, error) {
|
|||||||
return fs.Join(fs.Root(), filename), nil
|
return fs.Join(fs.Root(), filename), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCrossBoundaries(path string) bool {
|
func (fs *ChrootHelper) followedPath(filename string, followFinal bool, op string) (string, error) {
|
||||||
path = filepath.ToSlash(path)
|
fullpath, err := fs.underlyingPath(filename)
|
||||||
path = filepath.Clean(path)
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
return strings.HasPrefix(path, ".."+string(filepath.Separator))
|
sl, ok := fs.underlying.(billy.Symlink)
|
||||||
|
if !ok {
|
||||||
|
return fullpath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rel, err := fs.relativeToRoot(fullpath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fullpath, err = fs.resolveFollowedPath(rel, followFinal, op, sl)
|
||||||
|
if errors.Is(err, billy.ErrNotSupported) {
|
||||||
|
return fs.underlyingPath(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullpath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *ChrootHelper) resolveFollowedPath(rel string, followFinal bool, op string, sl billy.Symlink) (string, error) {
|
||||||
|
if rel == "" {
|
||||||
|
return fs.resolveFollowedRoot(followFinal, op, sl)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := splitRelativePath(rel)
|
||||||
|
resolved := ""
|
||||||
|
followed := 0
|
||||||
|
|
||||||
|
for len(parts) > 0 {
|
||||||
|
part := parts[0]
|
||||||
|
parts = parts[1:]
|
||||||
|
|
||||||
|
currentRel := joinRelativePath(resolved, part)
|
||||||
|
currentPath := fs.Join(fs.Root(), currentRel)
|
||||||
|
if len(parts) == 0 && !followFinal {
|
||||||
|
return currentPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := sl.Lstat(currentPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fs.Join(fs.Root(), joinRelativePath(append([]string{currentRel}, parts...)...)), nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
resolved = currentRel
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
followed++
|
||||||
|
if followed > maxFollowedSymlinks {
|
||||||
|
return "", symlinkLoopError(op, currentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := sl.Readlink(currentPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetRel, err := fs.linkTargetRel(currentPath, target)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if targetRel == currentRel {
|
||||||
|
return "", symlinkLoopError(op, currentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(splitRelativePath(targetRel), parts...)
|
||||||
|
resolved = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.Join(fs.Root(), resolved), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func symlinkLoopError(op, path string) error {
|
||||||
|
return &os.PathError{Op: op, Path: path, Err: syscall.ELOOP}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *ChrootHelper) resolveFollowedRoot(followFinal bool, op string, sl billy.Symlink) (string, error) {
|
||||||
|
root := fs.Join(fs.Root(), "")
|
||||||
|
if !followFinal {
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := sl.Lstat(root)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := sl.Readlink(root)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetRel, err := fs.linkTargetRel(root, target)
|
||||||
|
if err != nil {
|
||||||
|
return root, err
|
||||||
|
}
|
||||||
|
if targetRel == "" {
|
||||||
|
return "", symlinkLoopError(op, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.resolveFollowedPath(targetRel, followFinal, op, sl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *ChrootHelper) relativeToRoot(filename string) (string, error) {
|
||||||
|
rel, err := filepath.Rel(filepath.Clean(fs.Root()), filepath.Clean(filename))
|
||||||
|
if err != nil || isCrossBoundaries(rel) {
|
||||||
|
return "", billy.ErrCrossedBoundary
|
||||||
|
}
|
||||||
|
|
||||||
|
if rel == "." {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return rel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *ChrootHelper) linkTargetRel(linkPath, target string) (string, error) {
|
||||||
|
target = filepath.FromSlash(target)
|
||||||
|
if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) {
|
||||||
|
return fs.relativeToRoot(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.relativeToRoot(fs.Join(filepath.Dir(linkPath), target))
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitRelativePath(filename string) []string {
|
||||||
|
filename = filepath.Clean(filename)
|
||||||
|
if filename == "" || filename == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(filepath.ToSlash(filename), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinRelativePath(elem ...string) string {
|
||||||
|
parts := make([]string, 0, len(elem))
|
||||||
|
for _, part := range elem {
|
||||||
|
if part == "" || part == "." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts = append(parts, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(parts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCreateExclusive(flag int) bool {
|
||||||
|
return flag&os.O_CREATE != 0 && flag&os.O_EXCL != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCrossBoundaries(name string) bool {
|
||||||
|
name = filepath.ToSlash(name)
|
||||||
|
name = strings.TrimLeft(name, "/")
|
||||||
|
name = path.Clean(name)
|
||||||
|
|
||||||
|
return name == ".." || strings.HasPrefix(name, "../")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *ChrootHelper) Create(filename string) (billy.File, error) {
|
func (fs *ChrootHelper) Create(filename string) (billy.File, error) {
|
||||||
fullpath, err := fs.underlyingPath(filename)
|
fullpath, err := fs.followedPath(filename, true, "create")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -56,7 +231,7 @@ func (fs *ChrootHelper) Create(filename string) (billy.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *ChrootHelper) Open(filename string) (billy.File, error) {
|
func (fs *ChrootHelper) Open(filename string) (billy.File, error) {
|
||||||
fullpath, err := fs.underlyingPath(filename)
|
fullpath, err := fs.followedPath(filename, true, "open")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -70,7 +245,7 @@ func (fs *ChrootHelper) Open(filename string) (billy.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) {
|
func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) {
|
||||||
fullpath, err := fs.underlyingPath(filename)
|
fullpath, err := fs.followedPath(filename, !isCreateExclusive(flag), "open")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -84,12 +259,16 @@ func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) {
|
func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) {
|
||||||
fullpath, err := fs.underlyingPath(filename)
|
fullpath, err := fs.followedPath(filename, true, "stat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs.underlying.Stat(fullpath)
|
fi, err := fs.underlying.Stat(fullpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fileInfo{FileInfo: fi, name: filepath.Base(filename)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *ChrootHelper) Rename(from, to string) error {
|
func (fs *ChrootHelper) Rename(from, to string) error {
|
||||||
@@ -135,7 +314,7 @@ func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) {
|
func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) {
|
||||||
fullpath, err := fs.underlyingPath(path)
|
fullpath, err := fs.followedPath(path, true, "readdir")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -241,6 +420,11 @@ type file struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fileInfo struct {
|
||||||
|
os.FileInfo
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
|
func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
|
||||||
filename = fs.Join(fs.Root(), filename)
|
filename = fs.Join(fs.Root(), filename)
|
||||||
filename, _ = filepath.Rel(fs.Root(), filename)
|
filename, _ = filepath.Rel(fs.Root(), filename)
|
||||||
@@ -254,3 +438,7 @@ func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
|
|||||||
func (f *file) Name() string {
|
func (f *file) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fi fileInfo) Name() string {
|
||||||
|
return fi.name
|
||||||
|
}
|
||||||
|
|||||||
+5
@@ -24,6 +24,9 @@ var Default = &ChrootOS{}
|
|||||||
// New returns a new OS filesystem.
|
// New returns a new OS filesystem.
|
||||||
// By default paths are deduplicated, but still enforced
|
// By default paths are deduplicated, but still enforced
|
||||||
// under baseDir. For more info refer to WithDeduplicatePath.
|
// under baseDir. For more info refer to WithDeduplicatePath.
|
||||||
|
//
|
||||||
|
// New returns ChrootOS by default for v5 compatibility. Users should prefer
|
||||||
|
// New with WithBoundOS.
|
||||||
func New(baseDir string, opts ...Option) billy.Filesystem {
|
func New(baseDir string, opts ...Option) billy.Filesystem {
|
||||||
o := &options{
|
o := &options{
|
||||||
deduplicatePath: true,
|
deduplicatePath: true,
|
||||||
@@ -47,6 +50,8 @@ func WithBoundOS() Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithChrootOS returns the option of using a Chroot filesystem OS.
|
// WithChrootOS returns the option of using a Chroot filesystem OS.
|
||||||
|
//
|
||||||
|
// Deprecated: use WithBoundOS instead.
|
||||||
func WithChrootOS() Option {
|
func WithChrootOS() Option {
|
||||||
return func(o *options) {
|
return func(o *options) {
|
||||||
o.Type = ChrootOSFS
|
o.Type = ChrootOSFS
|
||||||
|
|||||||
+85
-15
@@ -20,6 +20,7 @@
|
|||||||
package osfs
|
package osfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -29,6 +30,31 @@ import (
|
|||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrBaseDirCannotBeRemoved is returned when removing the BoundOS base dir.
|
||||||
|
ErrBaseDirCannotBeRemoved = errors.New("base dir cannot be removed")
|
||||||
|
|
||||||
|
// ErrBaseDirCannotBeRenamed is returned when renaming the BoundOS base dir.
|
||||||
|
ErrBaseDirCannotBeRenamed = errors.New("base dir cannot be renamed")
|
||||||
|
|
||||||
|
dotPrefixes = dotPathPrefixes()
|
||||||
|
dotSeparators = dotPathSeparators()
|
||||||
|
)
|
||||||
|
|
||||||
|
func dotPathPrefixes() []string {
|
||||||
|
if filepath.Separator == '\\' {
|
||||||
|
return []string{"./", ".\\"}
|
||||||
|
}
|
||||||
|
return []string{"./"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dotPathSeparators() string {
|
||||||
|
if filepath.Separator == '\\' {
|
||||||
|
return `/\`
|
||||||
|
}
|
||||||
|
return `/`
|
||||||
|
}
|
||||||
|
|
||||||
// BoundOS is a fs implementation based on the OS filesystem which is bound to
|
// BoundOS is a fs implementation based on the OS filesystem which is bound to
|
||||||
// a base dir.
|
// a base dir.
|
||||||
// Prefer this fs implementation over ChrootOS.
|
// Prefer this fs implementation over ChrootOS.
|
||||||
@@ -54,6 +80,7 @@ func (fs *BoundOS) Create(filename string) (billy.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
|
func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
|
||||||
|
filename = fs.expandDot(filename)
|
||||||
fn, err := fs.abs(filename)
|
fn, err := fs.abs(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -62,6 +89,7 @@ func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) {
|
func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) {
|
||||||
|
path = fs.expandDot(path)
|
||||||
dir, err := fs.abs(path)
|
dir, err := fs.abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -71,6 +99,12 @@ func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) Rename(from, to string) error {
|
func (fs *BoundOS) Rename(from, to string) error {
|
||||||
|
if fs.isBaseDir(from) {
|
||||||
|
return ErrBaseDirCannotBeRenamed
|
||||||
|
}
|
||||||
|
from = fs.expandDot(from)
|
||||||
|
to = fs.expandDot(to)
|
||||||
|
|
||||||
f, err := fs.abs(from)
|
f, err := fs.abs(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -89,6 +123,7 @@ func (fs *BoundOS) Rename(from, to string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error {
|
func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
path = fs.expandDot(path)
|
||||||
dir, err := fs.abs(path)
|
dir, err := fs.abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -101,6 +136,7 @@ func (fs *BoundOS) Open(filename string) (billy.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) {
|
func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) {
|
||||||
|
filename = fs.expandDot(filename)
|
||||||
filename, err := fs.abs(filename)
|
filename, err := fs.abs(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -109,6 +145,11 @@ func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) Remove(filename string) error {
|
func (fs *BoundOS) Remove(filename string) error {
|
||||||
|
if fs.isBaseDir(filename) {
|
||||||
|
return ErrBaseDirCannotBeRemoved
|
||||||
|
}
|
||||||
|
filename = fs.expandDot(filename)
|
||||||
|
|
||||||
fn, err := fs.abs(filename)
|
fn, err := fs.abs(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -122,6 +163,7 @@ func (fs *BoundOS) Remove(filename string) error {
|
|||||||
func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) {
|
func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) {
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
var err error
|
var err error
|
||||||
|
dir = fs.expandDot(dir)
|
||||||
dir, err = fs.abs(dir)
|
dir, err = fs.abs(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -144,6 +186,11 @@ func (fs *BoundOS) Join(elem ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) RemoveAll(path string) error {
|
func (fs *BoundOS) RemoveAll(path string) error {
|
||||||
|
if fs.isBaseDir(path) {
|
||||||
|
return ErrBaseDirCannotBeRemoved
|
||||||
|
}
|
||||||
|
path = fs.expandDot(path)
|
||||||
|
|
||||||
dir, err := fs.abs(path)
|
dir, err := fs.abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -152,6 +199,7 @@ func (fs *BoundOS) RemoveAll(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) Symlink(target, link string) error {
|
func (fs *BoundOS) Symlink(target, link string) error {
|
||||||
|
link = fs.expandDot(link)
|
||||||
ln, err := fs.abs(link)
|
ln, err := fs.abs(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -164,6 +212,7 @@ func (fs *BoundOS) Symlink(target, link string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) {
|
func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) {
|
||||||
|
filename = fs.expandDot(filename)
|
||||||
filename = filepath.Clean(filename)
|
filename = filepath.Clean(filename)
|
||||||
if !filepath.IsAbs(filename) {
|
if !filepath.IsAbs(filename) {
|
||||||
filename = filepath.Join(fs.baseDir, filename)
|
filename = filepath.Join(fs.baseDir, filename)
|
||||||
@@ -175,6 +224,7 @@ func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) Readlink(link string) (string, error) {
|
func (fs *BoundOS) Readlink(link string) (string, error) {
|
||||||
|
link = fs.expandDot(link)
|
||||||
if !filepath.IsAbs(link) {
|
if !filepath.IsAbs(link) {
|
||||||
link = filepath.Clean(filepath.Join(fs.baseDir, link))
|
link = filepath.Clean(filepath.Join(fs.baseDir, link))
|
||||||
}
|
}
|
||||||
@@ -185,6 +235,7 @@ func (fs *BoundOS) Readlink(link string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fs *BoundOS) Chmod(path string, mode os.FileMode) error {
|
func (fs *BoundOS) Chmod(path string, mode os.FileMode) error {
|
||||||
|
path = fs.expandDot(path)
|
||||||
abspath, err := fs.abs(path)
|
abspath, err := fs.abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -199,7 +250,7 @@ func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return New(joined), nil
|
return New(joined, WithBoundOS()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root returns the current base dir of the billy.Filesystem.
|
// Root returns the current base dir of the billy.Filesystem.
|
||||||
@@ -220,6 +271,37 @@ func (fs *BoundOS) createDir(fullpath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *BoundOS) expandDot(path string) string {
|
||||||
|
if path == "." {
|
||||||
|
return fs.baseDir
|
||||||
|
}
|
||||||
|
for _, prefix := range dotPrefixes {
|
||||||
|
if strings.HasPrefix(path, prefix) {
|
||||||
|
path = strings.TrimLeft(strings.TrimPrefix(path, prefix), dotSeparators)
|
||||||
|
if path == "" {
|
||||||
|
return fs.baseDir
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *BoundOS) isBaseDir(path string) bool {
|
||||||
|
if path == "" || filepath.Clean(path) == "." {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
path = fs.expandDot(path)
|
||||||
|
if filepath.Clean(path) == filepath.Clean(fs.baseDir) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
abspath, err := fs.abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return filepath.Clean(abspath) == filepath.Clean(fs.baseDir)
|
||||||
|
}
|
||||||
|
|
||||||
// abs transforms filename to an absolute path, taking into account the base dir.
|
// abs transforms filename to an absolute path, taking into account the base dir.
|
||||||
// Relative paths won't be allowed to ascend the base dir, so `../file` will become
|
// Relative paths won't be allowed to ascend the base dir, so `../file` will become
|
||||||
// `/working-dir/file`.
|
// `/working-dir/file`.
|
||||||
@@ -233,7 +315,7 @@ func (fs *BoundOS) abs(filename string) (string, error) {
|
|||||||
|
|
||||||
path, err := securejoin.SecureJoin(fs.baseDir, filename)
|
path, err := securejoin.SecureJoin(fs.baseDir, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.deduplicatePath {
|
if fs.deduplicatePath {
|
||||||
@@ -246,24 +328,12 @@ func (fs *BoundOS) abs(filename string) (string, error) {
|
|||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// insideBaseDir checks whether filename is located within
|
|
||||||
// the fs.baseDir.
|
|
||||||
func (fs *BoundOS) insideBaseDir(filename string) (bool, error) {
|
|
||||||
if filename == fs.baseDir {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(filename, fs.baseDir+string(filepath.Separator)) {
|
|
||||||
return false, fmt.Errorf("path outside base dir")
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// insideBaseDirEval checks whether filename is contained within
|
// insideBaseDirEval checks whether filename is contained within
|
||||||
// a dir that is within the fs.baseDir, by first evaluating any symlinks
|
// a dir that is within the fs.baseDir, by first evaluating any symlinks
|
||||||
// that either filename or fs.baseDir may contain.
|
// that either filename or fs.baseDir may contain.
|
||||||
func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) {
|
func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) {
|
||||||
// "/" contains all others.
|
// "/" contains all others.
|
||||||
if fs.baseDir == "/" {
|
if fs.baseDir == "/" || fs.baseDir == filename {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
dir, err := filepath.EvalSymlinks(filepath.Dir(filename))
|
dir, err := filepath.EvalSymlinks(filepath.Dir(filename))
|
||||||
|
|||||||
+10
@@ -14,6 +14,8 @@ import (
|
|||||||
// ChrootOS is a legacy filesystem based on a "soft chroot" of the os filesystem.
|
// ChrootOS is a legacy filesystem based on a "soft chroot" of the os filesystem.
|
||||||
// Although this is still the default os filesystem, consider using BoundOS instead.
|
// Although this is still the default os filesystem, consider using BoundOS instead.
|
||||||
//
|
//
|
||||||
|
// Deprecated: use New with WithBoundOS instead.
|
||||||
|
//
|
||||||
// Behaviours of note:
|
// Behaviours of note:
|
||||||
// 1. A "soft chroot" translates the base dir to "/" for the purposes of the
|
// 1. A "soft chroot" translates the base dir to "/" for the purposes of the
|
||||||
// fs abstraction.
|
// fs abstraction.
|
||||||
@@ -24,6 +26,14 @@ import (
|
|||||||
type ChrootOS struct{}
|
type ChrootOS struct{}
|
||||||
|
|
||||||
func newChrootOS(baseDir string) billy.Filesystem {
|
func newChrootOS(baseDir string) billy.Filesystem {
|
||||||
|
if baseDir != "" {
|
||||||
|
resolved, err := filepath.EvalSymlinks(baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return chroot.New(&ChrootOS{}, baseDir)
|
||||||
|
}
|
||||||
|
baseDir = resolved
|
||||||
|
}
|
||||||
|
|
||||||
return chroot.New(&ChrootOS{}, baseDir)
|
return chroot.New(&ChrootOS{}, baseDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-24
@@ -16,8 +16,6 @@ import (
|
|||||||
// can but returns the first error it encounters. If the path does not exist,
|
// can but returns the first error it encounters. If the path does not exist,
|
||||||
// RemoveAll returns nil (no error).
|
// RemoveAll returns nil (no error).
|
||||||
func RemoveAll(fs billy.Basic, path string) error {
|
func RemoveAll(fs billy.Basic, path string) error {
|
||||||
fs, path = getUnderlyingAndPath(fs, path)
|
|
||||||
|
|
||||||
if r, ok := fs.(removerAll); ok {
|
if r, ok := fs.(removerAll); ok {
|
||||||
return r.RemoveAll(path)
|
return r.RemoveAll(path)
|
||||||
}
|
}
|
||||||
@@ -39,7 +37,7 @@ func removeAll(fs billy.Basic, path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, is this a directory we need to recurse into?
|
// Otherwise, is this a directory we need to recurse into?
|
||||||
dir, serr := fs.Stat(path)
|
dir, serr := lstat(fs, path)
|
||||||
if serr != nil {
|
if serr != nil {
|
||||||
if errors.Is(serr, os.ErrNotExist) {
|
if errors.Is(serr, os.ErrNotExist) {
|
||||||
return nil
|
return nil
|
||||||
@@ -48,8 +46,8 @@ func removeAll(fs billy.Basic, path string) error {
|
|||||||
return serr
|
return serr
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dir.IsDir() {
|
if dir.Mode()&os.ModeSymlink != 0 || !dir.IsDir() {
|
||||||
// Not a directory; return the error from Remove.
|
// Not a directory we should recurse into; return the error from Remove.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +60,7 @@ func removeAll(fs billy.Basic, path string) error {
|
|||||||
fis, err := dirfs.ReadDir(path)
|
fis, err := dirfs.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
// Race. It was deleted between the Lstat and Open.
|
// Race. It was deleted between the Lstat and ReadDir.
|
||||||
// Return nil per RemoveAll's docs.
|
// Return nil per RemoveAll's docs.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -91,7 +89,18 @@ func removeAll(fs billy.Basic, path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func lstat(filesystem billy.Basic, path string) (os.FileInfo, error) {
|
||||||
|
if sl, ok := filesystem.(billy.Symlink); ok {
|
||||||
|
// Avoid following a symlink substituted after the initial Remove fails.
|
||||||
|
fi, err := sl.Lstat(path)
|
||||||
|
if err == nil || !errors.Is(err, billy.ErrNotSupported) {
|
||||||
|
return fi, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filesystem.Stat(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFile writes data to a file named by filename in the given filesystem.
|
// WriteFile writes data to a file named by filename in the given filesystem.
|
||||||
@@ -123,8 +132,10 @@ func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) (
|
|||||||
// We generate random temporary file names so that there's a good
|
// We generate random temporary file names so that there's a good
|
||||||
// chance the file doesn't exist yet - keeps the number of tries in
|
// chance the file doesn't exist yet - keeps the number of tries in
|
||||||
// TempFile to a minimum.
|
// TempFile to a minimum.
|
||||||
var rand uint32
|
var (
|
||||||
var randmu sync.Mutex
|
rand uint32
|
||||||
|
randmu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
func reseed() uint32 {
|
func reseed() uint32 {
|
||||||
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
||||||
@@ -220,22 +231,6 @@ func getTempDir(fs billy.Basic) string {
|
|||||||
return ".tmp"
|
return ".tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
type underlying interface {
|
|
||||||
Underlying() billy.Basic
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) {
|
|
||||||
u, ok := fs.(underlying)
|
|
||||||
if !ok {
|
|
||||||
return fs, path
|
|
||||||
}
|
|
||||||
if ch, ok := fs.(billy.Chroot); ok {
|
|
||||||
path = fs.Join(ch.Root(), path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.Underlying(), path
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFile reads the named file and returns the contents from the given filesystem.
|
// ReadFile reads the named file and returns the contents from the given filesystem.
|
||||||
// A successful call returns err == nil, not err == EOF.
|
// A successful call returns err == nil, not err == EOF.
|
||||||
// Because ReadFile reads the whole file, it does not treat an EOF from Read
|
// Because ReadFile reads the whole file, it does not treat an EOF from Read
|
||||||
|
|||||||
+30
-1
@@ -61,6 +61,16 @@ type Config struct {
|
|||||||
CommentChar string
|
CommentChar string
|
||||||
// RepositoryFormatVersion identifies the repository format and layout version.
|
// RepositoryFormatVersion identifies the repository format and layout version.
|
||||||
RepositoryFormatVersion format.RepositoryFormatVersion
|
RepositoryFormatVersion format.RepositoryFormatVersion
|
||||||
|
// ProtectNTFS controls whether NTFS-specific path protections are
|
||||||
|
// applied (e.g. rejecting .git trailing spaces/periods, alternate
|
||||||
|
// data streams, 8.3 short names). When unset, defaults to true on
|
||||||
|
// Windows.
|
||||||
|
ProtectNTFS OptBool
|
||||||
|
// ProtectHFS controls whether HFS+-specific path protections are
|
||||||
|
// applied (e.g. rejecting .git with Unicode zero-width or
|
||||||
|
// directional characters that HFS+ would normalize away).
|
||||||
|
// When unset, defaults to true on macOS.
|
||||||
|
ProtectHFS OptBool
|
||||||
}
|
}
|
||||||
|
|
||||||
User struct {
|
User struct {
|
||||||
@@ -266,6 +276,8 @@ const (
|
|||||||
repositoryFormatVersionKey = "repositoryformatversion"
|
repositoryFormatVersionKey = "repositoryformatversion"
|
||||||
objectFormat = "objectformat"
|
objectFormat = "objectformat"
|
||||||
mirrorKey = "mirror"
|
mirrorKey = "mirror"
|
||||||
|
protectNTFSKey = "protectNTFS"
|
||||||
|
protectHFSKey = "protectHFS"
|
||||||
|
|
||||||
// DefaultPackWindow holds the number of previous objects used to
|
// DefaultPackWindow holds the number of previous objects used to
|
||||||
// generate deltas. The value 10 is the same used by git command.
|
// generate deltas. The value 10 is the same used by git command.
|
||||||
@@ -309,6 +321,14 @@ func (c *Config) unmarshalCore() {
|
|||||||
|
|
||||||
c.Core.Worktree = s.Options.Get(worktreeKey)
|
c.Core.Worktree = s.Options.Get(worktreeKey)
|
||||||
c.Core.CommentChar = s.Options.Get(commentCharKey)
|
c.Core.CommentChar = s.Options.Get(commentCharKey)
|
||||||
|
|
||||||
|
if parsed := parseConfigBool(s.Options.Get(protectNTFSKey)); parsed.IsSet() {
|
||||||
|
c.Core.ProtectNTFS = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsed := parseConfigBool(s.Options.Get(protectHFSKey)); parsed.IsSet() {
|
||||||
|
c.Core.ProtectHFS = parsed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) unmarshalUser() {
|
func (c *Config) unmarshalUser() {
|
||||||
@@ -379,7 +399,8 @@ func unmarshalSubmodules(fc *format.Config, submodules map[string]*Submodule) {
|
|||||||
m := &Submodule{}
|
m := &Submodule{}
|
||||||
m.unmarshal(sub)
|
m.unmarshal(sub)
|
||||||
|
|
||||||
if m.Validate() == ErrModuleBadPath {
|
if err := m.Validate(); errors.Is(err, ErrModuleBadPath) ||
|
||||||
|
errors.Is(err, ErrModuleBadName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +457,14 @@ func (c *Config) marshalCore() {
|
|||||||
if c.Core.Worktree != "" {
|
if c.Core.Worktree != "" {
|
||||||
s.SetOption(worktreeKey, c.Core.Worktree)
|
s.SetOption(worktreeKey, c.Core.Worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Core.ProtectNTFS.IsSet() {
|
||||||
|
s.SetOption(protectNTFSKey, c.Core.ProtectNTFS.FormatBool())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Core.ProtectHFS.IsSet() {
|
||||||
|
s.SetOption(protectHFSKey, c.Core.ProtectHFS.FormatBool())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) marshalExtensions() {
|
func (c *Config) marshalExtensions() {
|
||||||
|
|||||||
+52
@@ -3,8 +3,11 @@ package config
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/internal/pathutil"
|
||||||
format "github.com/go-git/go-git/v5/plumbing/format/config"
|
format "github.com/go-git/go-git/v5/plumbing/format/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,6 +15,7 @@ var (
|
|||||||
ErrModuleEmptyURL = errors.New("module config: empty URL")
|
ErrModuleEmptyURL = errors.New("module config: empty URL")
|
||||||
ErrModuleEmptyPath = errors.New("module config: empty path")
|
ErrModuleEmptyPath = errors.New("module config: empty path")
|
||||||
ErrModuleBadPath = errors.New("submodule has an invalid path")
|
ErrModuleBadPath = errors.New("submodule has an invalid path")
|
||||||
|
ErrModuleBadName = errors.New("ignoring suspicious submodule name")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -94,6 +98,10 @@ type Submodule struct {
|
|||||||
|
|
||||||
// Validate validates the fields and sets the default values.
|
// Validate validates the fields and sets the default values.
|
||||||
func (m *Submodule) Validate() error {
|
func (m *Submodule) Validate() error {
|
||||||
|
if err := validSubmoduleName(m.Name); err != nil {
|
||||||
|
return fmt.Errorf("%w: %q", ErrModuleBadName, m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
if m.Path == "" {
|
if m.Path == "" {
|
||||||
return ErrModuleEmptyPath
|
return ErrModuleEmptyPath
|
||||||
}
|
}
|
||||||
@@ -109,6 +117,50 @@ func (m *Submodule) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validSubmoduleName mirrors canonical Git's check_submodule_name in
|
||||||
|
// submodule-config.c [1]: reject empty names and any name with a ".."
|
||||||
|
// path component, using both '/' and '\\' as separators so the rule
|
||||||
|
// is consistent across platforms. The component check is delegated to
|
||||||
|
// `pathutil.IsHFSDot` and `pathutil.IsNTFSDot` with `.` as the needle,
|
||||||
|
// which both cover the bare ".." case and reject components that
|
||||||
|
// resolve to ".." after HFS+ Unicode normalisation (ignored code
|
||||||
|
// points, e.g. `.<U+200C>.`) or NTFS trailing-space/dot/ADS
|
||||||
|
// canonicalisation (e.g. `.. `, `..::$INDEX_ALLOCATION`).
|
||||||
|
// `.gitmodules` is attacker-controlled by definition, so both checks
|
||||||
|
// run unconditionally regardless of host OS.
|
||||||
|
//
|
||||||
|
// The additional checks (bare ".", NUL byte, leading or trailing
|
||||||
|
// separator, drive-letter prefix) close go-git-specific edge cases
|
||||||
|
// the canonical loop does not exercise: canonical Git treats names
|
||||||
|
// as opaque C strings, while Go strings carry NULs through and the
|
||||||
|
// billy filesystem layer is path-aware in ways Git's working storage
|
||||||
|
// is not.
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/submodule-config.c#L214-L237
|
||||||
|
func validSubmoduleName(name string) error {
|
||||||
|
if name == "" || name == "." {
|
||||||
|
return ErrModuleBadName
|
||||||
|
}
|
||||||
|
for _, seg := range strings.FieldsFunc(name, isPathSep) {
|
||||||
|
if pathutil.IsHFSDot(seg, ".") || pathutil.IsNTFSDot(seg, ".", "") {
|
||||||
|
return ErrModuleBadName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// go-git-specific defensive checks beyond canonical Git.
|
||||||
|
if strings.ContainsRune(name, 0) {
|
||||||
|
return ErrModuleBadName
|
||||||
|
}
|
||||||
|
if isPathSep(rune(name[0])) || isPathSep(rune(name[len(name)-1])) {
|
||||||
|
return ErrModuleBadName
|
||||||
|
}
|
||||||
|
if len(name) >= 2 && name[1] == ':' {
|
||||||
|
return ErrModuleBadName
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPathSep(r rune) bool { return r == '/' || r == '\\' }
|
||||||
|
|
||||||
func (m *Submodule) unmarshal(s *format.Subsection) {
|
func (m *Submodule) unmarshal(s *format.Subsection) {
|
||||||
m.raw = s
|
m.raw = s
|
||||||
|
|
||||||
|
|||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OptBool is a tri-state boolean: unset, explicitly false, or explicitly true.
|
||||||
|
// Its zero value (OptBoolUnset) means the setting was not specified, which
|
||||||
|
// allows merge logic based on reflect.Value.IsZero to skip unset fields while
|
||||||
|
// still letting an explicit "false" override a previously set "true".
|
||||||
|
type OptBool byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// OptBoolUnset indicates the setting was not specified.
|
||||||
|
OptBoolUnset OptBool = iota
|
||||||
|
// OptBoolFalse indicates the setting was explicitly set to false.
|
||||||
|
OptBoolFalse
|
||||||
|
// OptBoolTrue indicates the setting was explicitly set to true.
|
||||||
|
OptBoolTrue
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewOptBool converts a plain bool into an OptBool.
|
||||||
|
func NewOptBool(v bool) OptBool {
|
||||||
|
if v {
|
||||||
|
return OptBoolTrue
|
||||||
|
}
|
||||||
|
return OptBoolFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTrue returns whether the value is explicitly true.
|
||||||
|
func (o OptBool) IsTrue() bool { return o == OptBoolTrue }
|
||||||
|
|
||||||
|
// IsSet returns whether the value was explicitly specified (true or false).
|
||||||
|
func (o OptBool) IsSet() bool { return o != OptBoolUnset }
|
||||||
|
|
||||||
|
func (o OptBool) String() string {
|
||||||
|
switch o {
|
||||||
|
case OptBoolTrue:
|
||||||
|
return "true"
|
||||||
|
case OptBoolFalse:
|
||||||
|
return "false"
|
||||||
|
default:
|
||||||
|
return "unset"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatBool returns the strconv-formatted value. Only meaningful when IsSet.
|
||||||
|
func (o OptBool) FormatBool() string {
|
||||||
|
return strconv.FormatBool(o.IsTrue())
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseConfigBool mirrors upstream Git's git_parse_maybe_bool: it
|
||||||
|
// accepts true/yes/on (→ OptBoolTrue) and false/no/off (→
|
||||||
|
// OptBoolFalse) case-insensitively, plus any decimal integer (zero
|
||||||
|
// → OptBoolFalse, non-zero → OptBoolTrue). Empty or otherwise
|
||||||
|
// unrecognised values return OptBoolUnset, leaving the caller's
|
||||||
|
// platform default in place. The empty-string handling is the only
|
||||||
|
// intentional divergence from upstream, which returns false for
|
||||||
|
// empty: in our unmarshalCore caller, an empty value means the key
|
||||||
|
// is unset and the platform default should apply.
|
||||||
|
//
|
||||||
|
// Reference: upstream Git git_parse_maybe_bool_text at parse.c
|
||||||
|
// L157-L173 and git_parse_maybe_bool at parse.c L174-L182 in tag
|
||||||
|
// v2.54.0[1].
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/parse.c#L157-L182
|
||||||
|
func parseConfigBool(v string) OptBool {
|
||||||
|
switch strings.ToLower(v) {
|
||||||
|
case "true", "yes", "on":
|
||||||
|
return OptBoolTrue
|
||||||
|
case "false", "no", "off":
|
||||||
|
return OptBoolFalse
|
||||||
|
}
|
||||||
|
if i, err := strconv.Atoi(v); err == nil {
|
||||||
|
if i != 0 {
|
||||||
|
return OptBoolTrue
|
||||||
|
}
|
||||||
|
return OptBoolFalse
|
||||||
|
}
|
||||||
|
return OptBoolUnset
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package pathutil
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// IsDotGitName reports whether name is `.git` or its 8.3 NTFS short
|
||||||
|
// alias `git~1`, case-insensitively. Both are forbidden as path
|
||||||
|
// components (and as submodule names) because they refer to the
|
||||||
|
// repository's own metadata directory.
|
||||||
|
//
|
||||||
|
// File names that do not conform to the 8.3 format (up to eight
|
||||||
|
// characters for the basename, three for the file extension) are
|
||||||
|
// associated with a so-called "short name" on NTFS — at least on
|
||||||
|
// the `C:` drive by default — which means that `git~1/` is a valid
|
||||||
|
// way to refer to `.git/`.
|
||||||
|
func IsDotGitName(name string) bool {
|
||||||
|
switch strings.ToLower(name) {
|
||||||
|
case ".git", "git~1":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
package pathutil
|
||||||
|
|
||||||
|
import "unicode"
|
||||||
|
|
||||||
|
// hfsIgnoredCodepoints contains Unicode code points that HFS+ ignores
|
||||||
|
// during path normalization. A path component containing these
|
||||||
|
// characters between the bytes of ".git" (or ".gitmodules", etc.)
|
||||||
|
// will be treated as that name by HFS+, so they have to be filtered
|
||||||
|
// out before comparison.
|
||||||
|
//
|
||||||
|
// See upstream Git utf8.c next_hfs_char in tag v2.54.0[1].
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/utf8.c#L703-L740
|
||||||
|
var hfsIgnoredCodepoints = map[rune]struct{}{
|
||||||
|
0x200c: {}, // ZERO WIDTH NON-JOINER
|
||||||
|
0x200d: {}, // ZERO WIDTH JOINER
|
||||||
|
0x200e: {}, // LEFT-TO-RIGHT MARK
|
||||||
|
0x200f: {}, // RIGHT-TO-LEFT MARK
|
||||||
|
0x202a: {}, // LEFT-TO-RIGHT EMBEDDING
|
||||||
|
0x202b: {}, // RIGHT-TO-LEFT EMBEDDING
|
||||||
|
0x202c: {}, // POP DIRECTIONAL FORMATTING
|
||||||
|
0x202d: {}, // LEFT-TO-RIGHT OVERRIDE
|
||||||
|
0x202e: {}, // RIGHT-TO-LEFT OVERRIDE
|
||||||
|
0x206a: {}, // INHIBIT SYMMETRIC SWAPPING
|
||||||
|
0x206b: {}, // ACTIVATE SYMMETRIC SWAPPING
|
||||||
|
0x206c: {}, // INHIBIT ARABIC FORM SHAPING
|
||||||
|
0x206d: {}, // ACTIVATE ARABIC FORM SHAPING
|
||||||
|
0x206e: {}, // NATIONAL DIGIT SHAPES
|
||||||
|
0x206f: {}, // NOMINAL DIGIT SHAPES
|
||||||
|
0xfeff: {}, // ZERO WIDTH NO-BREAK SPACE
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHFSDot reports whether part would be treated as ".<needle>" on an
|
||||||
|
// HFS+ filesystem after stripping ignored Unicode code points and
|
||||||
|
// folding ASCII to lower case. The needle is the lowercase ASCII
|
||||||
|
// suffix without the leading dot (e.g. "git", "gitmodules"). It
|
||||||
|
// mirrors upstream Git's is_hfs_dot_generic and is the building
|
||||||
|
// block of IsHFSDotGit / IsHFSDotGitmodules.
|
||||||
|
//
|
||||||
|
// Reference: upstream Git utf8.c is_hfs_dot_generic at L741-L774 and
|
||||||
|
// the dotgit family at L784-L809 in tag v2.54.0[1].
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/utf8.c#L741-L809
|
||||||
|
func IsHFSDot(part, needle string) bool {
|
||||||
|
runes := []rune(part)
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
// skip ignored code points, then expect '.'
|
||||||
|
for i < len(runes) {
|
||||||
|
if _, ok := hfsIgnoredCodepoints[runes[i]]; !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(runes) || runes[i] != '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// match needle case-insensitively, skipping ignored code points
|
||||||
|
for _, expected := range needle {
|
||||||
|
for i < len(runes) {
|
||||||
|
if _, ok := hfsIgnoredCodepoints[runes[i]]; !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(runes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r := runes[i]
|
||||||
|
if r > 127 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if unicode.ToLower(r) != expected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip trailing ignored code points
|
||||||
|
for i < len(runes) {
|
||||||
|
if _, ok := hfsIgnoredCodepoints[runes[i]]; !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be at end of component
|
||||||
|
return i == len(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHFSDotGit reports whether part is an HFS+ equivalent of ".git".
|
||||||
|
func IsHFSDotGit(part string) bool { return IsHFSDot(part, "git") }
|
||||||
|
|
||||||
|
// IsHFSDotGitmodules reports whether part is an HFS+ equivalent of
|
||||||
|
// ".gitmodules", catching attempts to plant the file via Unicode
|
||||||
|
// code points that HFS+ would strip during normalisation.
|
||||||
|
func IsHFSDotGitmodules(part string) bool { return IsHFSDot(part, "gitmodules") }
|
||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
package pathutil
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// IsNTFSDotGit ports upstream Git's is_ntfs_dotgit. It detects path
|
||||||
|
// components that NTFS would resolve to ".git": the canonical name
|
||||||
|
// itself and its 8.3 short-name alias "git~1", each followed by any
|
||||||
|
// number of trailing spaces or periods (which NTFS silently trims)
|
||||||
|
// and an optional Alternate Data Stream suffix (":<stream>"). The
|
||||||
|
// bare strings ".git" and "git~1" also match, mirroring upstream.
|
||||||
|
//
|
||||||
|
// Reference: upstream Git path.c is_ntfs_dotgit at L1415-L1449
|
||||||
|
// in tag v2.54.0[1].
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/path.c#L1415-L1449
|
||||||
|
func IsNTFSDotGit(part string) bool {
|
||||||
|
var i int
|
||||||
|
switch {
|
||||||
|
case len(part) >= 4 && part[0] == '.' &&
|
||||||
|
asciiToLower(part[1]) == 'g' &&
|
||||||
|
asciiToLower(part[2]) == 'i' &&
|
||||||
|
asciiToLower(part[3]) == 't':
|
||||||
|
i = 4
|
||||||
|
case len(part) >= 5 &&
|
||||||
|
asciiToLower(part[0]) == 'g' &&
|
||||||
|
asciiToLower(part[1]) == 'i' &&
|
||||||
|
asciiToLower(part[2]) == 't' &&
|
||||||
|
part[3] == '~' && part[4] == '1':
|
||||||
|
i = 5
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; i < len(part); i++ {
|
||||||
|
c := part[i]
|
||||||
|
if c == ':' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c != '.' && c != ' ' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowsValidPath reports whether part is a valid Windows / NTFS
|
||||||
|
// path component for the worktree filesystem abstraction. It rejects
|
||||||
|
// NTFS-disguised variants of `.git` and `git~1` (trailing spaces,
|
||||||
|
// periods, Alternate Data Streams) and Windows reserved device
|
||||||
|
// names. Bare `.git` and `git~1` are allowed at this layer; the
|
||||||
|
// caller decides whether they are permissible at the current path
|
||||||
|
// position.
|
||||||
|
func WindowsValidPath(part string) bool {
|
||||||
|
if IsNTFSDotGit(part) && !IsDotGitName(part) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !isWindowsReservedName(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
// windowsReservedNames lists the Windows reserved device names.
|
||||||
|
// A path component is reserved if its base name (ignoring trailing
|
||||||
|
// spaces, extensions, and NTFS Alternate Data Streams) matches one of
|
||||||
|
// these case-insensitively.
|
||||||
|
//
|
||||||
|
// See upstream Git compat/mingw.c is_valid_win32_path().
|
||||||
|
var windowsReservedNames = []string{
|
||||||
|
"CON", "PRN", "AUX", "NUL",
|
||||||
|
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||||
|
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||||
|
"CONIN$", "CONOUT$",
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWindowsReservedName(part string) bool {
|
||||||
|
for _, name := range windowsReservedNames {
|
||||||
|
if len(part) < len(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(part[:len(name)], name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Exact match or followed by space, dot, colon (ADS), or separator.
|
||||||
|
if len(part) == len(name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch part[len(name)] {
|
||||||
|
case ' ', '.', ':':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNTFSDot ports upstream Git's is_ntfs_dot_generic. It detects NTFS
|
||||||
|
// path-component variants of a dotfile name that attackers can use to
|
||||||
|
// bypass case-insensitive comparisons against the canonical name on
|
||||||
|
// Windows. The dotgit parameter is the lowercase name without the
|
||||||
|
// leading dot (e.g. "gitmodules"); shortnamePrefix is the canonical
|
||||||
|
// 6-character NTFS short-name prefix used as a fall-back match
|
||||||
|
// (e.g. "gi7eba" for ".gitmodules").
|
||||||
|
//
|
||||||
|
// Reference: upstream Git path.c is_ntfs_dot_generic at L1451-L1507
|
||||||
|
// in tag v2.54.0[1].
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/path.c#L1451-L1507
|
||||||
|
func IsNTFSDot(name, dotgit, shortnamePrefix string) bool {
|
||||||
|
// onlySpacesAndPeriods returns true when the suffix from start
|
||||||
|
// onwards consists only of trailing spaces and periods, possibly
|
||||||
|
// terminated by a NTFS Alternate Data Stream colon. Mirrors the
|
||||||
|
// only_spaces_and_periods label in upstream's is_ntfs_dot_generic.
|
||||||
|
onlySpacesAndPeriods := func(start int) bool {
|
||||||
|
for i := start; i < len(name); i++ {
|
||||||
|
c := name[i]
|
||||||
|
if c == ':' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c != ' ' && c != '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 1: ".<dotgit>" prefix + trailing spaces / periods / ADS.
|
||||||
|
if len(name) >= len(dotgit)+1 && name[0] == '.' &&
|
||||||
|
strings.EqualFold(name[1:1+len(dotgit)], dotgit) {
|
||||||
|
if onlySpacesAndPeriods(len(dotgit) + 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 2: standard NTFS short name <dotgit[:6]>~[1-4].
|
||||||
|
if len(dotgit) >= 6 && len(name) >= 8 &&
|
||||||
|
strings.EqualFold(name[:6], dotgit[:6]) &&
|
||||||
|
name[6] == '~' && name[7] >= '1' && name[7] <= '4' {
|
||||||
|
if onlySpacesAndPeriods(8) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 3: fall-back NTFS short name keyed by shortnamePrefix.
|
||||||
|
if len(shortnamePrefix) < 6 || len(name) < 8 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sawTilde := false
|
||||||
|
i := 0
|
||||||
|
for i < 8 {
|
||||||
|
c := name[i]
|
||||||
|
switch {
|
||||||
|
case sawTilde:
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case c == '~':
|
||||||
|
i++
|
||||||
|
if i >= len(name) || name[i] < '1' || name[i] > '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sawTilde = true
|
||||||
|
case i >= 6:
|
||||||
|
return false
|
||||||
|
case c&0x80 != 0:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
if asciiToLower(c) != shortnamePrefix[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return onlySpacesAndPeriods(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNTFSDotGitmodules reports whether part is an NTFS-equivalent of
|
||||||
|
// ".gitmodules" — the file name (or any of its variants that NTFS
|
||||||
|
// would resolve to it) that attackers can use to plant submodule
|
||||||
|
// configuration disguised as a symlink. The 6-character canonical
|
||||||
|
// short-name prefix "gi7eba" mirrors upstream Git's is_ntfs_dotgitmodules.
|
||||||
|
func IsNTFSDotGitmodules(part string) bool {
|
||||||
|
return IsNTFSDot(part, "gitmodules", "gi7eba")
|
||||||
|
}
|
||||||
|
|
||||||
|
func asciiToLower(c byte) byte {
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
return c + ('a' - 'A')
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
package pathutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidPath is returned by ValidTreePath when its argument is
|
||||||
|
// not a safe path to materialise into the worktree.
|
||||||
|
var ErrInvalidPath = fmt.Errorf("invalid path")
|
||||||
|
|
||||||
|
// ValidTreePath rejects path strings that, if materialised into a
|
||||||
|
// worktree, would let an attacker-controlled tree entry escape the
|
||||||
|
// worktree or rewrite repository metadata. It rejects:
|
||||||
|
//
|
||||||
|
// - control characters (< 0x20, 0x7f);
|
||||||
|
// - empty paths and "." / ".." components;
|
||||||
|
// - Windows volume name prefixes (e.g. C:);
|
||||||
|
// - .git, its 8.3 NTFS short-name git~1, plus their HFS+ and NTFS
|
||||||
|
// variants — at every position, not just the root.
|
||||||
|
//
|
||||||
|
// HFS+/NTFS variants of `.git` are always rejected at this layer
|
||||||
|
// regardless of runtime config: tree paths are canonical UTF-8 with
|
||||||
|
// no zero-width characters or NTFS short-name forms, so an entry
|
||||||
|
// that looks like a disguised `.git` is suspicious anywhere. Windows
|
||||||
|
// reserved device names (CON, NUL, etc.) are not policed here — they
|
||||||
|
// are legitimate filenames on non-Windows filesystems and upstream
|
||||||
|
// Git accepts them. The wrapper layer (validPath in package git)
|
||||||
|
// rejects them at materialisation time when core.protectNTFS is on.
|
||||||
|
//
|
||||||
|
// Mirrors upstream Git's verify_path_internal at read-cache.c#L987
|
||||||
|
// in tag v2.54.0[1] with protect_hfs / protect_ntfs treated as
|
||||||
|
// always-on for `.git`-disguise detection (tree paths are not
|
||||||
|
// application-supplied) and is_valid_win32_path left to the wrapper.
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/read-cache.c#L987
|
||||||
|
func ValidTreePath(p string) error {
|
||||||
|
for i := 0; i < len(p); i++ {
|
||||||
|
if p[i] < 0x20 || p[i] == 0x7f {
|
||||||
|
return fmt.Errorf("%w %q: contains control character", ErrInvalidPath, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.FieldsFunc(p, func(r rune) bool { return r == '\\' || r == '/' })
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return fmt.Errorf("%w: %q", ErrInvalidPath, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:.
|
||||||
|
if vol := filepath.VolumeName(p); vol != "" {
|
||||||
|
return fmt.Errorf("%w: %q", ErrInvalidPath, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
if part == "." || part == ".." {
|
||||||
|
return fmt.Errorf("%w %q: cannot use %q", ErrInvalidPath, p, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsDotGitName(part) || IsHFSDotGit(part) || IsNTFSDotGit(part) {
|
||||||
|
return fmt.Errorf("%w component: %q", ErrInvalidPath, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+35
-2
@@ -2,12 +2,14 @@ package url
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
isSchemeRegExp = regexp.MustCompile(`^[^:]+://`)
|
isSchemeRegExp = regexp.MustCompile(`^[^:]+://`)
|
||||||
|
|
||||||
// Ref: https://github.com/git/git/blob/master/Documentation/urls.txt#L37
|
// Ref: https://github.com/git/git/blob/v2.54.0/Documentation/urls.adoc#L41-L48
|
||||||
scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5}):)?(?P<path>[^\\].*)$`)
|
scpLikeUrlRegExp = regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>[^:\s]+):(?:(?P<port>[0-9]{1,5}):)?(?P<path>[^\\].*)$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +22,38 @@ func MatchesScheme(url string) bool {
|
|||||||
// MatchesScpLike returns true if the given string matches an SCP-like
|
// MatchesScpLike returns true if the given string matches an SCP-like
|
||||||
// format scheme.
|
// format scheme.
|
||||||
func MatchesScpLike(url string) bool {
|
func MatchesScpLike(url string) bool {
|
||||||
return scpLikeUrlRegExp.MatchString(url)
|
if !scpLikeUrlRegExp.MatchString(url) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Mirror canonical Git's url_is_local_not_ssh in connect.c[1] for
|
||||||
|
// the cases the regex above cannot disambiguate by itself: a URL
|
||||||
|
// is treated as a local path (not SCP-style SSH) when a `/`
|
||||||
|
// precedes the first `:` (e.g. `./relative:path`,
|
||||||
|
// `/abs/with:colon/file`), or — on Windows only — when it has a
|
||||||
|
// DOS drive prefix like `C:foo` where the host is a single
|
||||||
|
// ASCII letter.
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/connect.c#L710-L716
|
||||||
|
if before, _, _ := strings.Cut(url, ":"); strings.Contains(before, "/") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if runtime.GOOS == "windows" && hasDosDrivePrefix(url) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasDosDrivePrefix reports whether s begins with `<letter>:` (a
|
||||||
|
// Windows drive prefix such as `C:` or `c:`). Mirrors canonical Git's
|
||||||
|
// win32_has_dos_drive_prefix[1].
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/compat/win32/path-utils.c#L20-L29
|
||||||
|
func hasDosDrivePrefix(s string) bool {
|
||||||
|
if len(s) < 2 || s[1] != ':' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := s[0]
|
||||||
|
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindScpLikeComponents returns the user, host, port and path of the
|
// FindScpLikeComponents returns the user, host, port and path of the
|
||||||
|
|||||||
+159
-5
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/hash"
|
"github.com/go-git/go-git/v5/plumbing/hash"
|
||||||
"github.com/go-git/go-git/v5/utils/binary"
|
"github.com/go-git/go-git/v5/utils/binary"
|
||||||
@@ -25,35 +26,88 @@ const (
|
|||||||
objectIDLength = hash.Size
|
objectIDLength = hash.Size
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Byte sizes of the idx v2 layout elements, used by the size formula
|
||||||
|
// in [validateIdxV2Size]. See [gitformat-pack] for the canonical
|
||||||
|
// layout.
|
||||||
|
//
|
||||||
|
// [gitformat-pack]: https://git-scm.com/docs/gitformat-pack
|
||||||
|
const (
|
||||||
|
headerLen = 8 // magic + version
|
||||||
|
fanoutLen = fanout * 4 // uint32 per bucket
|
||||||
|
crc32Len = 4 // CRC32 per object
|
||||||
|
offset32Len = 4 // 32-bit offset per object
|
||||||
|
offset64Len = 8 // 64-bit overflow offset
|
||||||
|
trailerHashes = 2 // pack checksum + idx checksum, each hashsz
|
||||||
|
)
|
||||||
|
|
||||||
|
// statInput is the optional shape the [Decoder] probes for at the
|
||||||
|
// start of [Decoder.Decode] to learn the on-disk length of the idx
|
||||||
|
// blob, which it uses to validate the canonical-Git size formula
|
||||||
|
// before any allocations driven by the fanout table. Callers that
|
||||||
|
// pass an [*os.File] or a `billy.File` backed by an `*os.File`
|
||||||
|
// (the production call sites in `storage/filesystem`) satisfy it
|
||||||
|
// directly; arbitrary [io.Reader]s do not, and decode for them
|
||||||
|
// retains the pre-existing behaviour of erroring out at the
|
||||||
|
// truncated-payload boundary instead.
|
||||||
|
//
|
||||||
|
// The interface is intentionally unexported so the public
|
||||||
|
// [NewDecoder] signature stays compatible with v5.
|
||||||
|
type statInput interface {
|
||||||
|
Stat() (fs.FileInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Decoder reads and decodes idx files from an input stream.
|
// Decoder reads and decodes idx files from an input stream.
|
||||||
type Decoder struct {
|
type Decoder struct {
|
||||||
io.Reader
|
io.Reader
|
||||||
h hash.Hash
|
src io.Reader
|
||||||
|
h hash.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDecoder builds a new idx stream decoder, that reads from r.
|
// NewDecoder builds a new idx stream decoder, that reads from r.
|
||||||
func NewDecoder(r io.Reader) *Decoder {
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
h := hash.New(crypto.SHA1)
|
h := hash.New(crypto.SHA1)
|
||||||
tr := io.TeeReader(r, h)
|
tr := io.TeeReader(r, h)
|
||||||
return &Decoder{tr, h}
|
return &Decoder{tr, r, h}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode reads from the stream and decode the content into the MemoryIndex struct.
|
// Decode reads from the stream and decode the content into the MemoryIndex struct.
|
||||||
func (d *Decoder) Decode(idx *MemoryIndex) error {
|
func (d *Decoder) Decode(idx *MemoryIndex) error {
|
||||||
|
idxSize := int64(-1)
|
||||||
|
if in, ok := d.src.(statInput); ok {
|
||||||
|
fi, err := in.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: stat input: %w", ErrMalformedIdxFile, err)
|
||||||
|
}
|
||||||
|
idxSize = fi.Size()
|
||||||
|
}
|
||||||
|
|
||||||
if err := validateHeader(d); err != nil {
|
if err := validateHeader(d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
flow := []func(*MemoryIndex, io.Reader) error{
|
headerFlow := []func(*MemoryIndex, io.Reader) error{
|
||||||
readVersion,
|
readVersion,
|
||||||
readFanout,
|
readFanout,
|
||||||
|
}
|
||||||
|
for _, f := range headerFlow {
|
||||||
|
if err := f(idx, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if idxSize >= 0 {
|
||||||
|
if err := validateIdxV2Size(idx, idxSize); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyFlow := []func(*MemoryIndex, io.Reader) error{
|
||||||
readObjectNames,
|
readObjectNames,
|
||||||
readCRC32,
|
readCRC32,
|
||||||
readOffsets,
|
readOffsets,
|
||||||
readPackChecksum,
|
readPackChecksum,
|
||||||
}
|
}
|
||||||
|
for _, f := range bodyFlow {
|
||||||
for _, f := range flow {
|
|
||||||
if err := f(idx, d); err != nil {
|
if err := f(idx, d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -199,3 +253,103 @@ func readIdxChecksum(idx *MemoryIndex, r io.Reader) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateIdxV2Size enforces the size formula used by canonical Git
|
||||||
|
// load_idx for idx v2 files: the on-disk length must lie within
|
||||||
|
// [minSize, maxSize] where
|
||||||
|
//
|
||||||
|
// perObject = hashsz + crc32Len + offset32Len
|
||||||
|
// minSize = headerLen + fanoutLen + trailerHashes*hashsz + nr*perObject
|
||||||
|
// maxSize = minSize + (nr-1)*offset64Len when nr > 0
|
||||||
|
//
|
||||||
|
// with nr taken from the last fanout entry and hashsz fixed at
|
||||||
|
// [objectIDLength] (SHA-1 in v5). Multiplications use a self-checking
|
||||||
|
// overflow guard so inputs whose claimed object count overflows the
|
||||||
|
// formula are rejected rather than wrapping into a smaller value.
|
||||||
|
func validateIdxV2Size(idx *MemoryIndex, idxSize int64) error {
|
||||||
|
nr := int64(idx.Fanout[fanout-1])
|
||||||
|
hashsz := int64(objectIDLength)
|
||||||
|
|
||||||
|
minSize := minIdxV2Size(nr, hashsz)
|
||||||
|
maxSize := maxIdxV2Size(nr, hashsz)
|
||||||
|
if minSize < 0 || maxSize < 0 {
|
||||||
|
return fmt.Errorf("%w: object count %d is inconsistent with file size", ErrMalformedIdxFile, nr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if idxSize < minSize || idxSize > maxSize {
|
||||||
|
return fmt.Errorf("%w: file size %d is inconsistent with object count %d", ErrMalformedIdxFile, idxSize, nr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// minIdxV2Size returns the minimum on-disk size of an idx v2 file
|
||||||
|
// holding nr objects with the given hash size, mirroring the
|
||||||
|
// computation in canonical Git load_idx. Returns -1 when any
|
||||||
|
// intermediate multiplication or addition would overflow int64.
|
||||||
|
func minIdxV2Size(nr, hashsz int64) int64 {
|
||||||
|
perObject := hashsz + crc32Len + offset32Len
|
||||||
|
fixed := int64(headerLen+fanoutLen) + trailerHashes*hashsz
|
||||||
|
|
||||||
|
objects, ok := mulInt64(nr, perObject)
|
||||||
|
if !ok {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
sum, ok := addInt64(fixed, objects)
|
||||||
|
if !ok {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxIdxV2Size returns the maximum on-disk size of an idx v2 file
|
||||||
|
// holding nr objects with the given hash size, mirroring the
|
||||||
|
// computation in canonical Git load_idx. Returns -1 on overflow.
|
||||||
|
func maxIdxV2Size(nr, hashsz int64) int64 {
|
||||||
|
minSize := minIdxV2Size(nr, hashsz)
|
||||||
|
if minSize < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if nr == 0 {
|
||||||
|
return minSize
|
||||||
|
}
|
||||||
|
overflow, ok := mulInt64(nr-1, offset64Len)
|
||||||
|
if !ok {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
sum, ok := addInt64(minSize, overflow)
|
||||||
|
if !ok {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// mulInt64 returns a*b and whether the result fits in an int64 without
|
||||||
|
// overflow. Negative operands or overflow yield ok=false. The overflow
|
||||||
|
// check uses the standard self-inverse identity: a*b/b == a only when
|
||||||
|
// the multiplication did not wrap.
|
||||||
|
func mulInt64(a, b int64) (int64, bool) {
|
||||||
|
if a < 0 || b < 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if a == 0 || b == 0 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
c := a * b
|
||||||
|
if c/b != a {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return c, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// addInt64 returns a+b and whether the result fits in an int64 without
|
||||||
|
// overflow. Negative operands or overflow yield ok=false.
|
||||||
|
func addInt64(a, b int64) (int64, bool) {
|
||||||
|
if a < 0 || b < 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
c := a + b
|
||||||
|
if c < a {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return c, true
|
||||||
|
}
|
||||||
|
|||||||
+21
-8
@@ -2,6 +2,7 @@ package idxfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -126,7 +127,10 @@ func (idx *MemoryIndex) FindOffset(h plumbing.Hash) (int64, error) {
|
|||||||
return 0, plumbing.ErrObjectNotFound
|
return 0, plumbing.ErrObjectNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := idx.getOffset(k, i)
|
offset, err := idx.getOffset(k, i)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
// Save the offset for reverse lookup
|
// Save the offset for reverse lookup
|
||||||
idx.mu.Lock()
|
idx.mu.Lock()
|
||||||
@@ -141,17 +145,19 @@ func (idx *MemoryIndex) FindOffset(h plumbing.Hash) (int64, error) {
|
|||||||
|
|
||||||
const isO64Mask = uint64(1) << 31
|
const isO64Mask = uint64(1) << 31
|
||||||
|
|
||||||
func (idx *MemoryIndex) getOffset(firstLevel, secondLevel int) uint64 {
|
func (idx *MemoryIndex) getOffset(firstLevel, secondLevel int) (uint64, error) {
|
||||||
offset := secondLevel << 2
|
offset := secondLevel << 2
|
||||||
ofs := encbin.BigEndian.Uint32(idx.Offset32[firstLevel][offset : offset+4])
|
ofs := encbin.BigEndian.Uint32(idx.Offset32[firstLevel][offset : offset+4])
|
||||||
|
|
||||||
if (uint64(ofs) & isO64Mask) != 0 {
|
if (uint64(ofs) & isO64Mask) != 0 {
|
||||||
offset := 8 * (uint64(ofs) & ^isO64Mask)
|
offset := 8 * (uint64(ofs) & ^isO64Mask)
|
||||||
n := encbin.BigEndian.Uint64(idx.Offset64[offset : offset+8])
|
if l := uint64(len(idx.Offset64)); l < 8 || offset > l-8 {
|
||||||
return n
|
return 0, fmt.Errorf("%w: offset64 index out of range", ErrMalformedIdxFile)
|
||||||
|
}
|
||||||
|
return encbin.BigEndian.Uint64(idx.Offset64[offset : offset+8]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint64(ofs)
|
return uint64(ofs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindCRC32 implements the Index interface.
|
// FindCRC32 implements the Index interface.
|
||||||
@@ -209,8 +215,11 @@ func (idx *MemoryIndex) genOffsetHash() error {
|
|||||||
mappedFirstLevel := idx.FanoutMapping[firstLevel]
|
mappedFirstLevel := idx.FanoutMapping[firstLevel]
|
||||||
for secondLevel := uint32(0); i < fanoutValue; i++ {
|
for secondLevel := uint32(0); i < fanoutValue; i++ {
|
||||||
copy(hash[:], idx.Names[mappedFirstLevel][secondLevel*objectIDLength:])
|
copy(hash[:], idx.Names[mappedFirstLevel][secondLevel*objectIDLength:])
|
||||||
offset := int64(idx.getOffset(mappedFirstLevel, int(secondLevel)))
|
off, err := idx.getOffset(mappedFirstLevel, int(secondLevel))
|
||||||
offsetHash[offset] = hash
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
offsetHash[int64(off)] = hash
|
||||||
secondLevel++
|
secondLevel++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,7 +300,11 @@ func (i *idxfileEntryIter) Next() (*Entry, error) {
|
|||||||
mappedFirstLevel := i.idx.FanoutMapping[i.firstLevel]
|
mappedFirstLevel := i.idx.FanoutMapping[i.firstLevel]
|
||||||
entry := new(Entry)
|
entry := new(Entry)
|
||||||
copy(entry.Hash[:], i.idx.Names[mappedFirstLevel][i.secondLevel*objectIDLength:])
|
copy(entry.Hash[:], i.idx.Names[mappedFirstLevel][i.secondLevel*objectIDLength:])
|
||||||
entry.Offset = i.idx.getOffset(mappedFirstLevel, i.secondLevel)
|
var err error
|
||||||
|
entry.Offset, err = i.idx.getOffset(mappedFirstLevel, i.secondLevel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
entry.CRC32 = i.idx.getCRC32(mappedFirstLevel, i.secondLevel)
|
entry.CRC32 = i.idx.getCRC32(mappedFirstLevel, i.secondLevel)
|
||||||
|
|
||||||
i.secondLevel++
|
i.secondLevel++
|
||||||
|
|||||||
+15
-3
@@ -11,9 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrClosed = errors.New("objfile: already closed")
|
ErrClosed = errors.New("objfile: already closed")
|
||||||
ErrHeader = errors.New("objfile: invalid header")
|
ErrHeader = errors.New("objfile: invalid header")
|
||||||
ErrNegativeSize = errors.New("objfile: negative object size")
|
ErrHeaderNotRead = errors.New("objfile: Header must be called before Read")
|
||||||
|
ErrNegativeSize = errors.New("objfile: negative object size")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader reads and decodes compressed objfile data from a provided io.Reader.
|
// Reader reads and decodes compressed objfile data from a provided io.Reader.
|
||||||
@@ -100,12 +101,23 @@ func (r *Reader) prepareForRead(t plumbing.ObjectType, size int64) {
|
|||||||
//
|
//
|
||||||
// If Read encounters the end of the data stream it will return err == io.EOF,
|
// If Read encounters the end of the data stream it will return err == io.EOF,
|
||||||
// either in the current call if n > 0 or in a subsequent call.
|
// either in the current call if n > 0 or in a subsequent call.
|
||||||
|
//
|
||||||
|
// Read returns ErrHeaderNotRead if Header has not been called successfully.
|
||||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
if r.multi == nil {
|
||||||
|
return 0, ErrHeaderNotRead
|
||||||
|
}
|
||||||
return r.multi.Read(p)
|
return r.multi.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hash of the object data stream that has been read so far.
|
// Hash returns the hash of the object data stream that has been read so far.
|
||||||
|
// It returns the zero plumbing.Hash if Header has not been called
|
||||||
|
// successfully — guarding against the nil hasher that prepareForRead has
|
||||||
|
// not yet allocated.
|
||||||
func (r *Reader) Hash() plumbing.Hash {
|
func (r *Reader) Hash() plumbing.Hash {
|
||||||
|
if r.multi == nil {
|
||||||
|
return plumbing.ZeroHash
|
||||||
|
}
|
||||||
return r.hasher.Sum()
|
return r.hasher.Sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-3
@@ -19,9 +19,6 @@ const (
|
|||||||
// https://github.com/git/git/blob/f7466e94375b3be27f229c78873f0acf8301c0a5/diff-delta.c#L428
|
// https://github.com/git/git/blob/f7466e94375b3be27f229c78873f0acf8301c0a5/diff-delta.c#L428
|
||||||
// Max size of a copy operation (64KB).
|
// Max size of a copy operation (64KB).
|
||||||
maxCopySize = 64 * 1024
|
maxCopySize = 64 * 1024
|
||||||
|
|
||||||
// Min size of a copy operation.
|
|
||||||
minCopySize = 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetDelta returns an EncodedObject of type OFSDeltaObject. Base and Target object,
|
// GetDelta returns an EncodedObject of type OFSDeltaObject. Base and Target object,
|
||||||
|
|||||||
+7
-1
@@ -78,7 +78,13 @@ func (o *FSObject) Reader() (io.ReadCloser, error) {
|
|||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ioutil.NewReadCloserWithCloser(r, f.Close), nil
|
// Cap the lazy stream at the resolved object size: well-formed
|
||||||
|
// content reaches EOF inside the bound, an inflated stream that
|
||||||
|
// runs past surfaces ErrInflatedSizeMismatch on the byte just
|
||||||
|
// past the limit. For delta-resolved objects o.size is the
|
||||||
|
// expanded size, which is what the caller is reading here.
|
||||||
|
bounded := newBoundedReadCloser(r, o.size)
|
||||||
|
return ioutil.NewReadCloserWithCloser(bounded, f.Close), nil
|
||||||
}
|
}
|
||||||
r, err := p.getObjectContent(o.offset)
|
r, err := p.getObjectContent(o.offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+15
-6
@@ -126,11 +126,17 @@ func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) {
|
|||||||
return h, err
|
return h, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Packfile) getDeltaObjectSize(buf *bytes.Buffer) int64 {
|
func (p *Packfile) getDeltaObjectSize(buf *bytes.Buffer) (int64, error) {
|
||||||
delta := buf.Bytes()
|
delta := buf.Bytes()
|
||||||
_, delta = decodeLEB128(delta) // skip src size
|
_, delta, err := decodeLEB128(delta) // skip src size
|
||||||
sz, _ := decodeLEB128(delta)
|
if err != nil {
|
||||||
return int64(sz)
|
return 0, err
|
||||||
|
}
|
||||||
|
sz, _, err := decodeLEB128(delta)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int64(sz), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) {
|
func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) {
|
||||||
@@ -145,7 +151,7 @@ func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.getDeltaObjectSize(buf), nil
|
return p.getDeltaObjectSize(buf)
|
||||||
default:
|
default:
|
||||||
return 0, ErrInvalidObject.AddDetails("type %q", h.Type)
|
return 0, ErrInvalidObject.AddDetails("type %q", h.Type)
|
||||||
}
|
}
|
||||||
@@ -233,7 +239,10 @@ func (p *Packfile) getNextObject(h *ObjectHeader, hash plumbing.Hash) (plumbing.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
size = p.getDeltaObjectSize(buf)
|
size, err = p.getDeltaObjectSize(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if size <= smallObjectThreshold {
|
if size <= smallObjectThreshold {
|
||||||
var obj = new(plumbing.MemoryObject)
|
var obj = new(plumbing.MemoryObject)
|
||||||
obj.SetSize(size)
|
obj.SetSize(size)
|
||||||
|
|||||||
+65
-7
@@ -26,6 +26,45 @@ var (
|
|||||||
ErrDeltaNotCached = errors.New("delta could not be found in cache")
|
ErrDeltaNotCached = errors.New("delta could not be found in cache")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maxObjectPreallocBytes caps the up-front size hint passed to
|
||||||
|
// bytes.Buffer.Grow when staging an object's contents, so a malformed length
|
||||||
|
// cannot trigger a huge or out-of-range allocation. The buffer still grows
|
||||||
|
// dynamically as data is written; this is purely a hint cap.
|
||||||
|
const maxObjectPreallocBytes = 1 << 30 // 1 GiB
|
||||||
|
|
||||||
|
// maxObjectsPrealloc caps the up-front capacity reserved from the pack's
|
||||||
|
// declared object count, so a header advertising an absurd quantity cannot
|
||||||
|
// trigger a multi-gigabyte allocation. The slice and maps still grow
|
||||||
|
// organically beyond this hint.
|
||||||
|
const maxObjectsPrealloc = 1 << 16 // 64 Ki entries
|
||||||
|
|
||||||
|
// Match upstream Git's pack depth ceiling: pack-objects.h OE_DEPTH_BITS,
|
||||||
|
// enforced in builtin/pack-objects.c as (1 << OE_DEPTH_BITS) - 1.
|
||||||
|
const maxDeltaChainDepth = 4095
|
||||||
|
|
||||||
|
// growHint returns a non-negative int64 size, clamped to a sane upper bound,
|
||||||
|
// suitable for passing to bytes.Buffer.Grow.
|
||||||
|
func growHint(n int64) int {
|
||||||
|
switch {
|
||||||
|
case n <= 0:
|
||||||
|
return 0
|
||||||
|
case n > maxObjectPreallocBytes:
|
||||||
|
return maxObjectPreallocBytes
|
||||||
|
default:
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectsHint returns a non-negative count, clamped to maxObjectsPrealloc,
|
||||||
|
// suitable for passing to make() as the capacity hint for slices or maps
|
||||||
|
// sized from a pack's declared object count.
|
||||||
|
func objectsHint(n uint32) int {
|
||||||
|
if n > maxObjectsPrealloc {
|
||||||
|
return maxObjectsPrealloc
|
||||||
|
}
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
|
||||||
// Observer interface is implemented by index encoders.
|
// Observer interface is implemented by index encoders.
|
||||||
type Observer interface {
|
type Observer interface {
|
||||||
// OnHeader is called when a new packfile is opened.
|
// OnHeader is called when a new packfile is opened.
|
||||||
@@ -166,9 +205,10 @@ func (p *Parser) init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.count = c
|
p.count = c
|
||||||
p.oiByHash = make(map[plumbing.Hash]*objectInfo, p.count)
|
hint := objectsHint(p.count)
|
||||||
p.oiByOffset = make(map[int64]*objectInfo, p.count)
|
p.oiByHash = make(map[plumbing.Hash]*objectInfo, hint)
|
||||||
p.oi = make([]*objectInfo, p.count)
|
p.oiByOffset = make(map[int64]*objectInfo, hint)
|
||||||
|
p.oi = make([]*objectInfo, 0, hint)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -261,7 +301,7 @@ func (p *Parser) indexObjects() error {
|
|||||||
}
|
}
|
||||||
if delta && !p.scanner.IsSeekable {
|
if delta && !p.scanner.IsSeekable {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
buf.Grow(int(oh.Length))
|
buf.Grow(growHint(oh.Length))
|
||||||
writers = append(writers, buf)
|
writers = append(writers, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +346,7 @@ func (p *Parser) indexObjects() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.oiByOffset[oh.Offset] = ota
|
p.oiByOffset[oh.Offset] = ota
|
||||||
p.oi[i] = ota
|
p.oi = append(p.oi, ota)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -317,8 +357,12 @@ func (p *Parser) resolveDeltas() error {
|
|||||||
defer sync.PutBytesBuffer(buf)
|
defer sync.PutBytesBuffer(buf)
|
||||||
|
|
||||||
for _, obj := range p.oi {
|
for _, obj := range p.oi {
|
||||||
|
if err := checkDeltaChainDepth(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
buf.Grow(int(obj.Length))
|
buf.Grow(growHint(obj.Length))
|
||||||
err := p.get(obj, buf)
|
err := p.get(obj, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -337,6 +381,9 @@ func (p *Parser) resolveDeltas() error {
|
|||||||
// create it once and reuse across all children.
|
// create it once and reuse across all children.
|
||||||
r := bytes.NewReader(buf.Bytes())
|
r := bytes.NewReader(buf.Bytes())
|
||||||
for _, child := range obj.Children {
|
for _, child := range obj.Children {
|
||||||
|
if err := checkDeltaChainDepth(child); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Even though we are discarding the output, we still need to read it to
|
// Even though we are discarding the output, we still need to read it to
|
||||||
// so that the scanner can advance to the next object, and the SHA1 can be
|
// so that the scanner can advance to the next object, and the SHA1 can be
|
||||||
// calculated.
|
// calculated.
|
||||||
@@ -356,6 +403,17 @@ func (p *Parser) resolveDeltas() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkDeltaChainDepth(o *objectInfo) error {
|
||||||
|
var depth int
|
||||||
|
for current := o; current != nil && current.DiskType.IsDelta(); current = current.Parent {
|
||||||
|
depth++
|
||||||
|
if depth > maxDeltaChainDepth {
|
||||||
|
return fmt.Errorf("%w: delta chain depth exceeds %d", ErrMalformedPackFile, maxDeltaChainDepth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Parser) resolveExternalRef(o *objectInfo) {
|
func (p *Parser) resolveExternalRef(o *objectInfo) {
|
||||||
if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef {
|
if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef {
|
||||||
p.oiByHash[o.SHA1] = o
|
p.oiByHash[o.SHA1] = o
|
||||||
@@ -405,7 +463,7 @@ func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) {
|
|||||||
if o.DiskType.IsDelta() {
|
if o.DiskType.IsDelta() {
|
||||||
b := sync.GetBytesBuffer()
|
b := sync.GetBytesBuffer()
|
||||||
defer sync.PutBytesBuffer(b)
|
defer sync.PutBytesBuffer(b)
|
||||||
buf.Grow(int(o.Length))
|
buf.Grow(growHint(o.Length))
|
||||||
err := p.get(o.Parent, b)
|
err := p.get(o.Parent, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+72
-41
@@ -31,10 +31,15 @@ const (
|
|||||||
// premptively made available for a patch operation.
|
// premptively made available for a patch operation.
|
||||||
maxPatchPreemptionSize uint = 65536
|
maxPatchPreemptionSize uint = 65536
|
||||||
|
|
||||||
// minDeltaSize defines the smallest size for a delta.
|
// minDeltaSize is the smallest valid delta: a 1-byte srcSz LEB128
|
||||||
minDeltaSize = 4
|
// header followed by a 1-byte targetSz LEB128 header (the
|
||||||
|
// shortest case being targetSz=0 with no operations).
|
||||||
|
minDeltaSize = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// uintBits is the bit width of uint on the current platform (32 or 64).
|
||||||
|
const uintBits = 32 << (^uint(0) >> 63)
|
||||||
|
|
||||||
type offset struct {
|
type offset struct {
|
||||||
mask byte
|
mask byte
|
||||||
shift uint
|
shift uint
|
||||||
@@ -142,7 +147,7 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
|
|||||||
baseBuf := bufio.NewReader(baseRd)
|
baseBuf := bufio.NewReader(baseRd)
|
||||||
basePos := uint(0)
|
basePos := uint(0)
|
||||||
|
|
||||||
for {
|
for remainingTargetSz > 0 {
|
||||||
cmd, err := deltaBuf.ReadByte()
|
cmd, err := deltaBuf.ReadByte()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
_ = dstWr.CloseWithError(ErrInvalidDelta)
|
_ = dstWr.CloseWithError(ErrInvalidDelta)
|
||||||
@@ -166,9 +171,9 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if invalidSize(sz, targetSz) ||
|
if invalidSize(sz, remainingTargetSz) ||
|
||||||
invalidOffsetSize(offset, sz, srcSz) {
|
invalidOffsetSize(offset, sz, srcSz) {
|
||||||
_ = dstWr.Close()
|
_ = dstWr.CloseWithError(ErrInvalidDelta)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +215,7 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
|
|||||||
|
|
||||||
case isCopyFromDelta(cmd):
|
case isCopyFromDelta(cmd):
|
||||||
sz := uint(cmd) // cmd is the size itself
|
sz := uint(cmd) // cmd is the size itself
|
||||||
if invalidSize(sz, targetSz) {
|
if invalidSize(sz, remainingTargetSz) {
|
||||||
_ = dstWr.CloseWithError(ErrInvalidDelta)
|
_ = dstWr.CloseWithError(ErrInvalidDelta)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -225,40 +230,48 @@ func ReaderFromDelta(base plumbing.EncodedObject, deltaRC io.Reader) (io.ReadClo
|
|||||||
_ = dstWr.CloseWithError(ErrDeltaCmd)
|
_ = dstWr.CloseWithError(ErrDeltaCmd)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if remainingTargetSz <= 0 {
|
|
||||||
_ = dstWr.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirror upstream's `data != top` post-loop check: every byte
|
||||||
|
// of the delta payload must be consumed.
|
||||||
|
if _, err := deltaBuf.ReadByte(); err == nil {
|
||||||
|
_ = dstWr.CloseWithError(ErrInvalidDelta)
|
||||||
|
return
|
||||||
|
} else if err != io.EOF {
|
||||||
|
_ = dstWr.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = dstWr.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return dstRd, nil
|
return dstRd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
|
func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
|
||||||
if len(delta) < minCopySize {
|
srcSz, delta, err := decodeLEB128(delta)
|
||||||
return ErrInvalidDelta
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", ErrInvalidDelta, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srcSz, delta := decodeLEB128(delta)
|
|
||||||
if srcSz != uint(len(src)) {
|
if srcSz != uint(len(src)) {
|
||||||
return ErrInvalidDelta
|
return ErrInvalidDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
targetSz, delta := decodeLEB128(delta)
|
targetSz, delta, err := decodeLEB128(delta)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", ErrInvalidDelta, err)
|
||||||
|
}
|
||||||
remainingTargetSz := targetSz
|
remainingTargetSz := targetSz
|
||||||
|
|
||||||
var cmd byte
|
|
||||||
|
|
||||||
growSz := min(targetSz, maxPatchPreemptionSize)
|
growSz := min(targetSz, maxPatchPreemptionSize)
|
||||||
dst.Grow(int(growSz))
|
dst.Grow(int(growSz))
|
||||||
for {
|
|
||||||
|
for remainingTargetSz > 0 {
|
||||||
if len(delta) == 0 {
|
if len(delta) == 0 {
|
||||||
return ErrInvalidDelta
|
return ErrInvalidDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = delta[0]
|
cmd := delta[0]
|
||||||
delta = delta[1:]
|
delta = delta[1:]
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -275,16 +288,16 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if invalidSize(sz, targetSz) ||
|
if invalidSize(sz, remainingTargetSz) ||
|
||||||
invalidOffsetSize(offset, sz, srcSz) {
|
invalidOffsetSize(offset, sz, srcSz) {
|
||||||
break
|
return ErrInvalidDelta
|
||||||
}
|
}
|
||||||
dst.Write(src[offset : offset+sz])
|
dst.Write(src[offset : offset+sz])
|
||||||
remainingTargetSz -= sz
|
remainingTargetSz -= sz
|
||||||
|
|
||||||
case isCopyFromDelta(cmd):
|
case isCopyFromDelta(cmd):
|
||||||
sz := uint(cmd) // cmd is the size itself
|
sz := uint(cmd) // cmd is the size itself
|
||||||
if invalidSize(sz, targetSz) {
|
if invalidSize(sz, remainingTargetSz) {
|
||||||
return ErrInvalidDelta
|
return ErrInvalidDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,10 +312,12 @@ func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
|
|||||||
default:
|
default:
|
||||||
return ErrDeltaCmd
|
return ErrDeltaCmd
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if remainingTargetSz <= 0 {
|
// Mirror upstream's `data != top` post-loop check: every byte of
|
||||||
break
|
// the delta payload must be consumed.
|
||||||
}
|
if len(delta) != 0 {
|
||||||
|
return ErrInvalidDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -354,7 +369,7 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
|
|||||||
baselr := io.LimitReader(sr, 0).(*io.LimitedReader)
|
baselr := io.LimitReader(sr, 0).(*io.LimitedReader)
|
||||||
deltalr := io.LimitReader(deltaBuf, 0).(*io.LimitedReader)
|
deltalr := io.LimitReader(deltaBuf, 0).(*io.LimitedReader)
|
||||||
|
|
||||||
for {
|
for remainingTargetSz > 0 {
|
||||||
buf := *bufp
|
buf := *bufp
|
||||||
cmd, err := deltaBuf.ReadByte()
|
cmd, err := deltaBuf.ReadByte()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -374,9 +389,9 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
|
|||||||
return 0, plumbing.ZeroHash, err
|
return 0, plumbing.ZeroHash, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if invalidSize(sz, targetSz) ||
|
if invalidSize(sz, remainingTargetSz) ||
|
||||||
invalidOffsetSize(offset, sz, srcSz) {
|
invalidOffsetSize(offset, sz, srcSz) {
|
||||||
return 0, plumbing.ZeroHash, err
|
return 0, plumbing.ZeroHash, ErrInvalidDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sr.Seek(int64(offset), io.SeekStart); err != nil {
|
if _, err := sr.Seek(int64(offset), io.SeekStart); err != nil {
|
||||||
@@ -389,7 +404,7 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
|
|||||||
remainingTargetSz -= sz
|
remainingTargetSz -= sz
|
||||||
} else if isCopyFromDelta(cmd) {
|
} else if isCopyFromDelta(cmd) {
|
||||||
sz := uint(cmd) // cmd is the size itself
|
sz := uint(cmd) // cmd is the size itself
|
||||||
if invalidSize(sz, targetSz) {
|
if invalidSize(sz, remainingTargetSz) {
|
||||||
return 0, plumbing.ZeroHash, ErrInvalidDelta
|
return 0, plumbing.ZeroHash, ErrInvalidDelta
|
||||||
}
|
}
|
||||||
deltalr.N = int64(sz)
|
deltalr.N = int64(sz)
|
||||||
@@ -399,30 +414,41 @@ func patchDeltaWriter(dst io.Writer, base io.ReaderAt, delta io.Reader,
|
|||||||
|
|
||||||
remainingTargetSz -= sz
|
remainingTargetSz -= sz
|
||||||
} else {
|
} else {
|
||||||
return 0, plumbing.ZeroHash, err
|
return 0, plumbing.ZeroHash, ErrDeltaCmd
|
||||||
}
|
|
||||||
if remainingTargetSz <= 0 {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirror upstream's `data != top` post-loop check: every byte of
|
||||||
|
// the delta payload must be consumed.
|
||||||
|
if _, err := deltaBuf.ReadByte(); err == nil {
|
||||||
|
return 0, plumbing.ZeroHash, ErrInvalidDelta
|
||||||
|
} else if err != io.EOF {
|
||||||
|
return 0, plumbing.ZeroHash, err
|
||||||
|
}
|
||||||
|
|
||||||
return targetSz, hasher.Sum(), nil
|
return targetSz, hasher.Sum(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decodes a number encoded as an unsigned LEB128 at the start of some
|
// Decodes a number encoded as an unsigned LEB128 at the start of some
|
||||||
// binary data and returns the decoded number and the rest of the
|
// binary data and returns the decoded number, the rest of the stream,
|
||||||
// stream.
|
// and an error if the encoded value does not fit in a uint.
|
||||||
//
|
//
|
||||||
// This must be called twice on the delta data buffer, first to get the
|
// This must be called twice on the delta data buffer, first to get the
|
||||||
// expected source buffer size, and again to get the target buffer size.
|
// expected source buffer size, and again to get the target buffer size.
|
||||||
func decodeLEB128(input []byte) (uint, []byte) {
|
func decodeLEB128(input []byte) (uint, []byte, error) {
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
return 0, input
|
return 0, input, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var num, sz uint
|
var num, sz uint
|
||||||
var b byte
|
var b byte
|
||||||
for {
|
for {
|
||||||
|
// A continuation byte at shift > uintBits-7 cannot contribute
|
||||||
|
// without overflowing the accumulator.
|
||||||
|
if sz*7 > uintBits-7 {
|
||||||
|
return 0, input, ErrLengthOverflow
|
||||||
|
}
|
||||||
|
|
||||||
b = input[sz]
|
b = input[sz]
|
||||||
num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks
|
num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks
|
||||||
sz++
|
sz++
|
||||||
@@ -432,12 +458,16 @@ func decodeLEB128(input []byte) (uint, []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return num, input[sz:]
|
return num, input[sz:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeLEB128ByteReader(input io.ByteReader) (uint, error) {
|
func decodeLEB128ByteReader(input io.ByteReader) (uint, error) {
|
||||||
var num, sz uint
|
var num, sz uint
|
||||||
for {
|
for {
|
||||||
|
if sz*7 > uintBits-7 {
|
||||||
|
return 0, ErrLengthOverflow
|
||||||
|
}
|
||||||
|
|
||||||
b, err := input.ReadByte()
|
b, err := input.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -529,8 +559,9 @@ func decodeSize(cmd byte, delta []byte) (uint, []byte, error) {
|
|||||||
return sz, delta, nil
|
return sz, delta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidSize(sz, targetSz uint) bool {
|
// invalidSize reports whether sz exceeds the remaining target size.
|
||||||
return sz > targetSz
|
func invalidSize(sz, remaining uint) bool {
|
||||||
|
return sz > remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidOffsetSize(offset, sz, srcSz uint) bool {
|
func invalidOffsetSize(offset, sz, srcSz uint) bool {
|
||||||
|
|||||||
+145
-9
@@ -29,8 +29,100 @@ var (
|
|||||||
ErrSeekNotSupported = NewError("not seek support")
|
ErrSeekNotSupported = NewError("not seek support")
|
||||||
// ErrMalformedPackFile is returned by the parser when the pack file is corrupted.
|
// ErrMalformedPackFile is returned by the parser when the pack file is corrupted.
|
||||||
ErrMalformedPackFile = errors.New("malformed PACK file")
|
ErrMalformedPackFile = errors.New("malformed PACK file")
|
||||||
|
// ErrLengthOverflow is returned when a variable-length integer would not
|
||||||
|
// fit into its accumulator because the input declares more continuation
|
||||||
|
// bytes than the type can hold.
|
||||||
|
ErrLengthOverflow = errors.New("variable-length integer overflow")
|
||||||
|
// ErrInflatedSizeMismatch is returned when a packfile object inflates to
|
||||||
|
// more bytes than the size declared in its object header. A well-formed
|
||||||
|
// packfile never produces more data than the declared size; exceeding it
|
||||||
|
// indicates a structurally invalid entry.
|
||||||
|
ErrInflatedSizeMismatch = errors.New("packfile: inflated object exceeds declared size")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// boundedWriter passes writes through to w up to limit bytes total, then
|
||||||
|
// returns ErrInflatedSizeMismatch. It is used to enforce that a packfile
|
||||||
|
// object's inflated length does not exceed the size declared in its header.
|
||||||
|
type boundedWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
limit int64
|
||||||
|
n int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write forwards p to the underlying writer while keeping the running total
|
||||||
|
// at or below limit. On overrun it forwards the legal prefix and reports
|
||||||
|
// the number of bytes actually consumed alongside ErrInflatedSizeMismatch,
|
||||||
|
// matching the contract in io.Writer. A write error from the underlying
|
||||||
|
// writer during overrun-handling is joined with ErrInflatedSizeMismatch so
|
||||||
|
// it is not silently dropped.
|
||||||
|
func (b *boundedWriter) Write(p []byte) (int, error) {
|
||||||
|
if b.n+int64(len(p)) > b.limit {
|
||||||
|
remain := int(b.limit - b.n)
|
||||||
|
err := error(ErrInflatedSizeMismatch)
|
||||||
|
if remain > 0 {
|
||||||
|
n, werr := b.w.Write(p[:remain])
|
||||||
|
b.n += int64(n)
|
||||||
|
if werr != nil {
|
||||||
|
err = errors.Join(ErrInflatedSizeMismatch, werr)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n, err := b.w.Write(p)
|
||||||
|
b.n += int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// boundedReadCloser wraps a ReadCloser and reports ErrInflatedSizeMismatch
|
||||||
|
// once more than limit bytes have been read. It is used by the on-demand
|
||||||
|
// object reader returned from FSObject.Reader so that a lazy Read of a
|
||||||
|
// packfile object cannot stream past its declared inflated size.
|
||||||
|
//
|
||||||
|
// The implementation builds on io.LimitedReader with the standard
|
||||||
|
// overrun-detection trick: request limit+1 bytes from the underlying so
|
||||||
|
// that the moment the sentinel byte materializes (LimitedReader.N drops
|
||||||
|
// to zero) we know the source produced more than limit bytes.
|
||||||
|
type boundedReadCloser struct {
|
||||||
|
lr io.LimitedReader
|
||||||
|
closer io.Closer
|
||||||
|
overrun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBoundedReadCloser wraps rc so that the cumulative bytes returned from
|
||||||
|
// Read never exceed limit. The first call that would have returned a byte
|
||||||
|
// past limit instead returns ErrInflatedSizeMismatch; subsequent calls
|
||||||
|
// keep returning the same error. A negative limit is treated as zero, so
|
||||||
|
// the first byte produced by rc surfaces ErrInflatedSizeMismatch.
|
||||||
|
func newBoundedReadCloser(rc io.ReadCloser, limit int64) *boundedReadCloser {
|
||||||
|
if limit < 0 {
|
||||||
|
limit = 0
|
||||||
|
}
|
||||||
|
return &boundedReadCloser{
|
||||||
|
lr: io.LimitedReader{R: rc, N: limit + 1},
|
||||||
|
closer: rc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read forwards Read up to the configured byte limit. When the underlying
|
||||||
|
// stream produces the limit+1 sentinel byte, the legal prefix is returned
|
||||||
|
// alongside ErrInflatedSizeMismatch; on subsequent calls only the error
|
||||||
|
// is returned.
|
||||||
|
func (b *boundedReadCloser) Read(p []byte) (int, error) {
|
||||||
|
if b.overrun {
|
||||||
|
return 0, ErrInflatedSizeMismatch
|
||||||
|
}
|
||||||
|
n, err := b.lr.Read(p)
|
||||||
|
if b.lr.N == 0 {
|
||||||
|
b.overrun = true
|
||||||
|
return n - 1, ErrInflatedSizeMismatch
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the underlying ReadCloser.
|
||||||
|
func (b *boundedReadCloser) Close() error { return b.closer.Close() }
|
||||||
|
|
||||||
// ObjectHeader contains the information related to the object, this information
|
// ObjectHeader contains the information related to the object, this information
|
||||||
// is collected from the previous bytes to the content of the object.
|
// is collected from the previous bytes to the content of the object.
|
||||||
type ObjectHeader struct {
|
type ObjectHeader struct {
|
||||||
@@ -220,6 +312,13 @@ func (s *Scanner) nextObjectHeader() (*ObjectHeader, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An OFS-delta references a base object that appears earlier
|
||||||
|
// in the pack; the negative offset must be strictly positive
|
||||||
|
// and not larger than the current object's offset.
|
||||||
|
if no <= 0 || no > h.Offset {
|
||||||
|
return nil, fmt.Errorf("%w: invalid OFS delta offset", ErrMalformedPackFile)
|
||||||
|
}
|
||||||
|
|
||||||
h.OffsetReference = h.Offset - no
|
h.OffsetReference = h.Offset - no
|
||||||
case plumbing.REFDeltaObject:
|
case plumbing.REFDeltaObject:
|
||||||
var err error
|
var err error
|
||||||
@@ -303,6 +402,13 @@ func (s *Scanner) readLength(first byte) (int64, error) {
|
|||||||
shift := firstLengthBits
|
shift := firstLengthBits
|
||||||
var err error
|
var err error
|
||||||
for c&maskContinue > 0 {
|
for c&maskContinue > 0 {
|
||||||
|
// Mirrors unpack_object_header_buffer in canonical Git's
|
||||||
|
// packfile.c: a continuation byte at shift > 64-7 cannot
|
||||||
|
// contribute without overflowing an int64.
|
||||||
|
if shift > 64-lengthBits {
|
||||||
|
return 0, fmt.Errorf("%w: %w", ErrMalformedPackFile, ErrLengthOverflow)
|
||||||
|
}
|
||||||
|
|
||||||
if c, err = s.r.ReadByte(); err != nil {
|
if c, err = s.r.ReadByte(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -315,10 +421,18 @@ func (s *Scanner) readLength(first byte) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NextObject writes the content of the next object into the reader, returns
|
// NextObject writes the content of the next object into the reader, returns
|
||||||
// the number of bytes written, the CRC32 of the content and an error, if any
|
// the number of bytes written, the CRC32 of the content and an error, if any.
|
||||||
|
//
|
||||||
|
// When a prior NextObjectHeader has stashed the object header in
|
||||||
|
// pendingObject, the inflated stream is bounded by the header's declared
|
||||||
|
// length and surfaces ErrInflatedSizeMismatch on overrun.
|
||||||
func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err error) {
|
func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err error) {
|
||||||
|
declaredSize := int64(-1)
|
||||||
|
if s.pendingObject != nil {
|
||||||
|
declaredSize = s.pendingObject.Length
|
||||||
|
}
|
||||||
s.pendingObject = nil
|
s.pendingObject = nil
|
||||||
written, err = s.copyObject(w)
|
written, err = s.copyObject(w, declaredSize)
|
||||||
|
|
||||||
s.r.Flush()
|
s.r.Flush()
|
||||||
crc32 = s.crc.Sum32()
|
crc32 = s.crc.Sum32()
|
||||||
@@ -327,23 +441,39 @@ func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadObject returns a reader for the object content and an error
|
// ReadObject returns a reader for the object content and an error.
|
||||||
|
//
|
||||||
|
// When a prior NextObjectHeader has stashed the object header in
|
||||||
|
// pendingObject, the returned reader is bounded by the header's declared
|
||||||
|
// length so callers cannot stream past the declared inflated size; an
|
||||||
|
// overrun surfaces ErrInflatedSizeMismatch on the byte just past the
|
||||||
|
// limit.
|
||||||
func (s *Scanner) ReadObject() (io.ReadCloser, error) {
|
func (s *Scanner) ReadObject() (io.ReadCloser, error) {
|
||||||
|
declaredSize := int64(-1)
|
||||||
|
if s.pendingObject != nil {
|
||||||
|
declaredSize = s.pendingObject.Length
|
||||||
|
}
|
||||||
s.pendingObject = nil
|
s.pendingObject = nil
|
||||||
zr, err := sync.GetZlibReader(s.r)
|
zr, err := sync.GetZlibReader(s.r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("zlib reset error: %s", err)
|
return nil, fmt.Errorf("zlib reset error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.NewReadCloserWithCloser(zr.Reader, func() error {
|
rc := ioutil.NewReadCloserWithCloser(zr.Reader, func() error {
|
||||||
sync.PutZlibReader(zr)
|
sync.PutZlibReader(zr)
|
||||||
return nil
|
return nil
|
||||||
}), nil
|
})
|
||||||
|
if declaredSize >= 0 {
|
||||||
|
return newBoundedReadCloser(rc, declaredSize), nil
|
||||||
|
}
|
||||||
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRegularObject reads and write a non-deltified object
|
// copyObject inflates a non-deltified object's zlib stream into w. When
|
||||||
// from it zlib stream in an object entry in the packfile.
|
// declaredSize is non-negative, the write sink is wrapped in a
|
||||||
func (s *Scanner) copyObject(w io.Writer) (n int64, err error) {
|
// boundedWriter so an overrun surfaces ErrInflatedSizeMismatch instead
|
||||||
|
// of being silently appended.
|
||||||
|
func (s *Scanner) copyObject(w io.Writer, declaredSize int64) (n int64, err error) {
|
||||||
zr, err := sync.GetZlibReader(s.r)
|
zr, err := sync.GetZlibReader(s.r)
|
||||||
defer sync.PutZlibReader(zr)
|
defer sync.PutZlibReader(zr)
|
||||||
|
|
||||||
@@ -352,8 +482,14 @@ func (s *Scanner) copyObject(w io.Writer) (n int64, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer ioutil.CheckClose(zr.Reader, &err)
|
defer ioutil.CheckClose(zr.Reader, &err)
|
||||||
|
|
||||||
|
sink := w
|
||||||
|
if declaredSize >= 0 {
|
||||||
|
sink = &boundedWriter{w: w, limit: declaredSize}
|
||||||
|
}
|
||||||
|
|
||||||
buf := sync.GetByteSlice()
|
buf := sync.GetByteSlice()
|
||||||
n, err = io.CopyBuffer(w, zr.Reader, *buf)
|
n, err = io.CopyBuffer(sink, zr.Reader, *buf)
|
||||||
sync.PutByteSlice(buf)
|
sync.PutByteSlice(buf)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+113
-94
@@ -5,7 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
@@ -20,6 +20,7 @@ const (
|
|||||||
beginpgp string = "-----BEGIN PGP SIGNATURE-----"
|
beginpgp string = "-----BEGIN PGP SIGNATURE-----"
|
||||||
endpgp string = "-----END PGP SIGNATURE-----"
|
endpgp string = "-----END PGP SIGNATURE-----"
|
||||||
headerpgp string = "gpgsig"
|
headerpgp string = "gpgsig"
|
||||||
|
headerpgp256 string = "gpgsig-sha256"
|
||||||
headerencoding string = "encoding"
|
headerencoding string = "encoding"
|
||||||
|
|
||||||
// https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153
|
// https://github.com/git/git/blob/bcb6cae2966cc407ca1afc77413b3ef11103c175/Documentation/gitformat-signature.txt#L153
|
||||||
@@ -41,6 +42,11 @@ type MessageEncoding string
|
|||||||
// in time, such as a timestamp, the author of the changes since the last
|
// in time, such as a timestamp, the author of the changes since the last
|
||||||
// commit, a pointer to the previous commit(s), etc.
|
// commit, a pointer to the previous commit(s), etc.
|
||||||
// http://shafiulazam.com/gitbook/1_the_git_object_model.html
|
// http://shafiulazam.com/gitbook/1_the_git_object_model.html
|
||||||
|
//
|
||||||
|
// When a Commit is populated by Decode it retains a reference to the source
|
||||||
|
// plumbing.EncodedObject so that EncodeWithoutSignature can reproduce the
|
||||||
|
// exact bytes the signature was computed over. Refer to EncodeWithoutSignature
|
||||||
|
// for more information.
|
||||||
type Commit struct {
|
type Commit struct {
|
||||||
// Hash of the commit object.
|
// Hash of the commit object.
|
||||||
Hash plumbing.Hash
|
Hash plumbing.Hash
|
||||||
@@ -66,6 +72,9 @@ type Commit struct {
|
|||||||
ExtraHeaders []ExtraHeader
|
ExtraHeaders []ExtraHeader
|
||||||
|
|
||||||
s storer.EncodedObjectStorer
|
s storer.EncodedObjectStorer
|
||||||
|
// src holds the encoded object this Commit was decoded from, used by
|
||||||
|
// EncodeWithoutSignature to recover the canonical signed bytes.
|
||||||
|
src plumbing.EncodedObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtraHeader holds any non-standard header
|
// ExtraHeader holds any non-standard header
|
||||||
@@ -98,8 +107,8 @@ func (h ExtraHeader) Format(f fmt.State, verb rune) {
|
|||||||
func parseExtraHeader(line []byte) (ExtraHeader, bool) {
|
func parseExtraHeader(line []byte) (ExtraHeader, bool) {
|
||||||
split := bytes.SplitN(line, []byte{' '}, 2)
|
split := bytes.SplitN(line, []byte{' '}, 2)
|
||||||
|
|
||||||
out := ExtraHeader {
|
out := ExtraHeader{
|
||||||
Key: string(bytes.TrimRight(split[0], "\n")),
|
Key: string(bytes.TrimRight(split[0], "\n")),
|
||||||
Value: "",
|
Value: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +190,11 @@ func (c *Commit) NumParents() int {
|
|||||||
|
|
||||||
var ErrParentNotFound = errors.New("commit parent not found")
|
var ErrParentNotFound = errors.New("commit parent not found")
|
||||||
|
|
||||||
|
// ErrMalformedCommit is returned when a commit object cannot be decoded
|
||||||
|
// because its standard headers (tree, parent, author, committer) are missing,
|
||||||
|
// duplicated, or out of order.
|
||||||
|
var ErrMalformedCommit = errors.New("malformed commit")
|
||||||
|
|
||||||
// Parent returns the ith parent of a commit.
|
// Parent returns the ith parent of a commit.
|
||||||
func (c *Commit) Parent(i int) (*Commit, error) {
|
func (c *Commit) Parent(i int) (*Commit, error) {
|
||||||
if len(c.ParentHashes) == 0 || i > len(c.ParentHashes)-1 {
|
if len(c.ParentHashes) == 0 || i > len(c.ParentHashes)-1 {
|
||||||
@@ -227,14 +241,23 @@ func (c *Commit) Type() plumbing.ObjectType {
|
|||||||
return plumbing.CommitObject
|
return plumbing.CommitObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commit) reset() {
|
||||||
|
storer := c.s
|
||||||
|
*c = Commit{
|
||||||
|
Encoding: defaultUtf8CommitMessageEncoding,
|
||||||
|
s: storer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Decode transforms a plumbing.EncodedObject into a Commit struct.
|
// Decode transforms a plumbing.EncodedObject into a Commit struct.
|
||||||
func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
|
func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
|
||||||
if o.Type() != plumbing.CommitObject {
|
if o.Type() != plumbing.CommitObject {
|
||||||
return ErrUnsupportedObject
|
return ErrUnsupportedObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.reset()
|
||||||
c.Hash = o.Hash()
|
c.Hash = o.Hash()
|
||||||
c.Encoding = defaultUtf8CommitMessageEncoding
|
c.src = o
|
||||||
|
|
||||||
reader, err := o.Reader()
|
reader, err := o.Reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -245,97 +268,17 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
|
|||||||
r := sync.GetBufioReader(reader)
|
r := sync.GetBufioReader(reader)
|
||||||
defer sync.PutBufioReader(r)
|
defer sync.PutBufioReader(r)
|
||||||
|
|
||||||
var message bool
|
s := &commitScanner{r: r, c: c}
|
||||||
var mergetag bool
|
for state := scanTree; state != nil; {
|
||||||
var pgpsig bool
|
state, err = state(s)
|
||||||
var msgbuf bytes.Buffer
|
if err != nil {
|
||||||
var extraheader *ExtraHeader = nil
|
|
||||||
for {
|
|
||||||
line, err := r.ReadBytes('\n')
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mergetag {
|
|
||||||
if len(line) > 0 && line[0] == ' ' {
|
|
||||||
line = bytes.TrimLeft(line, " ")
|
|
||||||
c.MergeTag += string(line)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
mergetag = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pgpsig {
|
|
||||||
if len(line) > 0 && line[0] == ' ' {
|
|
||||||
line = bytes.TrimLeft(line, " ")
|
|
||||||
c.PGPSignature += string(line)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
pgpsig = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if extraheader != nil {
|
|
||||||
if len(line) > 0 && line[0] == ' ' {
|
|
||||||
extraheader.Value += string(line[1:])
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
extraheader.Value = strings.TrimRight(extraheader.Value, "\n")
|
|
||||||
c.ExtraHeaders = append(c.ExtraHeaders, *extraheader)
|
|
||||||
extraheader = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !message {
|
|
||||||
original_line := line
|
|
||||||
line = bytes.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
message = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
split := bytes.SplitN(line, []byte{' '}, 2)
|
|
||||||
|
|
||||||
var data []byte
|
|
||||||
if len(split) == 2 {
|
|
||||||
data = split[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch string(split[0]) {
|
|
||||||
case "tree":
|
|
||||||
c.TreeHash = plumbing.NewHash(string(data))
|
|
||||||
case "parent":
|
|
||||||
c.ParentHashes = append(c.ParentHashes, plumbing.NewHash(string(data)))
|
|
||||||
case "author":
|
|
||||||
c.Author.Decode(data)
|
|
||||||
case "committer":
|
|
||||||
c.Committer.Decode(data)
|
|
||||||
case headermergetag:
|
|
||||||
c.MergeTag += string(data) + "\n"
|
|
||||||
mergetag = true
|
|
||||||
case headerencoding:
|
|
||||||
c.Encoding = MessageEncoding(data)
|
|
||||||
case headerpgp:
|
|
||||||
c.PGPSignature += string(data) + "\n"
|
|
||||||
pgpsig = true
|
|
||||||
default:
|
|
||||||
h, maybecontinued := parseExtraHeader(original_line)
|
|
||||||
if maybecontinued {
|
|
||||||
extraheader = &h
|
|
||||||
} else {
|
|
||||||
c.ExtraHeaders = append(c.ExtraHeaders, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
msgbuf.Write(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
c.Message = msgbuf.String()
|
if !s.sawTree {
|
||||||
|
return fmt.Errorf("%w: missing tree header", ErrMalformedCommit)
|
||||||
|
}
|
||||||
|
c.Message = s.msgbuf.String()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,11 +287,73 @@ func (c *Commit) Encode(o plumbing.EncodedObject) error {
|
|||||||
return c.encode(o, true)
|
return c.encode(o, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeWithoutSignature export a Commit into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature).
|
// EncodeWithoutSignature exports a Commit into a plumbing.EncodedObject
|
||||||
|
// without any signature headers, producing the payload that PGP/GPG
|
||||||
|
// signatures are computed over.
|
||||||
|
//
|
||||||
|
// Behaviour depends on how the Commit was created:
|
||||||
|
//
|
||||||
|
// - For Commits populated by Decode whose exported fields still match the
|
||||||
|
// source object, the payload is streamed from the raw source bytes with
|
||||||
|
// gpgsig and gpgsig-sha256 headers (and their continuation lines)
|
||||||
|
// stripped verbatim. This preserves the exact bytes the signature was
|
||||||
|
// computed over, regardless of any normalization performed by Decode.
|
||||||
|
//
|
||||||
|
// - For Commits constructed in memory, or for decoded Commits whose
|
||||||
|
// exported fields have been mutated, the payload is derived from the
|
||||||
|
// current struct fields. Mutation is detected by re-decoding the source
|
||||||
|
// object and comparing exported fields; if any differ, the in-memory
|
||||||
|
// representation prevails.
|
||||||
func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
|
func (c *Commit) EncodeWithoutSignature(o plumbing.EncodedObject) error {
|
||||||
|
if c.matchesSource() {
|
||||||
|
return stripObjectSignatures(o, c.src, plumbing.CommitObject)
|
||||||
|
}
|
||||||
return c.encode(o, false)
|
return c.encode(o, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matchesSource reports whether c.src is set and re-decoding it produces a
|
||||||
|
// Commit whose payload-affecting exported fields are identical to those of
|
||||||
|
// c. It is the auto-detection used by EncodeWithoutSignature to decide
|
||||||
|
// between the raw bytes and the struct-encoded payload.
|
||||||
|
//
|
||||||
|
// PGPSignature is intentionally excluded from the comparison: neither path
|
||||||
|
// emits it, so mutating it must not trigger a switch to struct-encode (which
|
||||||
|
// would change the byte layout the caller is trying to verify against).
|
||||||
|
func (c *Commit) matchesSource() bool {
|
||||||
|
if c.src == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fresh := &Commit{}
|
||||||
|
if err := fresh.Decode(c.src); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return c.Hash == fresh.Hash &&
|
||||||
|
signatureEqual(c.Author, fresh.Author) &&
|
||||||
|
signatureEqual(c.Committer, fresh.Committer) &&
|
||||||
|
c.MergeTag == fresh.MergeTag &&
|
||||||
|
c.Message == fresh.Message &&
|
||||||
|
c.TreeHash == fresh.TreeHash &&
|
||||||
|
c.Encoding == fresh.Encoding &&
|
||||||
|
slices.Equal(c.ParentHashes, fresh.ParentHashes) &&
|
||||||
|
slices.Equal(c.ExtraHeaders, fresh.ExtraHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
func signatureEqual(a, b Signature) bool {
|
||||||
|
return a.Name == b.Name &&
|
||||||
|
a.Email == b.Email &&
|
||||||
|
a.When.Unix() == b.When.Unix() &&
|
||||||
|
a.When.Format("-0700") == b.When.Format("-0700")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStandardHeader(key string) bool {
|
||||||
|
switch key {
|
||||||
|
case "tree", "parent", "author", "committer",
|
||||||
|
headerencoding, headermergetag, headerpgp, headerpgp256:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
||||||
o.SetType(plumbing.CommitObject)
|
o.SetType(plumbing.CommitObject)
|
||||||
w, err := o.Writer()
|
w, err := o.Writer()
|
||||||
@@ -407,7 +412,9 @@ func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, header := range c.ExtraHeaders {
|
for _, header := range c.ExtraHeaders {
|
||||||
|
if isStandardHeader(header.Key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, err = fmt.Fprintf(w, "\n%s", header); err != nil {
|
if _, err = fmt.Fprintf(w, "\n%s", header); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -478,9 +485,21 @@ func (c *Commit) String() string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrMultipleSignatures is returned by Verify when the commit carries more
|
||||||
|
// than one armored signature block. Mirrors upstream's parse_gpg_output
|
||||||
|
// rejection of GOODSIG/BADSIG status lines after the first
|
||||||
|
// (gpg-interface.c:257-269): multi-signature commits are intentionally
|
||||||
|
// unsupported because their provenance cannot be reduced to a single
|
||||||
|
// authoritative signer.
|
||||||
|
var ErrMultipleSignatures = errors.New("commit has multiple signatures")
|
||||||
|
|
||||||
// Verify performs PGP verification of the commit with a provided armored
|
// Verify performs PGP verification of the commit with a provided armored
|
||||||
// keyring and returns openpgp.Entity associated with verifying key on success.
|
// keyring and returns openpgp.Entity associated with verifying key on success.
|
||||||
func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
|
func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
|
||||||
|
if countSignatureBlocks([]byte(c.PGPSignature)) > 1 {
|
||||||
|
return nil, ErrMultipleSignatures
|
||||||
|
}
|
||||||
|
|
||||||
keyRingReader := strings.NewReader(armoredKeyRing)
|
keyRingReader := strings.NewReader(armoredKeyRing)
|
||||||
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
|
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+377
@@ -0,0 +1,377 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// commitScanner holds the working state of the commit decoder driven by the
|
||||||
|
// stateFn loop in (*Commit).Decode. Each commitState reads one or more lines
|
||||||
|
// from r, updates the in-progress *Commit and the scanner's bookkeeping, and
|
||||||
|
// returns the state that should run next (or nil to stop).
|
||||||
|
type commitScanner struct {
|
||||||
|
r *bufio.Reader
|
||||||
|
c *Commit
|
||||||
|
msgbuf bytes.Buffer
|
||||||
|
|
||||||
|
// pending holds a line that was read but the current state decided to
|
||||||
|
// hand back to the next state, paired with the io.EOF flag that was
|
||||||
|
// returned when the line was originally read.
|
||||||
|
pending []byte
|
||||||
|
pendingErr error
|
||||||
|
|
||||||
|
// First-occurrence tracking: once the corresponding field has been
|
||||||
|
// decoded, subsequent occurrences are silently dropped (matches
|
||||||
|
// upstream's find_commit_header / first-wins semantics).
|
||||||
|
//
|
||||||
|
// gpgsig is not tracked here: upstream's parse_buffer_signed_by_header
|
||||||
|
// (commit.c:1186) accumulates every occurrence into one signature buffer,
|
||||||
|
// so we do the same on the scanner side to keep verification payloads
|
||||||
|
// byte-aligned. gpgsig-sha256 is recognized and skipped without exposing a
|
||||||
|
// new field in v5.
|
||||||
|
sawTree, sawAuthor, sawCommitter bool
|
||||||
|
sawEncoding, sawMergetag bool
|
||||||
|
|
||||||
|
// extra is the multi-line ExtraHeader currently being assembled.
|
||||||
|
extra *ExtraHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// commitState is one step of the decoder state machine. Each function reads
|
||||||
|
// the lines it needs, mutates *Commit via s.c, and returns the next state to
|
||||||
|
// run (or nil to terminate the loop).
|
||||||
|
type commitState func(*commitScanner) (commitState, error)
|
||||||
|
|
||||||
|
// readLine returns the next line from the buffer, transparently consuming any
|
||||||
|
// line that was previously pushed back by a state that decided not to handle
|
||||||
|
// it.
|
||||||
|
func (s *commitScanner) readLine() ([]byte, error) {
|
||||||
|
if s.pending != nil {
|
||||||
|
line, err := s.pending, s.pendingErr
|
||||||
|
s.pending, s.pendingErr = nil, nil
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
line, err := s.r.ReadBytes('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushBack stashes an unconsumed line so the next state's readLine call sees
|
||||||
|
// it. Only one line can be pushed back at a time.
|
||||||
|
func (s *commitScanner) pushBack(line []byte, err error) {
|
||||||
|
s.pending = line
|
||||||
|
s.pendingErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTree expects the first non-empty header to be `tree HASH`. Anything
|
||||||
|
// else (or an empty buffer) is rejected with ErrMalformedCommit, matching
|
||||||
|
// upstream's `bogus commit object` check.
|
||||||
|
func scanTree(s *commitScanner) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 || isBlankLine(line) {
|
||||||
|
return nil, fmt.Errorf("%w: missing tree header", ErrMalformedCommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key != "tree" {
|
||||||
|
return nil, fmt.Errorf("%w: tree header must be first", ErrMalformedCommit)
|
||||||
|
}
|
||||||
|
h, herr := parseObjectIDHex(data, ErrMalformedCommit, "tree")
|
||||||
|
if herr != nil {
|
||||||
|
return nil, herr
|
||||||
|
}
|
||||||
|
s.c.TreeHash = h
|
||||||
|
s.sawTree = true
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanParents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanParents consumes contiguous `parent HASH` lines. The first non-parent
|
||||||
|
// line ends the parent block and is handed off to scanAuthor; any later
|
||||||
|
// `parent` line is silently dropped (matches upstream's parse_commit_buffer
|
||||||
|
// exiting its parent loop at the first non-parent line and
|
||||||
|
// read_commit_extra_header_lines filtering `parent` out of extras).
|
||||||
|
func scanParents(s *commitScanner) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if isBlankLine(line) {
|
||||||
|
return scanMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key == "parent" {
|
||||||
|
h, herr := parseObjectIDHex(data, ErrMalformedCommit, "parent")
|
||||||
|
if herr != nil {
|
||||||
|
return nil, herr
|
||||||
|
}
|
||||||
|
s.c.ParentHashes = append(s.c.ParentHashes, h)
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanParents, nil
|
||||||
|
}
|
||||||
|
s.pushBack(line, err)
|
||||||
|
return scanAuthor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanAuthor accepts an `author` line at its canonical position immediately
|
||||||
|
// after the parent block. Any other header here is pushed back for
|
||||||
|
// scanCommitter; an out-of-place author is therefore silently dropped.
|
||||||
|
// Mirrors upstream's parse_commit_date func.
|
||||||
|
func scanAuthor(s *commitScanner) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if isBlankLine(line) {
|
||||||
|
return scanMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key == "author" {
|
||||||
|
s.c.Author.Decode(data)
|
||||||
|
s.sawAuthor = true
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanCommitter, nil
|
||||||
|
}
|
||||||
|
s.pushBack(line, err)
|
||||||
|
return scanCommitter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanCommitter accepts a `committer` line at its canonical position
|
||||||
|
// immediately after the author. Any other header is pushed back for
|
||||||
|
// scanHeaders. Same upstream rationale as scanAuthor.
|
||||||
|
func scanCommitter(s *commitScanner) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if isBlankLine(line) {
|
||||||
|
return scanMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key == "committer" {
|
||||||
|
s.c.Committer.Decode(data)
|
||||||
|
s.sawCommitter = true
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanHeaders, nil
|
||||||
|
}
|
||||||
|
s.pushBack(line, err)
|
||||||
|
return scanHeaders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanHeaders dispatches one header line. Continuation-bearing headers
|
||||||
|
// (mergetag, gpgsig, gpgsig-sha256, and unknown extras whose value is
|
||||||
|
// continued on subsequent lines) hand off to a dedicated continuation state
|
||||||
|
// that handles the `<space>...` lines and then returns here.
|
||||||
|
func scanHeaders(s *commitScanner) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if isBlankLine(line) {
|
||||||
|
return scanMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
originalLine := line
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
|
||||||
|
var next commitState = scanHeaders
|
||||||
|
switch key {
|
||||||
|
case "tree", "parent", "author", "committer":
|
||||||
|
// Anything reaching scanHeaders with one of these keys is out of
|
||||||
|
// canonical position: duplicate tree, parent past the contiguous
|
||||||
|
// block, or author/committer not at their expected slot. Drop them
|
||||||
|
// the same way upstream's standard_header_field filter excludes
|
||||||
|
// them from the extras list (read_commit_extra_header_lines,
|
||||||
|
// commit.c:1520-1522).
|
||||||
|
case headerencoding:
|
||||||
|
if !s.sawEncoding {
|
||||||
|
s.c.Encoding = MessageEncoding(data)
|
||||||
|
s.sawEncoding = true
|
||||||
|
}
|
||||||
|
case headermergetag:
|
||||||
|
if s.sawMergetag {
|
||||||
|
next = scanSkipCont
|
||||||
|
} else {
|
||||||
|
s.c.MergeTag += string(data) + "\n"
|
||||||
|
s.sawMergetag = true
|
||||||
|
next = scanMergetagCont
|
||||||
|
}
|
||||||
|
case headerpgp:
|
||||||
|
s.c.PGPSignature += string(data) + "\n"
|
||||||
|
next = scanPgpCont
|
||||||
|
case headerpgp256:
|
||||||
|
next = scanSkipCont
|
||||||
|
default:
|
||||||
|
h, multiline := parseExtraHeader(originalLine)
|
||||||
|
if multiline {
|
||||||
|
s.extra = &h
|
||||||
|
next = scanExtraCont
|
||||||
|
} else {
|
||||||
|
s.c.ExtraHeaders = append(s.c.ExtraHeaders, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanMergetagCont accumulates continuation lines for the first mergetag
|
||||||
|
// header. Continuations strip exactly one leading space, mirroring upstream's
|
||||||
|
// `line + 1` (commit.c:1509). The first non-continuation line is pushed back
|
||||||
|
// so scanHeaders can dispatch it.
|
||||||
|
func scanMergetagCont(s *commitScanner) (commitState, error) {
|
||||||
|
return continuationCont(s, &s.c.MergeTag, scanMergetagCont)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanPgpCont accumulates continuation lines for a signature header.
|
||||||
|
// Continuations strip exactly one leading space, mirroring upstream's
|
||||||
|
// `line + 1` (commit.c:1509). The first non-continuation line is pushed back
|
||||||
|
// so scanHeaders can dispatch it. Repeat occurrences of the same signature
|
||||||
|
// header land back here and concatenate, matching upstream's
|
||||||
|
// parse_buffer_signed_by_header (commit.c:1186).
|
||||||
|
func scanPgpCont(s *commitScanner) (commitState, error) {
|
||||||
|
return continuationCont(s, &s.c.PGPSignature, scanPgpCont)
|
||||||
|
}
|
||||||
|
|
||||||
|
func continuationCont(s *commitScanner, dst *string, self commitState) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) > 0 && line[0] == ' ' {
|
||||||
|
*dst += string(line[1:])
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return self, nil
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
s.pushBack(line, err)
|
||||||
|
}
|
||||||
|
return scanHeaders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanSkipCont discards continuation lines that belong to a header scanHeaders
|
||||||
|
// chose to drop.
|
||||||
|
func scanSkipCont(s *commitScanner) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) > 0 && line[0] == ' ' {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanSkipCont, nil
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
s.pushBack(line, err)
|
||||||
|
}
|
||||||
|
return scanHeaders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanExtraCont accumulates continuation lines for an unknown ExtraHeader
|
||||||
|
// whose value spans multiple lines, then finalises the entry once the
|
||||||
|
// continuation block ends.
|
||||||
|
func scanExtraCont(s *commitScanner) (commitState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) > 0 && line[0] == ' ' {
|
||||||
|
s.extra.Value += string(line[1:])
|
||||||
|
if err == io.EOF {
|
||||||
|
s.finaliseExtra()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanExtraCont, nil
|
||||||
|
}
|
||||||
|
s.finaliseExtra()
|
||||||
|
if len(line) > 0 {
|
||||||
|
s.pushBack(line, err)
|
||||||
|
}
|
||||||
|
return scanHeaders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *commitScanner) finaliseExtra() {
|
||||||
|
s.extra.Value = strings.TrimRight(s.extra.Value, "\n")
|
||||||
|
s.c.ExtraHeaders = append(s.c.ExtraHeaders, *s.extra)
|
||||||
|
s.extra = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanMessage drains the remaining bytes into the message buffer.
|
||||||
|
func scanMessage(s *commitScanner) (commitState, error) {
|
||||||
|
for {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
s.msgbuf.Write(line)
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isBlankLine reports whether line is the canonical header/body separator:
|
||||||
|
// a single newline. Mirrors upstream's `*line == '\n'` test in
|
||||||
|
// read_commit_extra_header_lines (commit.c:1502).
|
||||||
|
func isBlankLine(line []byte) bool {
|
||||||
|
return len(line) == 1 && line[0] == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitHeader returns the header keyword (everything before the first space)
|
||||||
|
// and the value (everything after, with the trailing newline stripped). If
|
||||||
|
// the header has no value the returned data is nil.
|
||||||
|
func splitHeader(line []byte) (string, []byte) {
|
||||||
|
trimmed := bytes.TrimRight(line, "\n")
|
||||||
|
key, value, ok := bytes.Cut(trimmed, []byte{' '})
|
||||||
|
if !ok {
|
||||||
|
return string(trimmed), nil
|
||||||
|
}
|
||||||
|
return string(key), value
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseObjectIDHex(data []byte, malformedErr error, header string) (plumbing.Hash, error) {
|
||||||
|
id := string(data)
|
||||||
|
if !plumbing.IsHash(id) {
|
||||||
|
return plumbing.ZeroHash, fmt.Errorf("%w: bad %s hash", malformedErr, header)
|
||||||
|
}
|
||||||
|
return plumbing.NewHash(id), nil
|
||||||
|
}
|
||||||
+121
-1
@@ -1,6 +1,13 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import "bytes"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/go-git/go-git/v5/utils/ioutil"
|
||||||
|
"github.com/go-git/go-git/v5/utils/sync"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
signatureTypeUnknown signatureType = iota
|
signatureTypeUnknown signatureType = iota
|
||||||
@@ -100,3 +107,116 @@ func parseSignedBytes(b []byte) (int, signatureType) {
|
|||||||
}
|
}
|
||||||
return match, t
|
return match, t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// countSignatureBlocks reports how many distinct armored signature blocks
|
||||||
|
// start at a line boundary in b. Used by verification paths to reject
|
||||||
|
// multi-signature payloads, matching upstream's check in gpg-interface.c
|
||||||
|
// where parse_gpg_output bails out the first time it sees a second
|
||||||
|
// exclusive status line (a second GOODSIG/BADSIG/etc.).
|
||||||
|
func countSignatureBlocks(b []byte) int {
|
||||||
|
n, count := 0, 0
|
||||||
|
for n < len(b) {
|
||||||
|
i := b[n:]
|
||||||
|
if typeForSignature(i) != signatureTypeUnknown {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if eol := bytes.IndexByte(i, '\n'); eol >= 0 {
|
||||||
|
n += eol + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSignatureHeader reports whether line is a canonical "gpgsig "/
|
||||||
|
// "gpgsig-sha256 " header line. Other "gpgsig"-prefixed extra headers
|
||||||
|
// are intentionally not matched.
|
||||||
|
func isSignatureHeader(line []byte) bool {
|
||||||
|
return bytes.HasPrefix(line, []byte(headerpgp+" ")) ||
|
||||||
|
bytes.HasPrefix(line, []byte(headerpgp256+" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripObjectSignatures streams src into dst, producing the byte sequence
|
||||||
|
// over which a PGP/GPG signature is computed:
|
||||||
|
//
|
||||||
|
// - Canonical "gpgsig" and "gpgsig-sha256" headers (and their
|
||||||
|
// continuation lines) are dropped, mirroring upstream's
|
||||||
|
// remove_signature in commit.c.
|
||||||
|
// - For tag objects, the inline trailing PGP signature is additionally
|
||||||
|
// truncated, mirroring upstream's parse_signature in gpg-interface.c
|
||||||
|
// used by gpg_verify_tag.
|
||||||
|
//
|
||||||
|
// The returned object's type is set to objType. Used by both
|
||||||
|
// Commit.EncodeWithoutSignature and Tag.EncodeWithoutSignature to
|
||||||
|
// reproduce the exact bytes the signature was computed over.
|
||||||
|
func stripObjectSignatures(dst, src plumbing.EncodedObject, objType plumbing.ObjectType) (err error) {
|
||||||
|
dst.SetType(objType)
|
||||||
|
|
||||||
|
r, err := src.Reader()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ioutil.CheckClose(r, &err)
|
||||||
|
|
||||||
|
var input io.Reader = r
|
||||||
|
if objType == plumbing.TagObject {
|
||||||
|
raw, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if sm, _ := parseSignedBytes(raw); sm >= 0 {
|
||||||
|
raw = raw[:sm]
|
||||||
|
}
|
||||||
|
input = bytes.NewReader(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := dst.Writer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ioutil.CheckClose(w, &err)
|
||||||
|
|
||||||
|
return stripHeaderSignatures(w, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripHeaderSignatures copies r to w, dropping canonical signature header
|
||||||
|
// lines (gpgsig and gpgsig-sha256) and their continuation lines. Lines
|
||||||
|
// past the blank line that closes the header block are copied verbatim.
|
||||||
|
func stripHeaderSignatures(w io.Writer, r io.Reader) error {
|
||||||
|
br := sync.GetBufioReader(r)
|
||||||
|
defer sync.PutBufioReader(br)
|
||||||
|
|
||||||
|
var inBody, skipping bool
|
||||||
|
for {
|
||||||
|
line, rerr := br.ReadBytes('\n')
|
||||||
|
if rerr != nil && rerr != io.EOF {
|
||||||
|
return rerr
|
||||||
|
}
|
||||||
|
|
||||||
|
write := true
|
||||||
|
if !inBody {
|
||||||
|
switch {
|
||||||
|
case skipping && len(line) > 0 && line[0] == ' ':
|
||||||
|
write = false
|
||||||
|
case isSignatureHeader(line):
|
||||||
|
skipping = true
|
||||||
|
write = false
|
||||||
|
case len(line) == 1 && line[0] == '\n':
|
||||||
|
skipping = false
|
||||||
|
inBody = true
|
||||||
|
default:
|
||||||
|
skipping = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if write && len(line) > 0 {
|
||||||
|
if _, werr := w.Write(line); werr != nil {
|
||||||
|
return werr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rerr == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+90
-45
@@ -1,9 +1,8 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
@@ -13,6 +12,10 @@ import (
|
|||||||
"github.com/go-git/go-git/v5/utils/sync"
|
"github.com/go-git/go-git/v5/utils/sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrMalformedTag is returned when a tag object cannot be decoded because
|
||||||
|
// its required headers (object, type, tag) are missing or out of order.
|
||||||
|
var ErrMalformedTag = errors.New("malformed tag")
|
||||||
|
|
||||||
// Tag represents an annotated tag object. It points to a single git object of
|
// Tag represents an annotated tag object. It points to a single git object of
|
||||||
// any type, but tags typically are applied to commit or blob objects. It
|
// any type, but tags typically are applied to commit or blob objects. It
|
||||||
// provides a reference that associates the target with a tag name. It also
|
// provides a reference that associates the target with a tag name. It also
|
||||||
@@ -39,6 +42,9 @@ type Tag struct {
|
|||||||
Target plumbing.Hash
|
Target plumbing.Hash
|
||||||
|
|
||||||
s storer.EncodedObjectStorer
|
s storer.EncodedObjectStorer
|
||||||
|
// src holds the encoded object this Tag was decoded from, used by
|
||||||
|
// EncodeWithoutSignature to recover the canonical signed bytes.
|
||||||
|
src plumbing.EncodedObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTag gets a tag from an object storer and decodes it.
|
// GetTag gets a tag from an object storer and decodes it.
|
||||||
@@ -77,13 +83,20 @@ func (t *Tag) Type() plumbing.ObjectType {
|
|||||||
return plumbing.TagObject
|
return plumbing.TagObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tag) reset() {
|
||||||
|
storer := t.s
|
||||||
|
*t = Tag{s: storer}
|
||||||
|
}
|
||||||
|
|
||||||
// Decode transforms a plumbing.EncodedObject into a Tag struct.
|
// Decode transforms a plumbing.EncodedObject into a Tag struct.
|
||||||
func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
|
func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
|
||||||
if o.Type() != plumbing.TagObject {
|
if o.Type() != plumbing.TagObject {
|
||||||
return ErrUnsupportedObject
|
return ErrUnsupportedObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.reset()
|
||||||
t.Hash = o.Hash()
|
t.Hash = o.Hash()
|
||||||
|
t.src = o
|
||||||
|
|
||||||
reader, err := o.Reader()
|
reader, err := o.Reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,42 +107,15 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
|
|||||||
r := sync.GetBufioReader(reader)
|
r := sync.GetBufioReader(reader)
|
||||||
defer sync.PutBufioReader(r)
|
defer sync.PutBufioReader(r)
|
||||||
|
|
||||||
for {
|
scanner := &tagScanner{r: r, t: t}
|
||||||
var line []byte
|
for state := scanTagObject; state != nil; {
|
||||||
line, err = r.ReadBytes('\n')
|
state, err = state(scanner)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
line = bytes.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
break // Start of message
|
|
||||||
}
|
|
||||||
|
|
||||||
split := bytes.SplitN(line, []byte{' '}, 2)
|
|
||||||
switch string(split[0]) {
|
|
||||||
case "object":
|
|
||||||
t.Target = plumbing.NewHash(string(split[1]))
|
|
||||||
case "type":
|
|
||||||
t.TargetType, err = plumbing.ParseObjectType(string(split[1]))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "tag":
|
|
||||||
t.Name = string(split[1])
|
|
||||||
case "tagger":
|
|
||||||
t.Tagger.Decode(split[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(r)
|
data := scanner.msgbuf.Bytes()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if sm, _ := parseSignedBytes(data); sm >= 0 {
|
if sm, _ := parseSignedBytes(data); sm >= 0 {
|
||||||
t.PGPSignature = string(data[sm:])
|
t.PGPSignature = string(data[sm:])
|
||||||
data = data[:sm]
|
data = data[:sm]
|
||||||
@@ -144,11 +130,54 @@ func (t *Tag) Encode(o plumbing.EncodedObject) error {
|
|||||||
return t.encode(o, true)
|
return t.encode(o, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeWithoutSignature export a Tag into a plumbing.EncodedObject without the signature (correspond to the payload of the PGP signature).
|
// EncodeWithoutSignature exports a Tag into a plumbing.EncodedObject without
|
||||||
|
// any signature data, producing the payload that PGP/GPG signatures are
|
||||||
|
// computed over.
|
||||||
|
//
|
||||||
|
// Behaviour mirrors Commit.EncodeWithoutSignature:
|
||||||
|
//
|
||||||
|
// - For Tags populated by Decode whose exported fields still match the
|
||||||
|
// source object, the payload is streamed from the raw source bytes with
|
||||||
|
// the inline trailing signature truncated and gpgsig/gpgsig-sha256
|
||||||
|
// headers (and their continuation lines) stripped verbatim. This
|
||||||
|
// preserves the exact bytes the signature was computed over, regardless
|
||||||
|
// of any normalization performed by Decode.
|
||||||
|
//
|
||||||
|
// - For Tags constructed in memory, or for decoded Tags whose exported
|
||||||
|
// fields have been mutated, the payload is derived from the current
|
||||||
|
// struct fields. Mutation is detected by re-decoding the source object
|
||||||
|
// and comparing exported fields; if any differ, the in-memory
|
||||||
|
// representation prevails.
|
||||||
func (t *Tag) EncodeWithoutSignature(o plumbing.EncodedObject) error {
|
func (t *Tag) EncodeWithoutSignature(o plumbing.EncodedObject) error {
|
||||||
|
if t.matchesSource() {
|
||||||
|
return stripObjectSignatures(o, t.src, plumbing.TagObject)
|
||||||
|
}
|
||||||
return t.encode(o, false)
|
return t.encode(o, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matchesSource reports whether t.src is set and re-decoding it produces a
|
||||||
|
// Tag whose payload-affecting exported fields are identical to those of t.
|
||||||
|
//
|
||||||
|
// PGPSignature is intentionally excluded from the comparison: neither path
|
||||||
|
// emits it as part of the verification payload, so mutating it must not
|
||||||
|
// trigger a switch to struct-encode (which would change the byte layout the
|
||||||
|
// caller is trying to verify against).
|
||||||
|
func (t *Tag) matchesSource() bool {
|
||||||
|
if t.src == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fresh := &Tag{}
|
||||||
|
if err := fresh.Decode(t.src); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Hash == fresh.Hash &&
|
||||||
|
t.Name == fresh.Name &&
|
||||||
|
signatureEqual(t.Tagger, fresh.Tagger) &&
|
||||||
|
t.Message == fresh.Message &&
|
||||||
|
t.TargetType == fresh.TargetType &&
|
||||||
|
t.Target == fresh.Target
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
||||||
o.SetType(plumbing.TagObject)
|
o.SetType(plumbing.TagObject)
|
||||||
w, err := o.Writer()
|
w, err := o.Writer()
|
||||||
@@ -158,16 +187,26 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
|||||||
defer ioutil.CheckClose(w, &err)
|
defer ioutil.CheckClose(w, &err)
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(w,
|
if _, err = fmt.Fprintf(w,
|
||||||
"object %s\ntype %s\ntag %s\ntagger ",
|
"object %s\ntype %s\ntag %s\n",
|
||||||
t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
|
t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = t.Tagger.Encode(w); err != nil {
|
if !isZeroSignature(t.Tagger) {
|
||||||
return err
|
if _, err = fmt.Fprint(w, "tagger "); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.Tagger.Encode(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = fmt.Fprint(w, "\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = fmt.Fprint(w, "\n\n"); err != nil {
|
if _, err = fmt.Fprint(w, "\n"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +214,12 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this is highly sensitive to what it sent along in the message.
|
// Note that this is highly sensitive to what is sent along in the
|
||||||
// Message *always* needs to end with a newline, or else the message and the
|
// message. Message *always* needs to end with a newline, or else the
|
||||||
// signature will be concatenated into a corrupt object. Since this is a
|
// message and the trailing signature will be concatenated into a
|
||||||
// lower-level method, we assume you know what you are doing and have already
|
// corrupt object. Since this is a lower-level method, we assume you
|
||||||
// done the needful on the message in the caller.
|
// know what you are doing and have already done the needful on the
|
||||||
|
// message in the caller.
|
||||||
if includeSig {
|
if includeSig {
|
||||||
if _, err = fmt.Fprint(w, t.PGPSignature); err != nil {
|
if _, err = fmt.Fprint(w, t.PGPSignature); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -189,6 +229,10 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isZeroSignature(s Signature) bool {
|
||||||
|
return s.Name == "" && s.Email == "" && s.When.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
// Commit returns the commit pointed to by the tag. If the tag points to a
|
// Commit returns the commit pointed to by the tag. If the tag points to a
|
||||||
// different type of object ErrUnsupportedObject will be returned.
|
// different type of object ErrUnsupportedObject will be returned.
|
||||||
func (t *Tag) Commit() (*Commit, error) {
|
func (t *Tag) Commit() (*Commit, error) {
|
||||||
@@ -256,7 +300,8 @@ func (t *Tag) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify performs PGP verification of the tag with a provided armored
|
// Verify performs PGP verification of the tag with a provided armored
|
||||||
// keyring and returns openpgp.Entity associated with verifying key on success.
|
// keyring and returns openpgp.Entity associated with verifying key on
|
||||||
|
// success.
|
||||||
func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
|
func (t *Tag) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
|
||||||
keyRingReader := strings.NewReader(armoredKeyRing)
|
keyRingReader := strings.NewReader(armoredKeyRing)
|
||||||
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
|
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
|
||||||
|
|||||||
+237
@@ -0,0 +1,237 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tagScanner holds the working state of the tag decoder driven by the
|
||||||
|
// stateFn loop in (*Tag).Decode. Each tagState reads one or more lines
|
||||||
|
// from r, updates the in-progress *Tag and the scanner's bookkeeping,
|
||||||
|
// and returns the state that should run next (or nil to stop).
|
||||||
|
type tagScanner struct {
|
||||||
|
r *bufio.Reader
|
||||||
|
t *Tag
|
||||||
|
msgbuf bytes.Buffer
|
||||||
|
|
||||||
|
// pending holds a line that was read but the current state decided to
|
||||||
|
// hand back to the next state, paired with the io.EOF flag returned
|
||||||
|
// when the line was originally read.
|
||||||
|
pending []byte
|
||||||
|
pendingErr error
|
||||||
|
|
||||||
|
// First-occurrence tracking: once the corresponding canonical
|
||||||
|
// header has been decoded at its expected position, subsequent
|
||||||
|
// occurrences (or out-of-position lines) are silently dropped,
|
||||||
|
// matching the strict layout enforced by upstream's
|
||||||
|
// parse_tag_buffer (tag.c:130).
|
||||||
|
//
|
||||||
|
// gpgsig-sha256 is recognized and skipped without exposing a new field
|
||||||
|
// in v5.
|
||||||
|
sawObject, sawType, sawName, sawTagger bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// tagState is one step of the decoder state machine. Each function reads
|
||||||
|
// the lines it needs, mutates *Tag via s.t, and returns the next state
|
||||||
|
// to run (or nil to terminate the loop).
|
||||||
|
type tagState func(*tagScanner) (tagState, error)
|
||||||
|
|
||||||
|
// readLine returns the next line from the buffer, transparently
|
||||||
|
// consuming any line that was previously pushed back by a state that
|
||||||
|
// decided not to handle it.
|
||||||
|
func (s *tagScanner) readLine() ([]byte, error) {
|
||||||
|
if s.pending != nil {
|
||||||
|
line, err := s.pending, s.pendingErr
|
||||||
|
s.pending, s.pendingErr = nil, nil
|
||||||
|
return line, err
|
||||||
|
}
|
||||||
|
return s.r.ReadBytes('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushBack stashes an unconsumed line so the next state's readLine call
|
||||||
|
// sees it. Only one line can be pushed back at a time.
|
||||||
|
func (s *tagScanner) pushBack(line []byte, err error) {
|
||||||
|
s.pending = line
|
||||||
|
s.pendingErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTagObject requires the first line to be `object HASH`, mirroring
|
||||||
|
// upstream's strict parse_tag_buffer (tag.c:151-156). Anything else
|
||||||
|
// returns ErrMalformedTag.
|
||||||
|
func scanTagObject(s *tagScanner) (tagState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 || isBlankLine(line) {
|
||||||
|
return nil, fmt.Errorf("%w: missing object header", ErrMalformedTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key != "object" {
|
||||||
|
return nil, fmt.Errorf("%w: object header must be first", ErrMalformedTag)
|
||||||
|
}
|
||||||
|
h, herr := parseObjectIDHex(data, ErrMalformedTag, "object")
|
||||||
|
if herr != nil {
|
||||||
|
return nil, herr
|
||||||
|
}
|
||||||
|
s.t.Target = h
|
||||||
|
s.sawObject = true
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanTagType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTagType requires a `type` line immediately after the object header,
|
||||||
|
// mirroring upstream's parse_tag_buffer (tag.c:158-166).
|
||||||
|
func scanTagType(s *tagScanner) (tagState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 || isBlankLine(line) {
|
||||||
|
return nil, fmt.Errorf("%w: missing type header", ErrMalformedTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key != "type" {
|
||||||
|
return nil, fmt.Errorf("%w: type header must follow object", ErrMalformedTag)
|
||||||
|
}
|
||||||
|
ot, perr := plumbing.ParseObjectType(string(data))
|
||||||
|
if perr != nil {
|
||||||
|
return nil, perr
|
||||||
|
}
|
||||||
|
s.t.TargetType = ot
|
||||||
|
s.sawType = true
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanTagName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTagName requires a `tag` line immediately after the type header,
|
||||||
|
// mirroring upstream's parse_tag_buffer (tag.c:186-194).
|
||||||
|
func scanTagName(s *tagScanner) (tagState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 || isBlankLine(line) {
|
||||||
|
return nil, fmt.Errorf("%w: missing tag header", ErrMalformedTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key != "tag" {
|
||||||
|
return nil, fmt.Errorf("%w: tag header must follow type", ErrMalformedTag)
|
||||||
|
}
|
||||||
|
s.t.Name = string(data)
|
||||||
|
s.sawName = true
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanTagTagger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTagTagger accepts a `tagger` line at its canonical position. Any
|
||||||
|
// other header is pushed back for scanTagHeaders.
|
||||||
|
func scanTagTagger(s *tagScanner) (tagState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if isBlankLine(line) {
|
||||||
|
return scanTagMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key, data := splitHeader(line)
|
||||||
|
if key == "tagger" {
|
||||||
|
s.t.Tagger.Decode(data)
|
||||||
|
s.sawTagger = true
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanTagHeaders, nil
|
||||||
|
}
|
||||||
|
s.pushBack(line, err)
|
||||||
|
return scanTagHeaders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTagHeaders dispatches one header line. gpgsig-sha256 hands off to
|
||||||
|
// scanTagSkipCont so the continuation block can be consumed; out-of-position
|
||||||
|
// canonical fields and unknown headers are silently dropped.
|
||||||
|
func scanTagHeaders(s *tagScanner) (tagState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if isBlankLine(line) {
|
||||||
|
return scanTagMessage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _ := splitHeader(line)
|
||||||
|
next := scanTagHeaders
|
||||||
|
switch key {
|
||||||
|
case "object", "type", "tag", "tagger":
|
||||||
|
// Out-of-canonical-position duplicates are dropped, mirroring the
|
||||||
|
// strict ordering of upstream's parse_tag_buffer.
|
||||||
|
case headerpgp256:
|
||||||
|
next = scanTagSkipCont
|
||||||
|
default:
|
||||||
|
// Unknown header: silently dropped (the Tag struct does not
|
||||||
|
// expose ExtraHeaders).
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTagSkipCont discards continuation lines for a header scanTagHeaders chose
|
||||||
|
// to drop. The first non-continuation line is pushed back so scanTagHeaders can
|
||||||
|
// dispatch it.
|
||||||
|
func scanTagSkipCont(s *tagScanner) (tagState, error) {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) > 0 && line[0] == ' ' {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return scanTagSkipCont, nil
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
s.pushBack(line, err)
|
||||||
|
}
|
||||||
|
return scanTagHeaders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanTagMessage drains the remaining bytes into the message buffer.
|
||||||
|
// (*Tag).Decode then runs parseSignedBytes over those bytes to peel off
|
||||||
|
// the optional inline trailing PGP signature.
|
||||||
|
func scanTagMessage(s *tagScanner) (tagState, error) {
|
||||||
|
for {
|
||||||
|
line, err := s.readLine()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
s.msgbuf.Write(line)
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
-33
@@ -10,6 +10,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/internal/pathutil"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||||
"github.com/go-git/go-git/v5/plumbing/storer"
|
"github.com/go-git/go-git/v5/plumbing/storer"
|
||||||
@@ -29,6 +30,7 @@ var (
|
|||||||
ErrDirectoryNotFound = errors.New("directory not found")
|
ErrDirectoryNotFound = errors.New("directory not found")
|
||||||
ErrEntryNotFound = errors.New("entry not found")
|
ErrEntryNotFound = errors.New("entry not found")
|
||||||
ErrEntriesNotSorted = errors.New("entries in tree are not sorted")
|
ErrEntriesNotSorted = errors.New("entries in tree are not sorted")
|
||||||
|
ErrMalformedTree = errors.New("malformed tree")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tree is basically like a directory - it references a bunch of other trees
|
// Tree is basically like a directory - it references a bunch of other trees
|
||||||
@@ -37,9 +39,9 @@ type Tree struct {
|
|||||||
Entries []TreeEntry
|
Entries []TreeEntry
|
||||||
Hash plumbing.Hash
|
Hash plumbing.Hash
|
||||||
|
|
||||||
s storer.EncodedObjectStorer
|
s storer.EncodedObjectStorer
|
||||||
m map[string]*TreeEntry
|
t map[string]*Tree // tree path cache
|
||||||
t map[string]*Tree // tree path cache
|
entriesSorted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTree gets a tree from an object storer and decodes it.
|
// GetTree gets a tree from an object storer and decodes it.
|
||||||
@@ -117,7 +119,16 @@ func (t *Tree) Tree(path string) (*Tree, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TreeEntryFile returns the *File for a given *TreeEntry.
|
// TreeEntryFile returns the *File for a given *TreeEntry.
|
||||||
|
//
|
||||||
|
// The entry's name is validated against pathutil.ValidTreePath for
|
||||||
|
// the same reason FindEntry validates: TreeEntryFile is a boundary
|
||||||
|
// where attacker-controlled tree data leaves the trusted store as a
|
||||||
|
// *File whose Name a caller can hand to filesystem ops.
|
||||||
func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) {
|
func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) {
|
||||||
|
if err := pathutil.ValidTreePath(e.Name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
blob, err := GetBlob(t.s, e.Hash)
|
blob, err := GetBlob(t.s, e.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -127,7 +138,16 @@ func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindEntry search a TreeEntry in this tree or any subtree.
|
// FindEntry search a TreeEntry in this tree or any subtree.
|
||||||
|
//
|
||||||
|
// The lookup path is validated against pathutil.ValidTreePath to
|
||||||
|
// prevent attacker-controlled tree contents from leaking past this
|
||||||
|
// boundary as `.git`-shaped or path-traversal-shaped names. Callers
|
||||||
|
// that legitimately need to look up unsafe paths should walk the
|
||||||
|
// tree manually.
|
||||||
func (t *Tree) FindEntry(path string) (*TreeEntry, error) {
|
func (t *Tree) FindEntry(path string) (*TreeEntry, error) {
|
||||||
|
if err := pathutil.ValidTreePath(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if t.t == nil {
|
if t.t == nil {
|
||||||
t.t = make(map[string]*Tree)
|
t.t = make(map[string]*Tree)
|
||||||
}
|
}
|
||||||
@@ -182,16 +202,43 @@ func (t *Tree) dir(baseName string) (*Tree, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tree) entry(baseName string) (*TreeEntry, error) {
|
func (t *Tree) entry(baseName string) (*TreeEntry, error) {
|
||||||
if t.m == nil {
|
if t.entriesSorted {
|
||||||
t.buildMap()
|
if entry := t.searchEntry(baseName); entry != nil {
|
||||||
}
|
return entry, nil
|
||||||
|
}
|
||||||
entry, ok := t.m[baseName]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrEntryNotFound
|
return nil, ErrEntryNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry, nil
|
pastName := baseName + "/"
|
||||||
|
for i := range t.Entries {
|
||||||
|
entry := &t.Entries[i]
|
||||||
|
if entry.Name == baseName {
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
if treeEntrySortName(entry) > pastName {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrEntryNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) searchEntry(baseName string) *TreeEntry {
|
||||||
|
if i := t.searchEntryIndex(baseName); i < len(t.Entries) && t.Entries[i].Name == baseName {
|
||||||
|
return &t.Entries[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if i := t.searchEntryIndex(baseName + "/"); i < len(t.Entries) && t.Entries[i].Name == baseName {
|
||||||
|
return &t.Entries[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) searchEntryIndex(name string) int {
|
||||||
|
return sort.Search(len(t.Entries), func(i int) bool {
|
||||||
|
return treeEntrySortName(&t.Entries[i]) >= name
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files returns a FileIter allowing to iterate over the Tree
|
// Files returns a FileIter allowing to iterate over the Tree
|
||||||
@@ -212,20 +259,25 @@ func (t *Tree) Type() plumbing.ObjectType {
|
|||||||
return plumbing.TreeObject
|
return plumbing.TreeObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tree) reset() {
|
||||||
|
storer := t.s
|
||||||
|
*t = Tree{s: storer}
|
||||||
|
}
|
||||||
|
|
||||||
// Decode transform an plumbing.EncodedObject into a Tree struct
|
// Decode transform an plumbing.EncodedObject into a Tree struct
|
||||||
func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
|
func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
|
||||||
if o.Type() != plumbing.TreeObject {
|
if o.Type() != plumbing.TreeObject {
|
||||||
return ErrUnsupportedObject
|
return ErrUnsupportedObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.reset()
|
||||||
t.Hash = o.Hash()
|
t.Hash = o.Hash()
|
||||||
|
// assume tree is sorted as a valid tree should always be sorted.
|
||||||
|
t.entriesSorted = true
|
||||||
if o.Size() == 0 {
|
if o.Size() == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Entries = nil
|
|
||||||
t.m = nil
|
|
||||||
|
|
||||||
reader, err := o.Reader()
|
reader, err := o.Reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -235,10 +287,14 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
|
|||||||
r := sync.GetBufioReader(reader)
|
r := sync.GetBufioReader(reader)
|
||||||
defer sync.PutBufioReader(r)
|
defer sync.PutBufioReader(r)
|
||||||
|
|
||||||
|
var prevSortName string
|
||||||
for {
|
for {
|
||||||
str, err := r.ReadString(' ')
|
str, err := r.ReadString(' ')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
if len(str) != 0 {
|
||||||
|
return fmt.Errorf("%w: missing mode terminator", ErrMalformedTree)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,25 +304,41 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
|
|||||||
|
|
||||||
mode, err := filemode.New(str)
|
mode, err := filemode.New(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("%w: malformed mode", ErrMalformedTree)
|
||||||
}
|
}
|
||||||
|
mode = canonicalTreeMode(mode)
|
||||||
|
|
||||||
name, err := r.ReadString(0)
|
name, err := r.ReadString(0)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return fmt.Errorf("%w: missing filename terminator", ErrMalformedTree)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(name) == 1 {
|
||||||
|
return fmt.Errorf("%w: empty filename", ErrMalformedTree)
|
||||||
|
}
|
||||||
|
|
||||||
var hash plumbing.Hash
|
var hash plumbing.Hash
|
||||||
if _, err = io.ReadFull(r, hash[:]); err != nil {
|
if _, err = io.ReadFull(r, hash[:]); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return fmt.Errorf("%w: truncated object id", ErrMalformedTree)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseName := name[:len(name)-1]
|
baseName := name[:len(name)-1]
|
||||||
t.Entries = append(t.Entries, TreeEntry{
|
entry := TreeEntry{
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Name: baseName,
|
Name: baseName,
|
||||||
})
|
}
|
||||||
|
sortName := treeEntrySortName(&entry)
|
||||||
|
if len(t.Entries) != 0 && prevSortName > sortName {
|
||||||
|
t.entriesSorted = false
|
||||||
|
}
|
||||||
|
prevSortName = sortName
|
||||||
|
t.Entries = append(t.Entries, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -279,21 +351,37 @@ func (s TreeEntrySorter) Len() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s TreeEntrySorter) Less(i, j int) bool {
|
func (s TreeEntrySorter) Less(i, j int) bool {
|
||||||
name1 := s[i].Name
|
return treeEntrySortName(&s[i]) < treeEntrySortName(&s[j])
|
||||||
name2 := s[j].Name
|
|
||||||
if s[i].Mode == filemode.Dir {
|
|
||||||
name1 += "/"
|
|
||||||
}
|
|
||||||
if s[j].Mode == filemode.Dir {
|
|
||||||
name2 += "/"
|
|
||||||
}
|
|
||||||
return name1 < name2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TreeEntrySorter) Swap(i, j int) {
|
func (s TreeEntrySorter) Swap(i, j int) {
|
||||||
s[i], s[j] = s[j], s[i]
|
s[i], s[j] = s[j], s[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Git compares tree entries as if directory names had a trailing slash.
|
||||||
|
func treeEntrySortName(e *TreeEntry) string {
|
||||||
|
if e.Mode == filemode.Dir {
|
||||||
|
return e.Name + "/"
|
||||||
|
}
|
||||||
|
return e.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalTreeMode(mode filemode.FileMode) filemode.FileMode {
|
||||||
|
switch mode & 0o170000 {
|
||||||
|
case 0o040000:
|
||||||
|
return filemode.Dir
|
||||||
|
case 0o100000:
|
||||||
|
if mode&0o111 != 0 {
|
||||||
|
return filemode.Executable
|
||||||
|
}
|
||||||
|
return filemode.Regular
|
||||||
|
case 0o120000:
|
||||||
|
return filemode.Symlink
|
||||||
|
default:
|
||||||
|
return filemode.Submodule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Encode transforms a Tree into a plumbing.EncodedObject.
|
// Encode transforms a Tree into a plumbing.EncodedObject.
|
||||||
// The tree entries must be sorted by name.
|
// The tree entries must be sorted by name.
|
||||||
func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
|
func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
|
||||||
@@ -329,13 +417,6 @@ func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tree) buildMap() {
|
|
||||||
t.m = make(map[string]*TreeEntry)
|
|
||||||
for i := 0; i < len(t.Entries); i++ {
|
|
||||||
t.m[t.Entries[i].Name] = &t.Entries[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff returns a list of changes between this tree and the provided one
|
// Diff returns a list of changes between this tree and the provided one
|
||||||
func (t *Tree) Diff(to *Tree) (Changes, error) {
|
func (t *Tree) Diff(to *Tree) (Changes, error) {
|
||||||
return t.DiffContext(context.Background(), to)
|
return t.DiffContext(context.Background(), to)
|
||||||
@@ -455,6 +536,10 @@ func (w *TreeWalker) Next() (name string, entry TreeEntry, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := pathutil.ValidTreePath(entry.Name); err != nil {
|
||||||
|
return name, entry, err
|
||||||
|
}
|
||||||
|
|
||||||
if entry.Mode == filemode.Dir {
|
if entry.Mode == filemode.Dir {
|
||||||
obj, err = GetTree(w.s, entry.Hash)
|
obj, err = GetTree(w.s, entry.Hash)
|
||||||
}
|
}
|
||||||
|
|||||||
+147
-21
@@ -7,7 +7,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -24,6 +23,33 @@ import (
|
|||||||
"github.com/go-git/go-git/v5/utils/ioutil"
|
"github.com/go-git/go-git/v5/utils/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type contextKey int
|
||||||
|
|
||||||
|
const initialRequestKey contextKey = iota
|
||||||
|
|
||||||
|
// RedirectPolicy controls how the HTTP transport follows redirects.
|
||||||
|
//
|
||||||
|
// The values mirror Git's http.followRedirects config:
|
||||||
|
// "true" follows redirects for all requests, "false" treats redirects as
|
||||||
|
// errors, and "initial" follows redirects only for the initial
|
||||||
|
// /info/refs discovery request. The zero value defaults to "initial".
|
||||||
|
type RedirectPolicy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FollowInitialRedirects RedirectPolicy = "initial"
|
||||||
|
FollowRedirects RedirectPolicy = "true"
|
||||||
|
NoFollowRedirects RedirectPolicy = "false"
|
||||||
|
)
|
||||||
|
|
||||||
|
func withInitialRequest(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, initialRequestKey, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInitialRequest(req *http.Request) bool {
|
||||||
|
v, _ := req.Context().Value(initialRequestKey).(bool)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// it requires a bytes.Buffer, because we need to know the length
|
// it requires a bytes.Buffer, because we need to know the length
|
||||||
func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) {
|
func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) {
|
||||||
req.Header.Add("User-Agent", capability.DefaultAgent())
|
req.Header.Add("User-Agent", capability.DefaultAgent())
|
||||||
@@ -54,12 +80,15 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) (
|
|||||||
|
|
||||||
s.ApplyAuthToRequest(req)
|
s.ApplyAuthToRequest(req)
|
||||||
applyHeadersToRequest(req, nil, s.endpoint.Host, serviceName)
|
applyHeadersToRequest(req, nil, s.endpoint.Host, serviceName)
|
||||||
res, err := s.client.Do(req.WithContext(ctx))
|
res, err := s.client.Do(req.WithContext(withInitialRequest(ctx)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ModifyEndpointIfRedirect(res)
|
if err := s.ModifyEndpointIfRedirect(res); err != nil {
|
||||||
|
_ = res.Body.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
defer ioutil.CheckClose(res.Body, &err)
|
defer ioutil.CheckClose(res.Body, &err)
|
||||||
|
|
||||||
if err = NewErr(res); err != nil {
|
if err = NewErr(res); err != nil {
|
||||||
@@ -96,6 +125,7 @@ type client struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
transports *lru.Cache
|
transports *lru.Cache
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
follow RedirectPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientOptions holds user configurable options for the client.
|
// ClientOptions holds user configurable options for the client.
|
||||||
@@ -106,6 +136,11 @@ type ClientOptions struct {
|
|||||||
// size, will result in the least recently used transport getting deleted
|
// size, will result in the least recently used transport getting deleted
|
||||||
// before the provided transport is added to the cache.
|
// before the provided transport is added to the cache.
|
||||||
CacheMaxEntries int
|
CacheMaxEntries int
|
||||||
|
|
||||||
|
// RedirectPolicy controls redirect handling. Supported values are
|
||||||
|
// "true", "false", and "initial". The zero value defaults to
|
||||||
|
// "initial", matching Git's http.followRedirects default.
|
||||||
|
RedirectPolicy RedirectPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -150,12 +185,16 @@ func NewClientWithOptions(c *http.Client, opts *ClientOptions) transport.Transpo
|
|||||||
}
|
}
|
||||||
cl := &client{
|
cl := &client{
|
||||||
client: c,
|
client: c,
|
||||||
|
follow: FollowInitialRedirects,
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
if opts.CacheMaxEntries > 0 {
|
if opts.CacheMaxEntries > 0 {
|
||||||
cl.transports = lru.New(opts.CacheMaxEntries)
|
cl.transports = lru.New(opts.CacheMaxEntries)
|
||||||
}
|
}
|
||||||
|
if opts.RedirectPolicy != "" {
|
||||||
|
cl.follow = opts.RedirectPolicy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cl
|
return cl
|
||||||
}
|
}
|
||||||
@@ -289,14 +328,9 @@ func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient = &http.Client{
|
httpClient = c.cloneHTTPClient(transport)
|
||||||
Transport: transport,
|
|
||||||
CheckRedirect: c.client.CheckRedirect,
|
|
||||||
Jar: c.client.Jar,
|
|
||||||
Timeout: c.client.Timeout,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
httpClient = c.client
|
httpClient = c.cloneHTTPClient(c.client.Transport)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &session{
|
s := &session{
|
||||||
@@ -324,30 +358,122 @@ func (s *session) ApplyAuthToRequest(req *http.Request) {
|
|||||||
s.auth.SetAuth(req)
|
s.auth.SetAuth(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) ModifyEndpointIfRedirect(res *http.Response) {
|
func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
|
||||||
if res.Request == nil {
|
if res.Request == nil {
|
||||||
return
|
return nil
|
||||||
|
}
|
||||||
|
if s.endpoint == nil {
|
||||||
|
return fmt.Errorf("http redirect: nil endpoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
r := res.Request
|
r := res.Request
|
||||||
if !strings.HasSuffix(r.URL.Path, infoRefsPath) {
|
if !strings.HasSuffix(r.URL.Path, infoRefsPath) {
|
||||||
return
|
return fmt.Errorf("http redirect: target %q does not end with %s", r.URL.Path, infoRefsPath)
|
||||||
|
}
|
||||||
|
if r.URL.Scheme != "http" && r.URL.Scheme != "https" {
|
||||||
|
return fmt.Errorf("http redirect: unsupported scheme %q", r.URL.Scheme)
|
||||||
|
}
|
||||||
|
if r.URL.Scheme != s.endpoint.Protocol &&
|
||||||
|
!(s.endpoint.Protocol == "http" && r.URL.Scheme == "https") {
|
||||||
|
return fmt.Errorf("http redirect: changes scheme from %q to %q", s.endpoint.Protocol, r.URL.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
h, p, err := net.SplitHostPort(r.URL.Host)
|
host := endpointHost(r.URL.Hostname())
|
||||||
|
port, err := endpointPort(r.URL.Port())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h = r.URL.Host
|
return err
|
||||||
}
|
}
|
||||||
if p != "" {
|
|
||||||
port, err := strconv.Atoi(p)
|
if host != s.endpoint.Host || effectivePort(r.URL.Scheme, port) != effectivePort(s.endpoint.Protocol, s.endpoint.Port) {
|
||||||
if err == nil {
|
s.endpoint.User = ""
|
||||||
s.endpoint.Port = port
|
s.endpoint.Password = ""
|
||||||
}
|
s.auth = nil
|
||||||
}
|
}
|
||||||
s.endpoint.Host = h
|
|
||||||
|
s.endpoint.Host = host
|
||||||
|
s.endpoint.Port = port
|
||||||
|
|
||||||
s.endpoint.Protocol = r.URL.Scheme
|
s.endpoint.Protocol = r.URL.Scheme
|
||||||
s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)]
|
s.endpoint.Path = r.URL.Path[:len(r.URL.Path)-len(infoRefsPath)]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func endpointHost(host string) string {
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
return "[" + host + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
|
func endpointPort(port string) (int, error) {
|
||||||
|
if port == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("http redirect: invalid port %q", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func effectivePort(scheme string, port int) int {
|
||||||
|
if port != 0 {
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(scheme) {
|
||||||
|
case "http":
|
||||||
|
return 80
|
||||||
|
case "https":
|
||||||
|
return 443
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) cloneHTTPClient(transport http.RoundTripper) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
CheckRedirect: wrapCheckRedirect(c.follow, c.client.CheckRedirect),
|
||||||
|
Jar: c.client.Jar,
|
||||||
|
Timeout: c.client.Timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapCheckRedirect(policy RedirectPolicy, next func(*http.Request, []*http.Request) error) func(*http.Request, []*http.Request) error {
|
||||||
|
return func(req *http.Request, via []*http.Request) error {
|
||||||
|
if err := checkRedirect(req, via, policy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if next != nil {
|
||||||
|
return next(req, via)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRedirect(req *http.Request, via []*http.Request, policy RedirectPolicy) error {
|
||||||
|
switch policy {
|
||||||
|
case FollowRedirects:
|
||||||
|
case NoFollowRedirects:
|
||||||
|
return fmt.Errorf("http redirect: redirects disabled to %s", req.URL)
|
||||||
|
case "", FollowInitialRedirects:
|
||||||
|
if !isInitialRequest(req) {
|
||||||
|
return fmt.Errorf("http redirect: redirect on non-initial request to %s", req.URL)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("http redirect: invalid redirect policy %q", policy)
|
||||||
|
}
|
||||||
|
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
|
||||||
|
return fmt.Errorf("http redirect: unsupported scheme %q", req.URL.Scheme)
|
||||||
|
}
|
||||||
|
if len(via) >= 10 {
|
||||||
|
return fmt.Errorf("http redirect: too many redirects")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*session) Close() error {
|
func (*session) Close() error {
|
||||||
|
|||||||
+33
-1
@@ -252,7 +252,39 @@ func (c *command) setAuthFromEndpoint() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func endpointToCommand(cmd string, ep *transport.Endpoint) string {
|
func endpointToCommand(cmd string, ep *transport.Endpoint) string {
|
||||||
return fmt.Sprintf("%s '%s'", cmd, ep.Path)
|
var b strings.Builder
|
||||||
|
b.WriteString(cmd)
|
||||||
|
b.WriteByte(' ')
|
||||||
|
writeShellQuote(&b, ep.Path)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeShellQuote writes s to b, wrapped in single quotes with
|
||||||
|
// embedded single quotes and exclamation marks escaped using the
|
||||||
|
// POSIX close-escape-reopen idiom:
|
||||||
|
//
|
||||||
|
// ' becomes '\''
|
||||||
|
// ! becomes '\!'
|
||||||
|
//
|
||||||
|
// It is a direct port of canonical Git's sq_quote_buf (quote.c).
|
||||||
|
// The bang escape keeps the result safe when re-evaluated under
|
||||||
|
// csh-derived shells that perform history expansion. The output is
|
||||||
|
// safe to pass as a single argument through any POSIX shell and
|
||||||
|
// round-trips through git-shell's sq_dequote_to_argv.
|
||||||
|
func writeShellQuote(b *strings.Builder, s string) {
|
||||||
|
b.Grow(len(s) + 2)
|
||||||
|
b.WriteByte('\'')
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c == '\'' || c == '!' {
|
||||||
|
b.WriteString(`'\`)
|
||||||
|
b.WriteByte(c)
|
||||||
|
b.WriteByte('\'')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.WriteByte(c)
|
||||||
|
}
|
||||||
|
b.WriteByte('\'')
|
||||||
}
|
}
|
||||||
|
|
||||||
func overrideConfig(overrides *ssh.ClientConfig, c *ssh.ClientConfig) {
|
func overrideConfig(overrides *ssh.ClientConfig, c *ssh.ClientConfig) {
|
||||||
|
|||||||
+12
-1
@@ -1530,7 +1530,18 @@ func (r *Repository) Worktree() (*Worktree, error) {
|
|||||||
return nil, ErrIsBareRepository
|
return nil, ErrIsBareRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Worktree{r: r, Filesystem: r.wt}, nil
|
protectNTFS := defaultProtectNTFS()
|
||||||
|
protectHFS := defaultProtectHFS()
|
||||||
|
if cfg, err := r.Config(); err == nil {
|
||||||
|
if cfg.Core.ProtectNTFS.IsSet() {
|
||||||
|
protectNTFS = cfg.Core.ProtectNTFS.IsTrue()
|
||||||
|
}
|
||||||
|
if cfg.Core.ProtectHFS.IsSet() {
|
||||||
|
protectHFS = cfg.Core.ProtectHFS.IsTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Worktree{r: r, Filesystem: newWorktreeFilesystem(r.wt, protectNTFS, protectHFS)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) {
|
func expand_ref(s storer.ReferenceStorer, ref plumbing.ReferenceName) (*plumbing.Reference, error) {
|
||||||
|
|||||||
+17
-2
@@ -75,6 +75,10 @@ var (
|
|||||||
// ErrEmptyRefFile is returned when a reference file is attempted to be read,
|
// ErrEmptyRefFile is returned when a reference file is attempted to be read,
|
||||||
// but the file is empty
|
// but the file is empty
|
||||||
ErrEmptyRefFile = errors.New("ref file is empty")
|
ErrEmptyRefFile = errors.New("ref file is empty")
|
||||||
|
// ErrModuleNameEscape is returned when a submodule name would
|
||||||
|
// resolve outside the modules/ subtree, mirroring canonical Git's
|
||||||
|
// "ignoring suspicious submodule name" defence.
|
||||||
|
ErrModuleNameEscape = errors.New("submodule name escapes modules/ directory")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options holds configuration for the storage.
|
// Options holds configuration for the storage.
|
||||||
@@ -1127,9 +1131,20 @@ func (d *DotGit) PackRefs() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module return a billy.Filesystem pointing to the module folder
|
// Module returns a billy.Filesystem pointing to the module folder.
|
||||||
|
//
|
||||||
|
// As a defence in depth against submodule name path traversal,
|
||||||
|
// refuse names whose joined path leaves the modules/ subtree once
|
||||||
|
// cleaned. The config-layer parser also validates submodule names,
|
||||||
|
// but Module may be reached from any caller that constructs a
|
||||||
|
// Submodule struct programmatically and so bypasses the parser.
|
||||||
func (d *DotGit) Module(name string) (billy.Filesystem, error) {
|
func (d *DotGit) Module(name string) (billy.Filesystem, error) {
|
||||||
return d.fs.Chroot(d.fs.Join(modulePath, name))
|
p := d.fs.Join(modulePath, name)
|
||||||
|
cleaned := path.Clean(filepath.ToSlash(p))
|
||||||
|
if cleaned != modulePath && !strings.HasPrefix(cleaned, modulePath+"/") {
|
||||||
|
return nil, ErrModuleNameEscape
|
||||||
|
}
|
||||||
|
return d.fs.Chroot(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DotGit) AddAlternate(remote string) error {
|
func (d *DotGit) AddAlternate(remote string) error {
|
||||||
|
|||||||
+74
-8
@@ -6,9 +6,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
|
"github.com/go-git/go-git/v5/internal/pathutil"
|
||||||
|
giturl "github.com/go-git/go-git/v5/internal/url"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/format/index"
|
"github.com/go-git/go-git/v5/plumbing/format/index"
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
@@ -119,6 +122,16 @@ func (s *Submodule) Repository() (*Repository, error) {
|
|||||||
exists = true
|
exists = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// s.c.Path is sourced from the worktree's .gitmodules and is
|
||||||
|
// therefore tree-controlled. Apply the strict tree-path validator
|
||||||
|
// before chroot — the wrapper's tolerant validPath would let a
|
||||||
|
// final-position .git component through (e.g. "submodule/.git"),
|
||||||
|
// which a malicious .gitmodules could use to chroot the submodule
|
||||||
|
// worktree into the repository's actual .git directory.
|
||||||
|
if err := pathutil.ValidTreePath(s.c.Path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var worktree billy.Filesystem
|
var worktree billy.Filesystem
|
||||||
if worktree, err = s.w.Filesystem.Chroot(s.c.Path); err != nil {
|
if worktree, err = s.w.Filesystem.Chroot(s.c.Path); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -138,18 +151,25 @@ func (s *Submodule) Repository() (*Repository, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path.IsAbs(moduleEndpoint.Path) && moduleEndpoint.Protocol == "file" {
|
// A relative submodule URL such as "../X.git" must resolve against
|
||||||
remotes, err := s.w.r.Remotes()
|
// the parent repository's remote URL, not against the process CWD.
|
||||||
|
// Detect relativity from the raw configured URL because
|
||||||
|
// transport.NewEndpoint normalizes local paths to absolute form via
|
||||||
|
// filepath.Abs, which would otherwise mask the relative form here.
|
||||||
|
if giturl.IsLocalEndpoint(s.c.URL) &&
|
||||||
|
!path.IsAbs(s.c.URL) && !filepath.IsAbs(s.c.URL) {
|
||||||
|
|
||||||
|
base, err := defaultRemote(s.w.r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolving relative submodule URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootEndpoint, err := transport.NewEndpoint(base.URLs[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rootEndpoint, err := transport.NewEndpoint(remotes[0].c.URLs[0])
|
rootEndpoint.Path = path.Join(rootEndpoint.Path, s.c.URL)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rootEndpoint.Path = path.Join(rootEndpoint.Path, moduleEndpoint.Path)
|
|
||||||
*moduleEndpoint = *rootEndpoint
|
*moduleEndpoint = *rootEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +181,52 @@ func (s *Submodule) Repository() (*Repository, error) {
|
|||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultRemote returns the remote that relative submodule URLs are
|
||||||
|
// resolved against, mirroring canonical Git's repo_default_remote
|
||||||
|
// (remote.c) and resolve_relative_url (builtin/submodule--helper.c):
|
||||||
|
//
|
||||||
|
// 1. if HEAD is on a branch with branch.<name>.remote configured,
|
||||||
|
// use that remote;
|
||||||
|
// 2. else if exactly one remote is configured, use it;
|
||||||
|
// 3. otherwise fall back to DefaultRemoteName ("origin").
|
||||||
|
//
|
||||||
|
// Each rule falls through unconditionally: a branch lookup that
|
||||||
|
// finds the branch but with an empty Remote does not short-circuit
|
||||||
|
// rule (2). Returns an error when the chosen remote is not configured.
|
||||||
|
func defaultRemote(r *Repository) (*config.RemoteConfig, error) {
|
||||||
|
cfg, err := r.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref, err := r.Reference(plumbing.HEAD, false); err == nil &&
|
||||||
|
ref.Type() == plumbing.SymbolicReference &&
|
||||||
|
ref.Target().IsBranch() {
|
||||||
|
if b, ok := cfg.Branches[ref.Target().Short()]; ok && b.Remote != "" {
|
||||||
|
return lookupRemote(cfg, b.Remote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Remotes) == 1 {
|
||||||
|
for name := range cfg.Remotes {
|
||||||
|
return lookupRemote(cfg, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookupRemote(cfg, DefaultRemoteName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupRemote(cfg *config.Config, name string) (*config.RemoteConfig, error) {
|
||||||
|
rc, ok := cfg.Remotes[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("remote %q not found", name)
|
||||||
|
}
|
||||||
|
if len(rc.URLs) == 0 {
|
||||||
|
return nil, fmt.Errorf("remote %q has no configured URL", name)
|
||||||
|
}
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Update the registered submodule to match what the superproject expects, the
|
// Update the registered submodule to match what the superproject expects, the
|
||||||
// submodule should be initialized first calling the Init method or setting in
|
// submodule should be initialized first calling the Init method or setting in
|
||||||
// the options SubmoduleUpdateOptions.Init equals true
|
// the options SubmoduleUpdateOptions.Init equals true
|
||||||
|
|||||||
+15
@@ -5,11 +5,18 @@ package binary
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrIntegerOverflow is returned when a Git-format variable-width integer
|
||||||
|
// would not fit into an int64 because the input declares more continuation
|
||||||
|
// bytes than the type can hold.
|
||||||
|
var ErrIntegerOverflow = errors.New("variable-width integer overflow")
|
||||||
|
|
||||||
// Read reads structured binary data from r into data. Bytes are read and
|
// Read reads structured binary data from r into data. Bytes are read and
|
||||||
// decoded in BigEndian order
|
// decoded in BigEndian order
|
||||||
// https://golang.org/pkg/encoding/binary/#Read
|
// https://golang.org/pkg/encoding/binary/#Read
|
||||||
@@ -92,6 +99,14 @@ func ReadVariableWidthInt(r io.Reader) (int64, error) {
|
|||||||
|
|
||||||
var v = int64(c & maskLength)
|
var v = int64(c & maskLength)
|
||||||
for c&maskContinue > 0 {
|
for c&maskContinue > 0 {
|
||||||
|
// Reject input that, after the v++ and shift below, would
|
||||||
|
// not fit in an int64. With v < (MaxInt64-127)>>7, the
|
||||||
|
// post-increment v is at most (MaxInt64-127)>>7 and the
|
||||||
|
// final (v << 7) + (c & 0x7F) stays within int64.
|
||||||
|
if v >= (math.MaxInt64-int64(maskLength))>>lengthBits {
|
||||||
|
return 0, ErrIntegerOverflow
|
||||||
|
}
|
||||||
|
|
||||||
v++
|
v++
|
||||||
if err := Read(r, &c); err != nil {
|
if err := Read(r, &c); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
+4
-111
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
@@ -458,10 +457,6 @@ func (w *Worktree) resetWorktree(t *object.Tree, files []string) error {
|
|||||||
|
|
||||||
filesMap := buildFilePathMap(files)
|
filesMap := buildFilePathMap(files)
|
||||||
for _, ch := range changes {
|
for _, ch := range changes {
|
||||||
if err := w.validChange(ch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(files) > 0 {
|
if len(files) > 0 {
|
||||||
file := ""
|
file := ""
|
||||||
if ch.From != nil {
|
if ch.From != nil {
|
||||||
@@ -489,108 +484,6 @@ func (w *Worktree) resetWorktree(t *object.Tree, files []string) error {
|
|||||||
return w.r.Storer.SetIndex(idx)
|
return w.r.Storer.SetIndex(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// worktreeDeny is a list of paths that are not allowed
|
|
||||||
// to be used when resetting the worktree.
|
|
||||||
var worktreeDeny = map[string]struct{}{
|
|
||||||
// .git
|
|
||||||
GitDirName: {},
|
|
||||||
|
|
||||||
// For other historical reasons, file names that do not conform to the 8.3
|
|
||||||
// format (up to eight characters for the basename, three for the file
|
|
||||||
// extension, certain characters not allowed such as `+`, etc) are associated
|
|
||||||
// with a so-called "short name", at least on the `C:` drive by default.
|
|
||||||
// Which means that `git~1/` is a valid way to refer to `.git/`.
|
|
||||||
"git~1": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// validPath checks whether paths are valid.
|
|
||||||
// The rules around invalid paths could differ from upstream based on how
|
|
||||||
// filesystems are managed within go-git, but they are largely the same.
|
|
||||||
//
|
|
||||||
// For upstream rules:
|
|
||||||
// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/read-cache.c#L946
|
|
||||||
// https://github.com/git/git/blob/564d0252ca632e0264ed670534a51d18a689ef5d/path.c#L1383
|
|
||||||
func validPath(paths ...string) error {
|
|
||||||
for _, p := range paths {
|
|
||||||
parts := strings.FieldsFunc(p, func(r rune) bool { return (r == '\\' || r == '/') })
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return fmt.Errorf("invalid path: %q", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, denied := worktreeDeny[strings.ToLower(parts[0])]; denied {
|
|
||||||
return fmt.Errorf("invalid path prefix: %q", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
// Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:.
|
|
||||||
if vol := filepath.VolumeName(p); vol != "" {
|
|
||||||
return fmt.Errorf("invalid path: %q", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !windowsValidPath(parts[0]) {
|
|
||||||
return fmt.Errorf("invalid path: %q", p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, part := range parts {
|
|
||||||
if part == ".." {
|
|
||||||
return fmt.Errorf("invalid path %q: cannot use '..'", p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// windowsPathReplacer defines the chars that need to be replaced
|
|
||||||
// as part of windowsValidPath.
|
|
||||||
var windowsPathReplacer *strings.Replacer
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
windowsPathReplacer = strings.NewReplacer(" ", "", ".", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func windowsValidPath(part string) bool {
|
|
||||||
if len(part) > 3 && strings.EqualFold(part[:4], GitDirName) {
|
|
||||||
// For historical reasons, file names that end in spaces or periods are
|
|
||||||
// automatically trimmed. Therefore, `.git . . ./` is a valid way to refer
|
|
||||||
// to `.git/`.
|
|
||||||
if windowsPathReplacer.Replace(part[4:]) == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// For yet other historical reasons, NTFS supports so-called "Alternate Data
|
|
||||||
// Streams", i.e. metadata associated with a given file, referred to via
|
|
||||||
// `<filename>:<stream-name>:<stream-type>`. There exists a default stream
|
|
||||||
// type for directories, allowing `.git/` to be accessed via
|
|
||||||
// `.git::$INDEX_ALLOCATION/`.
|
|
||||||
//
|
|
||||||
// For performance reasons, _all_ Alternate Data Streams of `.git/` are
|
|
||||||
// forbidden, not just `::$INDEX_ALLOCATION`.
|
|
||||||
if len(part) > 4 && part[4:5] == ":" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Worktree) validChange(ch merkletrie.Change) error {
|
|
||||||
action, err := ch.Action()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case merkletrie.Delete:
|
|
||||||
return validPath(ch.From.String())
|
|
||||||
case merkletrie.Insert:
|
|
||||||
return validPath(ch.To.String())
|
|
||||||
case merkletrie.Modify:
|
|
||||||
return validPath(ch.From.String(), ch.To.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *indexBuilder) error {
|
func (w *Worktree) checkoutChange(ch merkletrie.Change, t *object.Tree, idx *indexBuilder) error {
|
||||||
a, err := ch.Action()
|
a, err := ch.Action()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -763,10 +656,10 @@ func (w *Worktree) checkoutFile(f *object.File) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) {
|
func (w *Worktree) checkoutFileSymlink(f *object.File) (err error) {
|
||||||
// https://github.com/git/git/commit/10ecfa76491e4923988337b2e2243b05376b40de
|
// .gitmodules symlink rejection (and its NTFS / HFS variants) is
|
||||||
if strings.EqualFold(f.Name, gitmodulesFile) {
|
// enforced by the worktreeFilesystem wrapper's Symlink method via
|
||||||
return ErrGitModulesSymlink
|
// validSymlinkName. See https://github.com/git/git/commit/10ecfa7
|
||||||
}
|
// for the upstream rationale.
|
||||||
|
|
||||||
from, err := f.Reader()
|
from, err := f.Reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+264
@@ -0,0 +1,264 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-billy/v5"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/internal/pathutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultProtectHFS returns the default value for core.protectHFS
|
||||||
|
// when not explicitly configured. Matches upstream Git's
|
||||||
|
// PROTECT_HFS_DEFAULT[1], which the Makefile sets to 1 on Darwin
|
||||||
|
// and leaves at 0 on every other platform.
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/config.mak.uname#L146
|
||||||
|
func defaultProtectHFS() bool {
|
||||||
|
return runtime.GOOS == "darwin"
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultProtectNTFS returns the default value for core.protectNTFS
|
||||||
|
// when not explicitly configured. Matches upstream Git's
|
||||||
|
// PROTECT_NTFS_DEFAULT, which has been 1 on every platform since
|
||||||
|
// 9102f958ee5 (CVE-2019-1353)[1]: WSL allows Linux processes to
|
||||||
|
// reach NTFS-mounted worktrees on Windows hosts, so the
|
||||||
|
// is_ntfs_dotgit guard cannot safely be gated on the runtime OS.
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/commit/9102f958ee5
|
||||||
|
func defaultProtectNTFS() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// worktreeFilesystem wraps a billy.Filesystem and validates every path passed
|
||||||
|
// to a mutating operation. This prevents writing to, or deleting from,
|
||||||
|
// dangerous locations (e.g. .git/*, ../) regardless of which worktree
|
||||||
|
// code path triggers the operation.
|
||||||
|
type worktreeFilesystem struct {
|
||||||
|
billy.Filesystem
|
||||||
|
protectNTFS bool
|
||||||
|
protectHFS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWorktreeFilesystem(fs billy.Filesystem, protectNTFS, protectHFS bool) *worktreeFilesystem {
|
||||||
|
return &worktreeFilesystem{Filesystem: fs, protectNTFS: protectNTFS, protectHFS: protectHFS}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Create(filename string) (billy.File, error) {
|
||||||
|
if err := sfs.validPath(filename); err != nil {
|
||||||
|
return nil, fmt.Errorf("create: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Create(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Open(filename string) (billy.File, error) {
|
||||||
|
if err := sfs.validReadPath(filename); err != nil {
|
||||||
|
return nil, fmt.Errorf("open: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Open(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
|
||||||
|
if err := sfs.validPath(filename); err != nil {
|
||||||
|
return nil, fmt.Errorf("openfile: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.OpenFile(filename, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Stat(filename string) (os.FileInfo, error) {
|
||||||
|
if err := sfs.validReadPath(filename); err != nil {
|
||||||
|
return nil, fmt.Errorf("stat: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Stat(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Remove(filename string) error {
|
||||||
|
if err := sfs.validPath(filename); err != nil {
|
||||||
|
return fmt.Errorf("remove: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Remove(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Rename(from, to string) error {
|
||||||
|
if err := sfs.validPath(from, to); err != nil {
|
||||||
|
return fmt.Errorf("rename: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Rename(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) ReadDir(path string) ([]os.FileInfo, error) {
|
||||||
|
if err := sfs.validReadPath(path); err != nil {
|
||||||
|
return nil, fmt.Errorf("readdir: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.ReadDir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Lstat(filename string) (os.FileInfo, error) {
|
||||||
|
if err := sfs.validReadPath(filename); err != nil {
|
||||||
|
return nil, fmt.Errorf("lstat: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Lstat(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Symlink(target, link string) error {
|
||||||
|
if err := sfs.validPath(link); err != nil {
|
||||||
|
return fmt.Errorf("symlink: %w", err)
|
||||||
|
}
|
||||||
|
if err := sfs.validSymlinkName(link); err != nil {
|
||||||
|
return fmt.Errorf("symlink: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Symlink(target, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Readlink(link string) (string, error) {
|
||||||
|
if err := sfs.validReadPath(link); err != nil {
|
||||||
|
return "", fmt.Errorf("readlink: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Readlink(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
// MkdirAll on the worktree root is a no-op: the root always exists,
|
||||||
|
// so there is nothing to materialise. Mirroring the tolerance that
|
||||||
|
// validReadPath gives to read-side operations avoids breaking callers
|
||||||
|
// that walk a directory tree and pass the relative-to-root prefix
|
||||||
|
// ("") through to the worktree FS.
|
||||||
|
if path == "" || path == "." || path == "/" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := sfs.validPath(path); err != nil {
|
||||||
|
return fmt.Errorf("mkdirall: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) TempFile(_, _ string) (billy.File, error) {
|
||||||
|
return nil, fmt.Errorf("tempfile: %w", errUnsupportedOperation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfs *worktreeFilesystem) Chroot(path string) (billy.Filesystem, error) {
|
||||||
|
if err := sfs.validReadPath(path); err != nil {
|
||||||
|
return nil, fmt.Errorf("chroot: %w", err)
|
||||||
|
}
|
||||||
|
return sfs.Filesystem.Chroot(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validReadPath is like validPath but treats the empty string and "." as
|
||||||
|
// valid references to the worktree root. Read-side operations on the root
|
||||||
|
// (e.g. ReadDir(""), Lstat(".")) are legitimate; mutating the root itself
|
||||||
|
// is not, so write-side operations continue to use validPath directly.
|
||||||
|
func (sfs *worktreeFilesystem) validReadPath(p string) error {
|
||||||
|
if p == "" || p == "." || p == "/" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return sfs.validPath(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errUnsupportedOperation = errors.New("unsupported operation")
|
||||||
|
|
||||||
|
// isDotGitVariant reports whether part is .git, git~1, or an HFS+
|
||||||
|
// equivalent of .git (when protectHFS is true). NTFS variants of .git
|
||||||
|
// (e.g. ".git " with trailing space, ".git::$INDEX_ALLOCATION") are
|
||||||
|
// detected separately by pathutil.WindowsValidPath, which applies
|
||||||
|
// regardless of position in the path. Both validators reuse this
|
||||||
|
// helper.
|
||||||
|
func isDotGitVariant(part string, protectHFS bool) bool {
|
||||||
|
if pathutil.IsDotGitName(part) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if protectHFS && pathutil.IsHFSDotGit(part) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validPath checks whether paths are valid for the worktree
|
||||||
|
// filesystem abstraction. It is intentionally tolerant of .git as
|
||||||
|
// the final path component of a multi-component path
|
||||||
|
// (e.g. "submodule/.git"), so that legitimate gitlink pointer files
|
||||||
|
// can still be Stat'd, Read, and Removed via the wrapper during
|
||||||
|
// submodule cleanup. Attacker-controlled tree-entry paths are
|
||||||
|
// validated separately by pathutil.ValidTreePath at the boundaries
|
||||||
|
// where data leaves the trusted store (Tree.FindEntry, the explicit
|
||||||
|
// callers in CherryPick and Submodule.Repository).
|
||||||
|
//
|
||||||
|
// For upstream rules:
|
||||||
|
// https://github.com/git/git/blob/v2.54.0/read-cache.c#L987
|
||||||
|
// https://github.com/git/git/blob/v2.54.0/path.c#L1419
|
||||||
|
func (sfs *worktreeFilesystem) validPath(paths ...string) error {
|
||||||
|
for _, p := range paths {
|
||||||
|
for i := 0; i < len(p); i++ {
|
||||||
|
if p[i] < 0x20 || p[i] == 0x7f {
|
||||||
|
return fmt.Errorf("invalid path %q: contains control character", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.FieldsFunc(p, func(r rune) bool { return (r == '\\' || r == '/') })
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return fmt.Errorf("invalid path: %q", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sfs.protectNTFS {
|
||||||
|
// Volume names are not supported, in both formats: \\ and <DRIVE_LETTER>:.
|
||||||
|
if vol := filepath.VolumeName(p); vol != "" {
|
||||||
|
return fmt.Errorf("invalid path: %q", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, part := range parts {
|
||||||
|
if part == "." || part == ".." {
|
||||||
|
return fmt.Errorf("invalid path %q: cannot use %q", p, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject .git (and equivalents) as a path component when it is
|
||||||
|
// either the first component (root-level .git) or a non-final
|
||||||
|
// component (traversal into a .git directory, e.g. "a/.git/config").
|
||||||
|
// A final non-first .git component (e.g. "submodule/.git") is
|
||||||
|
// allowed because submodule worktrees contain a .git pointer file.
|
||||||
|
if isDotGitVariant(part, sfs.protectHFS) && (i == 0 || i < len(parts)-1) {
|
||||||
|
return fmt.Errorf("invalid path component: %q", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sfs.protectNTFS && !pathutil.WindowsValidPath(part) {
|
||||||
|
return fmt.Errorf("invalid path: %q", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validSymlinkName checks the per-component name of a symlink for
|
||||||
|
// dotfile names that attackers can use to trick a checkout into
|
||||||
|
// writing a dangerous symlink. Each path component is compared
|
||||||
|
// against .gitmodules case-insensitively, against its NTFS variants
|
||||||
|
// (e.g. ".gitmodules .", ".gitmodules::$INDEX_ALLOCATION", or 8.3
|
||||||
|
// short-name forms) when protectNTFS is on, and against its HFS+
|
||||||
|
// variants (Unicode ignored code points folded into ".gitmodules")
|
||||||
|
// when protectHFS is on.
|
||||||
|
//
|
||||||
|
// Reference: upstream Git verify_path_internal at read-cache.c#L1004-L1024
|
||||||
|
// in tag v2.54.0[1].
|
||||||
|
//
|
||||||
|
// [1]: https://github.com/git/git/blob/v2.54.0/read-cache.c#L1004-L1024
|
||||||
|
func (sfs *worktreeFilesystem) validSymlinkName(name string) error {
|
||||||
|
parts := strings.FieldsFunc(name, func(r rune) bool {
|
||||||
|
return r == '/' || r == '\\'
|
||||||
|
})
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.EqualFold(part, gitmodulesFile) {
|
||||||
|
return ErrGitModulesSymlink
|
||||||
|
}
|
||||||
|
if sfs.protectNTFS && pathutil.IsNTFSDotGitmodules(part) {
|
||||||
|
return ErrGitModulesSymlink
|
||||||
|
}
|
||||||
|
if sfs.protectHFS && pathutil.IsHFSDotGitmodules(part) {
|
||||||
|
return ErrGitModulesSymlink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+9
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5/util"
|
"github.com/go-git/go-billy/v5/util"
|
||||||
|
"github.com/go-git/go-git/v5/internal/pathutil"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||||
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
|
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
|
||||||
@@ -545,6 +546,14 @@ func (w *Worktree) addOrUpdateFileToIndex(idx *index.Index, filename string, h p
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error {
|
func (w *Worktree) doAddFileToIndex(idx *index.Index, filename string, h plumbing.Hash) error {
|
||||||
|
// Mirror upstream's Index.Add gate at the v5 caller boundary: the
|
||||||
|
// index feeds future trees, so a name that the tree-side
|
||||||
|
// pathutil.ValidTreePath gate would reject must not enter the
|
||||||
|
// index in the first place. v5 keeps Index.Add's existing signature
|
||||||
|
// for API compatibility, so the validation happens here.
|
||||||
|
if err := pathutil.ValidTreePath(filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return w.doUpdateFileToIndex(idx.Add(filename), filename, h)
|
return w.doUpdateFileToIndex(idx.Add(filename), filename, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088
|
FROM golang:1.26@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479
|
||||||
|
|
||||||
ENV GOOS=linux
|
ENV GOOS=linux
|
||||||
ENV GOARCH=arm
|
ENV GOARCH=arm
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088
|
FROM golang:1.26@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479
|
||||||
|
|
||||||
ENV GOOS=linux
|
ENV GOOS=linux
|
||||||
ENV GOARCH=arm64
|
ENV GOARCH=arm64
|
||||||
|
|||||||
-5
@@ -12,7 +12,6 @@ package sha1cd
|
|||||||
// Original: https://github.com/golang/go/blob/master/src/crypto/sha1/sha1.go
|
// Original: https://github.com/golang/go/blob/master/src/crypto/sha1/sha1.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"hash"
|
"hash"
|
||||||
@@ -20,10 +19,6 @@ import (
|
|||||||
shared "github.com/pjbgf/sha1cd/internal"
|
shared "github.com/pjbgf/sha1cd/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
crypto.RegisterHash(crypto.SHA1, New)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The size of a SHA-1 checksum in bytes.
|
// The size of a SHA-1 checksum in bytes.
|
||||||
const Size = shared.Size
|
const Size = shared.Size
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -37,9 +37,9 @@ func block(dig *digest, p []byte) {
|
|||||||
chunk := p[:shared.Chunk]
|
chunk := p[:shared.Chunk]
|
||||||
|
|
||||||
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
|
||||||
rectifyCompressionState(m1, &cs)
|
rectifyCompressionState(&m1, &cs)
|
||||||
|
|
||||||
col := checkCollision(m1, cs, dig.h)
|
col := checkCollision(&m1, &cs, &dig.h)
|
||||||
if col {
|
if col {
|
||||||
dig.col = true
|
dig.col = true
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -11,11 +11,11 @@
|
|||||||
// Reference implementations:
|
// Reference implementations:
|
||||||
// - https://github.com/golang/go/blob/master/src/crypto/sha1/sha1block_amd64.s
|
// - https://github.com/golang/go/blob/master/src/crypto/sha1/sha1block_amd64.s
|
||||||
|
|
||||||
|
// Reverse the dword order in abcd via PSHUFD then store the 16 bytes in one
|
||||||
|
// move, instead of issuing four VPEXTRD's that each go through the store port.
|
||||||
#define LOADCS(abcd, e, index, target) \
|
#define LOADCS(abcd, e, index, target) \
|
||||||
VPEXTRD $3, abcd, ((index*20)+0)(target); \
|
VPSHUFD $0x1B, abcd, X8; \
|
||||||
VPEXTRD $2, abcd, ((index*20)+4)(target); \
|
VMOVDQU X8, ((index*20)+0)(target); \
|
||||||
VPEXTRD $1, abcd, ((index*20)+8)(target); \
|
|
||||||
VPEXTRD $0, abcd, ((index*20)+12)(target); \
|
|
||||||
MOVL e, ((index*20)+16)(target);
|
MOVL e, ((index*20)+16)(target);
|
||||||
|
|
||||||
#define LOADM1(m1, index, target) \
|
#define LOADM1(m1, index, target) \
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ func block(dig *digest, p []byte) {
|
|||||||
|
|
||||||
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
blockARM64(dig.h[:], chunk, m1[:], cs[:])
|
||||||
|
|
||||||
rectifyCompressionState(m1, &cs)
|
rectifyCompressionState(&m1, &cs)
|
||||||
col := checkCollision(m1, cs, dig.h)
|
col := checkCollision(&m1, &cs, &dig.h)
|
||||||
if col {
|
if col {
|
||||||
dig.col = true
|
dig.col = true
|
||||||
|
|
||||||
|
|||||||
+13
-12
@@ -127,7 +127,8 @@ func blockGeneric(dig *digest, p []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hi == 1 {
|
if hi == 1 {
|
||||||
col := checkCollision(m1, cs, [shared.WordBuffers]uint32{h0, h1, h2, h3, h4})
|
h := [shared.WordBuffers]uint32{h0, h1, h2, h3, h4}
|
||||||
|
col := checkCollision(&m1, &cs, &h)
|
||||||
if col {
|
if col {
|
||||||
dig.col = true
|
dig.col = true
|
||||||
hi++
|
hi++
|
||||||
@@ -143,23 +144,23 @@ func blockGeneric(dig *digest, p []byte) {
|
|||||||
|
|
||||||
//go:noinline
|
//go:noinline
|
||||||
func checkCollision(
|
func checkCollision(
|
||||||
m1 [shared.Rounds]uint32,
|
m1 *[shared.Rounds]uint32,
|
||||||
cs [shared.PreStepState][shared.WordBuffers]uint32,
|
cs *[shared.PreStepState][shared.WordBuffers]uint32,
|
||||||
h [shared.WordBuffers]uint32,
|
h *[shared.WordBuffers]uint32,
|
||||||
) bool {
|
) bool {
|
||||||
if mask := ubc.CalculateDvMask(m1); mask != 0 {
|
if mask := ubc.CalculateDvMask(m1); mask != 0 {
|
||||||
dvs := ubc.SHA1_dvs()
|
dvs := ubc.SHA1_dvs()
|
||||||
|
|
||||||
for i := 0; dvs[i].DvType != 0; i++ {
|
for i := 0; dvs[i].DvType != 0; i++ {
|
||||||
if (mask & ((uint32)(1) << uint32(dvs[i].MaskB))) != 0 {
|
if (mask & ((uint32)(1) << uint32(dvs[i].MaskB))) != 0 {
|
||||||
var csState [shared.WordBuffers]uint32
|
var csState *[shared.WordBuffers]uint32
|
||||||
switch dvs[i].TestT {
|
switch dvs[i].TestT {
|
||||||
case 58:
|
case 58:
|
||||||
csState = cs[1]
|
csState = &cs[1]
|
||||||
case 65:
|
case 65:
|
||||||
csState = cs[2]
|
csState = &cs[2]
|
||||||
case 0:
|
case 0:
|
||||||
csState = cs[0]
|
csState = &cs[0]
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("dvs data is trying to use a testT that isn't available: %d", dvs[i].TestT))
|
panic(fmt.Sprintf("dvs data is trying to use a testT that isn't available: %d", dvs[i].TestT))
|
||||||
}
|
}
|
||||||
@@ -168,7 +169,7 @@ func checkCollision(
|
|||||||
dvs[i].TestT, // testT is the step number
|
dvs[i].TestT, // testT is the step number
|
||||||
// m2 is a secondary message created XORing with
|
// m2 is a secondary message created XORing with
|
||||||
// ubc's DM prior to the SHA recompression step.
|
// ubc's DM prior to the SHA recompression step.
|
||||||
m1, dvs[i].Dm,
|
m1, &dvs[i].Dm,
|
||||||
csState,
|
csState,
|
||||||
h)
|
h)
|
||||||
|
|
||||||
@@ -182,8 +183,8 @@ func checkCollision(
|
|||||||
}
|
}
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func hasCollided(step uint32, m1, dm [shared.Rounds]uint32,
|
func hasCollided(step uint32, m1, dm *[shared.Rounds]uint32,
|
||||||
state [shared.WordBuffers]uint32, h [shared.WordBuffers]uint32) bool {
|
state *[shared.WordBuffers]uint32, h *[shared.WordBuffers]uint32) bool {
|
||||||
// Intermediary Hash Value.
|
// Intermediary Hash Value.
|
||||||
ihv := [shared.WordBuffers]uint32{}
|
ihv := [shared.WordBuffers]uint32{}
|
||||||
|
|
||||||
@@ -282,7 +283,7 @@ func hasCollided(step uint32, m1, dm [shared.Rounds]uint32,
|
|||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func rectifyCompressionState(
|
func rectifyCompressionState(
|
||||||
m1 [shared.Rounds]uint32,
|
m1 *[shared.Rounds]uint32,
|
||||||
cs *[shared.PreStepState][shared.WordBuffers]uint32,
|
cs *[shared.PreStepState][shared.WordBuffers]uint32,
|
||||||
) {
|
) {
|
||||||
if cs == nil {
|
if cs == nil {
|
||||||
|
|||||||
+4
-1
@@ -29,7 +29,10 @@ type DvInfo struct {
|
|||||||
// bitconditions for that DV have been met.
|
// bitconditions for that DV have been met.
|
||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func CalculateDvMask(W [80]uint32) uint32 {
|
func CalculateDvMask(W *[80]uint32) uint32 {
|
||||||
|
if W == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
mask := uint32(0xFFFFFFFF)
|
mask := uint32(0xFFFFFFFF)
|
||||||
mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | ^(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit))
|
mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | ^(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit))
|
||||||
mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | ^(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | DV_II_55_0_bit | DV_II_56_0_bit))
|
mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) | ^(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit | DV_II_55_0_bit | DV_II_56_0_bit))
|
||||||
|
|||||||
+5
-11
@@ -925,15 +925,10 @@ func (c *Command) execute(a []string) (err error) {
|
|||||||
// Also say we need help if the command isn't runnable.
|
// Also say we need help if the command isn't runnable.
|
||||||
helpVal, err := c.Flags().GetBool(helpFlagName)
|
helpVal, err := c.Flags().GetBool(helpFlagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// NOTE(d1): temporarily hardcoding "ayuda" as a replacement for "help"
|
// should be impossible to get here as we always declare a help
|
||||||
// source of the pain: https://github.com/spf13/cobra/issues/2359
|
// flag in InitDefaultHelpFlag()
|
||||||
helpVal, err = c.Flags().GetBool("ayuda")
|
c.Println("\"help\" flag declared as non-bool. Please correct your code")
|
||||||
if err != nil {
|
return err
|
||||||
// should be impossible to get here as we always declare a help
|
|
||||||
// flag in InitDefaultHelpFlag()
|
|
||||||
c.Println("\"help\" flag declared as non-bool. Please correct your code")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if helpVal {
|
if helpVal {
|
||||||
@@ -1231,8 +1226,7 @@ func (c *Command) InitDefaultHelpFlag() {
|
|||||||
} else {
|
} else {
|
||||||
usage += name
|
usage += name
|
||||||
}
|
}
|
||||||
// NOTE(d1): do not assume "help" exists in the context of translation
|
c.Flags().BoolP(helpFlagName, "h", false, usage)
|
||||||
// c.Flags().BoolP(helpFlagName, "h", false, usage)
|
|
||||||
_ = c.Flags().SetAnnotation(helpFlagName, FlagSetByCobraAnnotation, []string{"true"})
|
_ = c.Flags().SetAnnotation(helpFlagName, FlagSetByCobraAnnotation, []string{"true"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# This is a renovate-friendly source of Docker images.
|
# This is a renovate-friendly source of Docker images.
|
||||||
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
|
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
|
||||||
FROM otel/weaver:v0.21.2@sha256:2401de985c38bdb98b43918e2f43aa36b2afed4aa5669ac1c1de0a17301cd36d AS weaver
|
FROM otel/weaver:v0.24.2@sha256:d1fb16d279f39810c340fbbf1cf9e5e995a3a9cefa531938e9012437e3bc00c1 AS weaver
|
||||||
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
|
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
|
||||||
|
|||||||
+1
-1
@@ -586,7 +586,7 @@ func (c *cbcCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader
|
|||||||
|
|
||||||
// Length of encrypted portion of the packet (header, payload, padding).
|
// Length of encrypted portion of the packet (header, payload, padding).
|
||||||
// Enforce minimum padding and packet size.
|
// Enforce minimum padding and packet size.
|
||||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPacketSize)
|
||||||
// Enforce block size.
|
// Enforce block size.
|
||||||
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -274,10 +274,14 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter algorithms based on those supported by MultiAlgorithmSigner.
|
// Filter algorithms based on those supported by MultiAlgorithmSigner.
|
||||||
|
// Iterate over the signer's algorithms first to preserve its preference order.
|
||||||
|
supportedKeyAlgos := algorithmsForKeyFormat(keyFormat)
|
||||||
var keyAlgos []string
|
var keyAlgos []string
|
||||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
for _, signerAlgo := range as.Algorithms() {
|
||||||
if slices.Contains(as.Algorithms(), underlyingAlgo(algo)) {
|
if idx := slices.IndexFunc(supportedKeyAlgos, func(algo string) bool {
|
||||||
keyAlgos = append(keyAlgos, algo)
|
return underlyingAlgo(algo) == signerAlgo
|
||||||
|
}); idx >= 0 {
|
||||||
|
keyAlgos = append(keyAlgos, supportedKeyAlgos[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -6,6 +6,7 @@ package hpack
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// headerFieldTable implements a list of HeaderFields.
|
// headerFieldTable implements a list of HeaderFields.
|
||||||
@@ -54,10 +55,16 @@ func (t *headerFieldTable) len() int {
|
|||||||
|
|
||||||
// addEntry adds a new entry.
|
// addEntry adds a new entry.
|
||||||
func (t *headerFieldTable) addEntry(f HeaderField) {
|
func (t *headerFieldTable) addEntry(f HeaderField) {
|
||||||
|
// Prevent f from escaping to the heap.
|
||||||
|
f2 := HeaderField{
|
||||||
|
Name: strings.Clone(f.Name),
|
||||||
|
Value: strings.Clone(f.Value),
|
||||||
|
Sensitive: f.Sensitive,
|
||||||
|
}
|
||||||
id := uint64(t.len()) + t.evictCount + 1
|
id := uint64(t.len()) + t.evictCount + 1
|
||||||
t.byName[f.Name] = id
|
t.byName[f2.Name] = id
|
||||||
t.byNameValue[pairNameValue{f.Name, f.Value}] = id
|
t.byNameValue[pairNameValue{f2.Name, f2.Value}] = id
|
||||||
t.ents = append(t.ents, f)
|
t.ents = append(t.ents, f2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evictOldest evicts the n oldest entries in the table.
|
// evictOldest evicts the n oldest entries in the table.
|
||||||
|
|||||||
+3
-6
@@ -718,9 +718,6 @@ func canRetryError(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
|
||||||
if t.transportTestHooks != nil {
|
|
||||||
return t.newClientConn(nil, singleUse, nil)
|
|
||||||
}
|
|
||||||
host, _, err := net.SplitHostPort(addr)
|
host, _, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2861,6 +2858,9 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
|
|||||||
|
|
||||||
var seenMaxConcurrentStreams bool
|
var seenMaxConcurrentStreams bool
|
||||||
err := f.ForeachSetting(func(s Setting) error {
|
err := f.ForeachSetting(func(s Setting) error {
|
||||||
|
if err := s.Valid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
switch s.ID {
|
switch s.ID {
|
||||||
case SettingMaxFrameSize:
|
case SettingMaxFrameSize:
|
||||||
cc.maxFrameSize = s.Val
|
cc.maxFrameSize = s.Val
|
||||||
@@ -2892,9 +2892,6 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
|
|||||||
cc.henc.SetMaxDynamicTableSize(s.Val)
|
cc.henc.SetMaxDynamicTableSize(s.Val)
|
||||||
cc.peerMaxHeaderTableSize = s.Val
|
cc.peerMaxHeaderTableSize = s.Val
|
||||||
case SettingEnableConnectProtocol:
|
case SettingEnableConnectProtocol:
|
||||||
if err := s.Valid(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
|
// If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
|
||||||
// we require that it do so in the first SETTINGS frame.
|
// we require that it do so in the first SETTINGS frame.
|
||||||
//
|
//
|
||||||
|
|||||||
+12
-7
@@ -152,13 +152,17 @@ var ARM struct {
|
|||||||
// The booleans in Loong64 contain the correspondingly named cpu feature bit.
|
// The booleans in Loong64 contain the correspondingly named cpu feature bit.
|
||||||
// The struct is padded to avoid false sharing.
|
// The struct is padded to avoid false sharing.
|
||||||
var Loong64 struct {
|
var Loong64 struct {
|
||||||
_ CacheLinePad
|
_ CacheLinePad
|
||||||
HasLSX bool // support 128-bit vector extension
|
HasLSX bool // support 128-bit vector extension
|
||||||
HasLASX bool // support 256-bit vector extension
|
HasLASX bool // support 256-bit vector extension
|
||||||
HasCRC32 bool // support CRC instruction
|
HasCRC32 bool // support CRC instruction
|
||||||
HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction
|
HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D}
|
||||||
HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D} instruction
|
HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction
|
||||||
_ CacheLinePad
|
HasLLACQ_SCREL bool // support LLACQ.{W/D}, SCREL.{W/D} instruction
|
||||||
|
HasSCQ bool // support SC.Q instruction
|
||||||
|
HasDBAR_HINTS bool // supports finer-grained DBAR hints
|
||||||
|
|
||||||
|
_ CacheLinePad
|
||||||
}
|
}
|
||||||
|
|
||||||
// MIPS64X contains the supported CPU features of the current mips64/mips64le
|
// MIPS64X contains the supported CPU features of the current mips64/mips64le
|
||||||
@@ -232,6 +236,7 @@ var RISCV64 struct {
|
|||||||
HasZba bool // Address generation instructions extension
|
HasZba bool // Address generation instructions extension
|
||||||
HasZbb bool // Basic bit-manipulation extension
|
HasZbb bool // Basic bit-manipulation extension
|
||||||
HasZbs bool // Single-bit instructions extension
|
HasZbs bool // Single-bit instructions extension
|
||||||
|
HasZbc bool // Carryless multiplication extension
|
||||||
HasZvbb bool // Vector Basic Bit-manipulation
|
HasZvbb bool // Vector Basic Bit-manipulation
|
||||||
HasZvbc bool // Vector Carryless Multiplication
|
HasZvbc bool // Vector Carryless Multiplication
|
||||||
HasZvkb bool // Vector Cryptography Bit-manipulation
|
HasZvkb bool // Vector Cryptography Bit-manipulation
|
||||||
|
|||||||
+2
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
package cpu
|
package cpu
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
func doinit() {
|
func doinit() {
|
||||||
setMinimalFeatures()
|
setMinimalFeatures()
|
||||||
|
|
||||||
|
|||||||
+2
@@ -58,6 +58,7 @@ const (
|
|||||||
riscv_HWPROBE_EXT_ZBA = 0x8
|
riscv_HWPROBE_EXT_ZBA = 0x8
|
||||||
riscv_HWPROBE_EXT_ZBB = 0x10
|
riscv_HWPROBE_EXT_ZBB = 0x10
|
||||||
riscv_HWPROBE_EXT_ZBS = 0x20
|
riscv_HWPROBE_EXT_ZBS = 0x20
|
||||||
|
riscv_HWPROBE_EXT_ZBC = 0x80
|
||||||
riscv_HWPROBE_EXT_ZVBB = 0x20000
|
riscv_HWPROBE_EXT_ZVBB = 0x20000
|
||||||
riscv_HWPROBE_EXT_ZVBC = 0x40000
|
riscv_HWPROBE_EXT_ZVBC = 0x40000
|
||||||
riscv_HWPROBE_EXT_ZVKB = 0x80000
|
riscv_HWPROBE_EXT_ZVKB = 0x80000
|
||||||
@@ -108,6 +109,7 @@ func doinit() {
|
|||||||
RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA)
|
RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA)
|
||||||
RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB)
|
RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB)
|
||||||
RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS)
|
RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS)
|
||||||
|
RISCV64.HasZbc = isSet(v, riscv_HWPROBE_EXT_ZBC)
|
||||||
RISCV64.HasZvbb = isSet(v, riscv_HWPROBE_EXT_ZVBB)
|
RISCV64.HasZvbb = isSet(v, riscv_HWPROBE_EXT_ZVBB)
|
||||||
RISCV64.HasZvbc = isSet(v, riscv_HWPROBE_EXT_ZVBC)
|
RISCV64.HasZvbc = isSet(v, riscv_HWPROBE_EXT_ZVBC)
|
||||||
RISCV64.HasZvkb = isSet(v, riscv_HWPROBE_EXT_ZVKB)
|
RISCV64.HasZvkb = isSet(v, riscv_HWPROBE_EXT_ZVKB)
|
||||||
|
|||||||
+14
-2
@@ -15,8 +15,13 @@ const (
|
|||||||
cpucfg1_CRC32 = 1 << 25
|
cpucfg1_CRC32 = 1 << 25
|
||||||
|
|
||||||
// CPUCFG2 bits
|
// CPUCFG2 bits
|
||||||
cpucfg2_LAM_BH = 1 << 27
|
cpucfg2_LAM_BH = 1 << 27
|
||||||
cpucfg2_LAMCAS = 1 << 28
|
cpucfg2_LAMCAS = 1 << 28
|
||||||
|
cpucfg2_LLACQ_SCREL = 1 << 29
|
||||||
|
cpucfg2_SCQ = 1 << 30
|
||||||
|
|
||||||
|
// CPUCFG3 bits
|
||||||
|
cpucfg3_DBAR_HINTS = 1 << 17
|
||||||
)
|
)
|
||||||
|
|
||||||
func initOptions() {
|
func initOptions() {
|
||||||
@@ -26,6 +31,9 @@ func initOptions() {
|
|||||||
{Name: "crc32", Feature: &Loong64.HasCRC32},
|
{Name: "crc32", Feature: &Loong64.HasCRC32},
|
||||||
{Name: "lam_bh", Feature: &Loong64.HasLAM_BH},
|
{Name: "lam_bh", Feature: &Loong64.HasLAM_BH},
|
||||||
{Name: "lamcas", Feature: &Loong64.HasLAMCAS},
|
{Name: "lamcas", Feature: &Loong64.HasLAMCAS},
|
||||||
|
{Name: "llacq_screl", Feature: &Loong64.HasLLACQ_SCREL},
|
||||||
|
{Name: "scq", Feature: &Loong64.HasSCQ},
|
||||||
|
{Name: "dbar_hints", Feature: &Loong64.HasDBAR_HINTS},
|
||||||
}
|
}
|
||||||
|
|
||||||
// The CPUCFG data on Loong64 only reflects the hardware capabilities,
|
// The CPUCFG data on Loong64 only reflects the hardware capabilities,
|
||||||
@@ -37,10 +45,14 @@ func initOptions() {
|
|||||||
// through CPUCFG
|
// through CPUCFG
|
||||||
cfg1 := get_cpucfg(1)
|
cfg1 := get_cpucfg(1)
|
||||||
cfg2 := get_cpucfg(2)
|
cfg2 := get_cpucfg(2)
|
||||||
|
cfg3 := get_cpucfg(3)
|
||||||
|
|
||||||
Loong64.HasCRC32 = cfgIsSet(cfg1, cpucfg1_CRC32)
|
Loong64.HasCRC32 = cfgIsSet(cfg1, cpucfg1_CRC32)
|
||||||
Loong64.HasLAMCAS = cfgIsSet(cfg2, cpucfg2_LAMCAS)
|
Loong64.HasLAMCAS = cfgIsSet(cfg2, cpucfg2_LAMCAS)
|
||||||
Loong64.HasLAM_BH = cfgIsSet(cfg2, cpucfg2_LAM_BH)
|
Loong64.HasLAM_BH = cfgIsSet(cfg2, cpucfg2_LAM_BH)
|
||||||
|
Loong64.HasLLACQ_SCREL = cfgIsSet(cfg2, cpucfg2_LLACQ_SCREL)
|
||||||
|
Loong64.HasSCQ = cfgIsSet(cfg2, cpucfg2_SCQ)
|
||||||
|
Loong64.HasDBAR_HINTS = cfgIsSet(cfg3, cpucfg3_DBAR_HINTS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_cpucfg(reg uint32) uint32
|
func get_cpucfg(reg uint32) uint32
|
||||||
|
|||||||
+1
@@ -16,6 +16,7 @@ func initOptions() {
|
|||||||
{Name: "zba", Feature: &RISCV64.HasZba},
|
{Name: "zba", Feature: &RISCV64.HasZba},
|
||||||
{Name: "zbb", Feature: &RISCV64.HasZbb},
|
{Name: "zbb", Feature: &RISCV64.HasZbb},
|
||||||
{Name: "zbs", Feature: &RISCV64.HasZbs},
|
{Name: "zbs", Feature: &RISCV64.HasZbs},
|
||||||
|
{Name: "zbc", Feature: &RISCV64.HasZbc},
|
||||||
// RISC-V Cryptography Extensions
|
// RISC-V Cryptography Extensions
|
||||||
{Name: "zvbb", Feature: &RISCV64.HasZvbb},
|
{Name: "zvbb", Feature: &RISCV64.HasZvbb},
|
||||||
{Name: "zvbc", Feature: &RISCV64.HasZvbc},
|
{Name: "zvbc", Feature: &RISCV64.HasZvbc},
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cpu
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -systemdll=false -output zcpu_windows.go cpu_windows.go
|
||||||
|
|
||||||
|
//sys isProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) = kernel32.IsProcessorFeaturePresent
|
||||||
|
|
||||||
|
// The processor features to be tested for IsProcessorFeaturePresent, see
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
|
||||||
|
const (
|
||||||
|
_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30
|
||||||
|
_PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE = 31
|
||||||
|
_PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE = 34
|
||||||
|
_PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE = 43
|
||||||
|
|
||||||
|
_PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE = 44
|
||||||
|
_PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE = 45
|
||||||
|
_PF_ARM_SVE_INSTRUCTIONS_AVAILABLE = 46
|
||||||
|
_PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE = 47
|
||||||
|
|
||||||
|
_PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE = 64
|
||||||
|
_PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE = 65
|
||||||
|
)
|
||||||
+10
-14
@@ -4,10 +4,6 @@
|
|||||||
|
|
||||||
package cpu
|
package cpu
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func doinit() {
|
func doinit() {
|
||||||
// set HasASIMD and HasFP to true as per
|
// set HasASIMD and HasFP to true as per
|
||||||
// https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements
|
// https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements
|
||||||
@@ -18,25 +14,25 @@ func doinit() {
|
|||||||
ARM64.HasASIMD = true
|
ARM64.HasASIMD = true
|
||||||
ARM64.HasFP = true
|
ARM64.HasFP = true
|
||||||
|
|
||||||
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) {
|
if isProcessorFeaturePresent(_PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) {
|
||||||
ARM64.HasAES = true
|
ARM64.HasAES = true
|
||||||
ARM64.HasPMULL = true
|
ARM64.HasPMULL = true
|
||||||
ARM64.HasSHA1 = true
|
ARM64.HasSHA1 = true
|
||||||
ARM64.HasSHA2 = true
|
ARM64.HasSHA2 = true
|
||||||
}
|
}
|
||||||
ARM64.HasSHA3 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
|
ARM64.HasSHA3 = isProcessorFeaturePresent(_PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
|
||||||
ARM64.HasCRC32 = windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
|
ARM64.HasCRC32 = isProcessorFeaturePresent(_PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
|
||||||
ARM64.HasSHA512 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
|
ARM64.HasSHA512 = isProcessorFeaturePresent(_PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
|
||||||
ARM64.HasATOMICS = windows.IsProcessorFeaturePresent(windows.PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
|
ARM64.HasATOMICS = isProcessorFeaturePresent(_PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
|
||||||
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
|
if isProcessorFeaturePresent(_PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
|
||||||
ARM64.HasASIMDDP = true
|
ARM64.HasASIMDDP = true
|
||||||
ARM64.HasASIMDRDM = true
|
ARM64.HasASIMDRDM = true
|
||||||
}
|
}
|
||||||
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) {
|
if isProcessorFeaturePresent(_PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) {
|
||||||
ARM64.HasLRCPC = true
|
ARM64.HasLRCPC = true
|
||||||
ARM64.HasSM3 = true
|
ARM64.HasSM3 = true
|
||||||
}
|
}
|
||||||
ARM64.HasSVE = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
|
ARM64.HasSVE = isProcessorFeaturePresent(_PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
|
||||||
ARM64.HasSVE2 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
|
ARM64.HasSVE2 = isProcessorFeaturePresent(_PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
|
||||||
ARM64.HasJSCVT = windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
|
ARM64.HasJSCVT = isProcessorFeaturePresent(_PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
|
||||||
}
|
}
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
// Code generated by 'go generate'; DO NOT EDIT.
|
||||||
|
|
||||||
|
package cpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
procIsProcessorFeaturePresent = modkernel32.NewProc("IsProcessorFeaturePresent")
|
||||||
|
)
|
||||||
|
|
||||||
|
func isProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procIsProcessorFeaturePresent.Addr(), uintptr(ProcessorFeature))
|
||||||
|
ret = r0 != 0
|
||||||
|
return
|
||||||
|
}
|
||||||
+112
-16
@@ -13,11 +13,19 @@ import (
|
|||||||
|
|
||||||
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS
|
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS
|
||||||
|
|
||||||
// CPUSet represents a CPU affinity mask.
|
// CPUSet represents a bit mask of CPUs, to be used with [SchedGetaffinity], [SchedSetaffinity],
|
||||||
|
// and [SetMemPolicy].
|
||||||
|
//
|
||||||
|
// Note this type can only represent CPU IDs 0 through 1023.
|
||||||
|
// Use [CPUSetDynamic]/[NewCPUSet] instead to avoid this limit.
|
||||||
type CPUSet [cpuSetSize]cpuMask
|
type CPUSet [cpuSetSize]cpuMask
|
||||||
|
|
||||||
func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
|
// CPUSetDynamic represents a bit mask of CPUs, to be used with [SchedGetaffinityDynamic],
|
||||||
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set)))
|
// [SchedSetaffinityDynamic], and [SetMemPolicyDynamic]. Use [NewCPUSet] to allocate.
|
||||||
|
type CPUSetDynamic []cpuMask
|
||||||
|
|
||||||
|
func schedAffinity(trap uintptr, pid int, size uintptr, ptr unsafe.Pointer) error {
|
||||||
|
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(size), uintptr(ptr))
|
||||||
if e != 0 {
|
if e != 0 {
|
||||||
return errnoErr(e)
|
return errnoErr(e)
|
||||||
}
|
}
|
||||||
@@ -27,13 +35,13 @@ func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
|
|||||||
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
|
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
|
||||||
// If pid is 0 the calling thread is used.
|
// If pid is 0 the calling thread is used.
|
||||||
func SchedGetaffinity(pid int, set *CPUSet) error {
|
func SchedGetaffinity(pid int, set *CPUSet) error {
|
||||||
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set)
|
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, unsafe.Sizeof(*set), unsafe.Pointer(set))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
|
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
|
||||||
// If pid is 0 the calling thread is used.
|
// If pid is 0 the calling thread is used.
|
||||||
func SchedSetaffinity(pid int, set *CPUSet) error {
|
func SchedSetaffinity(pid int, set *CPUSet) error {
|
||||||
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set)
|
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, unsafe.Sizeof(*set), unsafe.Pointer(set))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zero clears the set s, so that it contains no CPUs.
|
// Zero clears the set s, so that it contains no CPUs.
|
||||||
@@ -45,9 +53,7 @@ func (s *CPUSet) Zero() {
|
|||||||
// will silently ignore any invalid CPU bits in [CPUSet] so this is an
|
// will silently ignore any invalid CPU bits in [CPUSet] so this is an
|
||||||
// efficient way of resetting the CPU affinity of a process.
|
// efficient way of resetting the CPU affinity of a process.
|
||||||
func (s *CPUSet) Fill() {
|
func (s *CPUSet) Fill() {
|
||||||
for i := range s {
|
cpuMaskFill(s[:])
|
||||||
s[i] = ^cpuMask(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func cpuBitsIndex(cpu int) int {
|
func cpuBitsIndex(cpu int) int {
|
||||||
@@ -58,24 +64,27 @@ func cpuBitsMask(cpu int) cpuMask {
|
|||||||
return cpuMask(1 << (uint(cpu) % _NCPUBITS))
|
return cpuMask(1 << (uint(cpu) % _NCPUBITS))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set adds cpu to the set s.
|
func cpuMaskFill(s []cpuMask) {
|
||||||
func (s *CPUSet) Set(cpu int) {
|
for i := range s {
|
||||||
|
s[i] = ^cpuMask(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuMaskSet(s []cpuMask, cpu int) {
|
||||||
i := cpuBitsIndex(cpu)
|
i := cpuBitsIndex(cpu)
|
||||||
if i < len(s) {
|
if i < len(s) {
|
||||||
s[i] |= cpuBitsMask(cpu)
|
s[i] |= cpuBitsMask(cpu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear removes cpu from the set s.
|
func cpuMaskClear(s []cpuMask, cpu int) {
|
||||||
func (s *CPUSet) Clear(cpu int) {
|
|
||||||
i := cpuBitsIndex(cpu)
|
i := cpuBitsIndex(cpu)
|
||||||
if i < len(s) {
|
if i < len(s) {
|
||||||
s[i] &^= cpuBitsMask(cpu)
|
s[i] &^= cpuBitsMask(cpu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSet reports whether cpu is in the set s.
|
func cpuMaskIsSet(s []cpuMask, cpu int) bool {
|
||||||
func (s *CPUSet) IsSet(cpu int) bool {
|
|
||||||
i := cpuBitsIndex(cpu)
|
i := cpuBitsIndex(cpu)
|
||||||
if i < len(s) {
|
if i < len(s) {
|
||||||
return s[i]&cpuBitsMask(cpu) != 0
|
return s[i]&cpuBitsMask(cpu) != 0
|
||||||
@@ -83,11 +92,98 @@ func (s *CPUSet) IsSet(cpu int) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count returns the number of CPUs in the set s.
|
func cpuMaskCount(s []cpuMask) int {
|
||||||
func (s *CPUSet) Count() int {
|
|
||||||
c := 0
|
c := 0
|
||||||
for _, b := range s {
|
for _, b := range s {
|
||||||
c += bits.OnesCount64(uint64(b))
|
c += bits.OnesCount64(uint64(b))
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set adds cpu to the set s. If cpu is out of bounds for s, no action is taken.
|
||||||
|
func (s *CPUSet) Set(cpu int) {
|
||||||
|
cpuMaskSet(s[:], cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes cpu from the set s. If cpu is out of bounds for s, no action is taken.
|
||||||
|
func (s *CPUSet) Clear(cpu int) {
|
||||||
|
cpuMaskClear(s[:], cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet reports whether cpu is in the set s.
|
||||||
|
func (s *CPUSet) IsSet(cpu int) bool {
|
||||||
|
return cpuMaskIsSet(s[:], cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of CPUs in the set s.
|
||||||
|
func (s *CPUSet) Count() int {
|
||||||
|
return cpuMaskCount(s[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCPUSet creates a CPU affinity mask capable of representing CPU IDs
|
||||||
|
// up to maxCPU (exclusive).
|
||||||
|
func NewCPUSet(maxCPU int) CPUSetDynamic {
|
||||||
|
numMasks := (maxCPU + _NCPUBITS - 1) / _NCPUBITS
|
||||||
|
if numMasks == 0 {
|
||||||
|
numMasks = 1
|
||||||
|
}
|
||||||
|
return make(CPUSetDynamic, numMasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero clears the set s, so that it contains no CPUs.
|
||||||
|
func (s CPUSetDynamic) Zero() {
|
||||||
|
clear(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill adds all possible CPU bits to the set s. On Linux, [SchedSetaffinityDynamic]
|
||||||
|
// will silently ignore any invalid CPU bits in [CPUSetDynamic] so this is an
|
||||||
|
// efficient way of resetting the CPU affinity of a process.
|
||||||
|
func (s CPUSetDynamic) Fill() {
|
||||||
|
cpuMaskFill(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set adds cpu to the set s. If cpu is out of bounds for s, no action is taken.
|
||||||
|
func (s CPUSetDynamic) Set(cpu int) {
|
||||||
|
cpuMaskSet(s, cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes cpu from the set s. If cpu is out of bounds for s, no action is taken.
|
||||||
|
func (s CPUSetDynamic) Clear(cpu int) {
|
||||||
|
cpuMaskClear(s, cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet reports whether cpu is in the set s.
|
||||||
|
func (s CPUSetDynamic) IsSet(cpu int) bool {
|
||||||
|
return cpuMaskIsSet(s, cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of CPUs in the set s.
|
||||||
|
func (s CPUSetDynamic) Count() int {
|
||||||
|
return cpuMaskCount(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CPUSetDynamic) size() uintptr {
|
||||||
|
return uintptr(len(s)) * unsafe.Sizeof(cpuMask(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s CPUSetDynamic) pointer() unsafe.Pointer {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return unsafe.Pointer(&s[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedGetaffinityDynamic gets the CPU affinity mask of the thread specified by pid.
|
||||||
|
// If pid is 0 the calling thread is used.
|
||||||
|
//
|
||||||
|
// If the set is smaller than the size of the affinity mask used by the kernel,
|
||||||
|
// [EINVAL] is returned.
|
||||||
|
func SchedGetaffinityDynamic(pid int, set CPUSetDynamic) error {
|
||||||
|
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set.size(), set.pointer())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedSetaffinityDynamic sets the CPU affinity mask of the thread specified by pid.
|
||||||
|
// If pid is 0 the calling thread is used.
|
||||||
|
func SchedSetaffinityDynamic(pid int, set CPUSetDynamic) error {
|
||||||
|
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set.size(), set.pointer())
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ if [[ "$GOOS" = "linux" ]]; then
|
|||||||
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
|
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
|
||||||
set -e
|
set -e
|
||||||
$cmd docker build --tag generate:$GOOS $GOOS
|
$cmd docker build --tag generate:$GOOS $GOOS
|
||||||
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
|
$cmd docker run --rm --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+3
@@ -354,6 +354,9 @@ struct ltchars {
|
|||||||
// Renamed in v6.16, commit c6d732c38f93 ("net: ethtool: remove duplicate defines for family info")
|
// Renamed in v6.16, commit c6d732c38f93 ("net: ethtool: remove duplicate defines for family info")
|
||||||
#define ETHTOOL_FAMILY_NAME ETHTOOL_GENL_NAME
|
#define ETHTOOL_FAMILY_NAME ETHTOOL_GENL_NAME
|
||||||
#define ETHTOOL_FAMILY_VERSION ETHTOOL_GENL_VERSION
|
#define ETHTOOL_FAMILY_VERSION ETHTOOL_GENL_VERSION
|
||||||
|
|
||||||
|
// Removed in v6.17, commit 760e6f7befba ("futex: Remove support for IMMUTABLE")
|
||||||
|
#define PR_FUTEX_HASH_GET_IMMUTABLE 3
|
||||||
'
|
'
|
||||||
|
|
||||||
includes_NetBSD='
|
includes_NetBSD='
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build darwin || linux || openbsd
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
// minIovec is the size of the small initial allocation used by
|
||||||
|
// Readv, Writev, etc.
|
||||||
|
//
|
||||||
|
// This small allocation gets stack allocated, which lets the
|
||||||
|
// common use case of len(iovs) <= minIovec avoid more expensive
|
||||||
|
// heap allocations.
|
||||||
|
const minIovec = 8
|
||||||
|
|
||||||
|
// appendBytes converts bs to Iovecs and appends them to vecs.
|
||||||
|
func appendBytes(vecs []Iovec, bs [][]byte) []Iovec {
|
||||||
|
for _, b := range bs {
|
||||||
|
var v Iovec
|
||||||
|
v.SetLen(len(b))
|
||||||
|
if len(b) > 0 {
|
||||||
|
v.Base = &b[0]
|
||||||
|
} else {
|
||||||
|
v.Base = (*byte)(unsafe.Pointer(&_zero))
|
||||||
|
}
|
||||||
|
vecs = append(vecs, v)
|
||||||
|
}
|
||||||
|
return vecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// writevRaceDetect tells the race detector that the program
|
||||||
|
// has read the first n bytes stored in iovecs.
|
||||||
|
func writevRaceDetect(iovecs []Iovec, n int) {
|
||||||
|
if !raceenabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; n > 0 && i < len(iovecs); i++ {
|
||||||
|
m := min(int(iovecs[i].Len), n)
|
||||||
|
n -= m
|
||||||
|
if m > 0 {
|
||||||
|
raceReadRange(unsafe.Pointer(iovecs[i].Base), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readvRaceDetect tells the race detector that the program
|
||||||
|
// has written to the first n bytes stored in iovecs.
|
||||||
|
func readvRaceDetect(iovecs []Iovec, n int, err error) {
|
||||||
|
if !raceenabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; n > 0 && i < len(iovecs); i++ {
|
||||||
|
m := min(int(iovecs[i].Len), n)
|
||||||
|
n -= m
|
||||||
|
if m > 0 {
|
||||||
|
raceWriteRange(unsafe.Pointer(iovecs[i].Base), m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
raceAcquire(unsafe.Pointer(&ioSync))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Readv(fd int, iovs [][]byte) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
n, err = readv(fd, iovecs)
|
||||||
|
readvRaceDetect(iovecs, n, err)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
n, err = preadv(fd, iovecs, offset)
|
||||||
|
readvRaceDetect(iovecs, n, err)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Writev(fd int, iovs [][]byte) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
if raceenabled {
|
||||||
|
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
||||||
|
}
|
||||||
|
n, err = writev(fd, iovecs)
|
||||||
|
writevRaceDetect(iovecs, n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
||||||
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
|
if raceenabled {
|
||||||
|
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
||||||
|
}
|
||||||
|
n, err = pwritev(fd, iovecs, offset)
|
||||||
|
writevRaceDetect(iovecs, n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
-89
@@ -602,95 +602,6 @@ func Connectx(fd int, srcIf uint32, srcAddr, dstAddr Sockaddr, associd SaeAssocI
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const minIovec = 8
|
|
||||||
|
|
||||||
func Readv(fd int, iovs [][]byte) (n int, err error) {
|
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
n, err = readv(fd, iovecs)
|
|
||||||
readvRacedetect(iovecs, n, err)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
n, err = preadv(fd, iovecs, offset)
|
|
||||||
readvRacedetect(iovecs, n, err)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Writev(fd int, iovs [][]byte) (n int, err error) {
|
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
if raceenabled {
|
|
||||||
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
|
||||||
}
|
|
||||||
n, err = writev(fd, iovecs)
|
|
||||||
writevRacedetect(iovecs, n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
if raceenabled {
|
|
||||||
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
|
||||||
}
|
|
||||||
n, err = pwritev(fd, iovecs, offset)
|
|
||||||
writevRacedetect(iovecs, n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendBytes(vecs []Iovec, bs [][]byte) []Iovec {
|
|
||||||
for _, b := range bs {
|
|
||||||
var v Iovec
|
|
||||||
v.SetLen(len(b))
|
|
||||||
if len(b) > 0 {
|
|
||||||
v.Base = &b[0]
|
|
||||||
} else {
|
|
||||||
v.Base = (*byte)(unsafe.Pointer(&_zero))
|
|
||||||
}
|
|
||||||
vecs = append(vecs, v)
|
|
||||||
}
|
|
||||||
return vecs
|
|
||||||
}
|
|
||||||
|
|
||||||
func writevRacedetect(iovecs []Iovec, n int) {
|
|
||||||
if !raceenabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; n > 0 && i < len(iovecs); i++ {
|
|
||||||
m := int(iovecs[i].Len)
|
|
||||||
if m > n {
|
|
||||||
m = n
|
|
||||||
}
|
|
||||||
n -= m
|
|
||||||
if m > 0 {
|
|
||||||
raceReadRange(unsafe.Pointer(iovecs[i].Base), m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readvRacedetect(iovecs []Iovec, n int, err error) {
|
|
||||||
if !raceenabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; n > 0 && i < len(iovecs); i++ {
|
|
||||||
m := int(iovecs[i].Len)
|
|
||||||
if m > n {
|
|
||||||
m = n
|
|
||||||
}
|
|
||||||
n -= m
|
|
||||||
if m > 0 {
|
|
||||||
raceWriteRange(unsafe.Pointer(iovecs[i].Base), m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
raceAcquire(unsafe.Pointer(&ioSync))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error)
|
//sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error)
|
||||||
//sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error)
|
//sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error)
|
||||||
|
|
||||||
|
|||||||
+18
-96
@@ -2150,33 +2150,10 @@ func Signalfd(fd int, sigmask *Sigset_t, flags int) (newfd int, err error) {
|
|||||||
//sys exitThread(code int) (err error) = SYS_EXIT
|
//sys exitThread(code int) (err error) = SYS_EXIT
|
||||||
//sys readv(fd int, iovs []Iovec) (n int, err error) = SYS_READV
|
//sys readv(fd int, iovs []Iovec) (n int, err error) = SYS_READV
|
||||||
//sys writev(fd int, iovs []Iovec) (n int, err error) = SYS_WRITEV
|
//sys writev(fd int, iovs []Iovec) (n int, err error) = SYS_WRITEV
|
||||||
//sys preadv(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PREADV
|
//sys preadvSyscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PREADV
|
||||||
//sys pwritev(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PWRITEV
|
//sys pwritevSyscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr) (n int, err error) = SYS_PWRITEV
|
||||||
//sys preadv2(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PREADV2
|
//sys preadv2Syscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PREADV2
|
||||||
//sys pwritev2(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PWRITEV2
|
//sys pwritev2Syscall(fd int, iovs []Iovec, offs_l uintptr, offs_h uintptr, flags int) (n int, err error) = SYS_PWRITEV2
|
||||||
|
|
||||||
// minIovec is the size of the small initial allocation used by
|
|
||||||
// Readv, Writev, etc.
|
|
||||||
//
|
|
||||||
// This small allocation gets stack allocated, which lets the
|
|
||||||
// common use case of len(iovs) <= minIovs avoid more expensive
|
|
||||||
// heap allocations.
|
|
||||||
const minIovec = 8
|
|
||||||
|
|
||||||
// appendBytes converts bs to Iovecs and appends them to vecs.
|
|
||||||
func appendBytes(vecs []Iovec, bs [][]byte) []Iovec {
|
|
||||||
for _, b := range bs {
|
|
||||||
var v Iovec
|
|
||||||
v.SetLen(len(b))
|
|
||||||
if len(b) > 0 {
|
|
||||||
v.Base = &b[0]
|
|
||||||
} else {
|
|
||||||
v.Base = (*byte)(unsafe.Pointer(&_zero))
|
|
||||||
}
|
|
||||||
vecs = append(vecs, v)
|
|
||||||
}
|
|
||||||
return vecs
|
|
||||||
}
|
|
||||||
|
|
||||||
// offs2lohi splits offs into its low and high order bits.
|
// offs2lohi splits offs into its low and high order bits.
|
||||||
func offs2lohi(offs int64) (lo, hi uintptr) {
|
func offs2lohi(offs int64) (lo, hi uintptr) {
|
||||||
@@ -2184,69 +2161,23 @@ func offs2lohi(offs int64) (lo, hi uintptr) {
|
|||||||
return uintptr(offs), uintptr(uint64(offs) >> (longBits - 1) >> 1) // two shifts to avoid false positive in vet
|
return uintptr(offs), uintptr(uint64(offs) >> (longBits - 1) >> 1) // two shifts to avoid false positive in vet
|
||||||
}
|
}
|
||||||
|
|
||||||
func Readv(fd int, iovs [][]byte) (n int, err error) {
|
func preadv(fd int, iovecs []Iovec, offset int64) (n int, err error) {
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
n, err = readv(fd, iovecs)
|
|
||||||
readvRacedetect(iovecs, n, err)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
lo, hi := offs2lohi(offset)
|
lo, hi := offs2lohi(offset)
|
||||||
n, err = preadv(fd, iovecs, lo, hi)
|
return preadvSyscall(fd, iovecs, lo, hi)
|
||||||
readvRacedetect(iovecs, n, err)
|
|
||||||
return n, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Preadv2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) {
|
func Preadv2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) {
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
iovecs := make([]Iovec, 0, minIovec)
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
iovecs = appendBytes(iovecs, iovs)
|
||||||
lo, hi := offs2lohi(offset)
|
lo, hi := offs2lohi(offset)
|
||||||
n, err = preadv2(fd, iovecs, lo, hi, flags)
|
n, err = preadv2Syscall(fd, iovecs, lo, hi, flags)
|
||||||
readvRacedetect(iovecs, n, err)
|
readvRaceDetect(iovecs, n, err)
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func readvRacedetect(iovecs []Iovec, n int, err error) {
|
func pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error) {
|
||||||
if !raceenabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; n > 0 && i < len(iovecs); i++ {
|
|
||||||
m := min(int(iovecs[i].Len), n)
|
|
||||||
n -= m
|
|
||||||
if m > 0 {
|
|
||||||
raceWriteRange(unsafe.Pointer(iovecs[i].Base), m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
raceAcquire(unsafe.Pointer(&ioSync))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Writev(fd int, iovs [][]byte) (n int, err error) {
|
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
if raceenabled {
|
|
||||||
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
|
||||||
}
|
|
||||||
n, err = writev(fd, iovecs)
|
|
||||||
writevRacedetect(iovecs, n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) {
|
|
||||||
iovecs := make([]Iovec, 0, minIovec)
|
|
||||||
iovecs = appendBytes(iovecs, iovs)
|
|
||||||
if raceenabled {
|
|
||||||
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
|
||||||
}
|
|
||||||
lo, hi := offs2lohi(offset)
|
lo, hi := offs2lohi(offset)
|
||||||
n, err = pwritev(fd, iovecs, lo, hi)
|
return pwritevSyscall(fd, iovecs, lo, hi)
|
||||||
writevRacedetect(iovecs, n)
|
|
||||||
return n, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pwritev2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) {
|
func Pwritev2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error) {
|
||||||
@@ -2256,24 +2187,11 @@ func Pwritev2(fd int, iovs [][]byte, offset int64, flags int) (n int, err error)
|
|||||||
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
raceReleaseMerge(unsafe.Pointer(&ioSync))
|
||||||
}
|
}
|
||||||
lo, hi := offs2lohi(offset)
|
lo, hi := offs2lohi(offset)
|
||||||
n, err = pwritev2(fd, iovecs, lo, hi, flags)
|
n, err = pwritev2Syscall(fd, iovecs, lo, hi, flags)
|
||||||
writevRacedetect(iovecs, n)
|
writevRaceDetect(iovecs, n)
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writevRacedetect(iovecs []Iovec, n int) {
|
|
||||||
if !raceenabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; n > 0 && i < len(iovecs); i++ {
|
|
||||||
m := min(int(iovecs[i].Len), n)
|
|
||||||
n -= m
|
|
||||||
if m > 0 {
|
|
||||||
raceReadRange(unsafe.Pointer(iovecs[i].Base), m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mmap varies by architecture; see syscall_linux_*.go.
|
// mmap varies by architecture; see syscall_linux_*.go.
|
||||||
//sys munmap(addr uintptr, length uintptr) (err error)
|
//sys munmap(addr uintptr, length uintptr) (err error)
|
||||||
//sys mremap(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (xaddr uintptr, err error)
|
//sys mremap(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (xaddr uintptr, err error)
|
||||||
@@ -2644,8 +2562,12 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) {
|
|||||||
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error)
|
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error)
|
||||||
//sys Mseal(b []byte, flags uint) (err error)
|
//sys Mseal(b []byte, flags uint) (err error)
|
||||||
|
|
||||||
//sys setMemPolicy(mode int, mask *CPUSet, size int) (err error) = SYS_SET_MEMPOLICY
|
//sys setMemPolicy(mode int, mask unsafe.Pointer, size uintptr) (err error) = SYS_SET_MEMPOLICY
|
||||||
|
|
||||||
func SetMemPolicy(mode int, mask *CPUSet) error {
|
func SetMemPolicy(mode int, mask *CPUSet) error {
|
||||||
return setMemPolicy(mode, mask, _CPU_SETSIZE)
|
return setMemPolicy(mode, unsafe.Pointer(mask), _CPU_SETSIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetMemPolicyDynamic(mode int, mask CPUSetDynamic) error {
|
||||||
|
return setMemPolicy(mode, mask.pointer(), mask.size())
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -82,6 +82,9 @@ func Time(t *Time_t) (Time_t, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Utime(path string, buf *Utimbuf) error {
|
func Utime(path string, buf *Utimbuf) error {
|
||||||
|
if buf == nil {
|
||||||
|
return Utimes(path, nil)
|
||||||
|
}
|
||||||
tv := []Timeval{
|
tv := []Timeval{
|
||||||
{Sec: buf.Actime},
|
{Sec: buf.Actime},
|
||||||
{Sec: buf.Modtime},
|
{Sec: buf.Modtime},
|
||||||
|
|||||||
+3
@@ -113,6 +113,9 @@ func Time(t *Time_t) (Time_t, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Utime(path string, buf *Utimbuf) error {
|
func Utime(path string, buf *Utimbuf) error {
|
||||||
|
if buf == nil {
|
||||||
|
return Utimes(path, nil)
|
||||||
|
}
|
||||||
tv := []Timeval{
|
tv := []Timeval{
|
||||||
{Sec: buf.Actime},
|
{Sec: buf.Actime},
|
||||||
{Sec: buf.Modtime},
|
{Sec: buf.Modtime},
|
||||||
|
|||||||
+3
@@ -150,6 +150,9 @@ func Time(t *Time_t) (Time_t, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Utime(path string, buf *Utimbuf) error {
|
func Utime(path string, buf *Utimbuf) error {
|
||||||
|
if buf == nil {
|
||||||
|
return Utimes(path, nil)
|
||||||
|
}
|
||||||
tv := []Timeval{
|
tv := []Timeval{
|
||||||
{Sec: buf.Actime},
|
{Sec: buf.Actime},
|
||||||
{Sec: buf.Modtime},
|
{Sec: buf.Modtime},
|
||||||
|
|||||||
+3
@@ -112,6 +112,9 @@ func Time(t *Time_t) (Time_t, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Utime(path string, buf *Utimbuf) error {
|
func Utime(path string, buf *Utimbuf) error {
|
||||||
|
if buf == nil {
|
||||||
|
return Utimes(path, nil)
|
||||||
|
}
|
||||||
tv := []Timeval{
|
tv := []Timeval{
|
||||||
{Sec: buf.Actime},
|
{Sec: buf.Actime},
|
||||||
{Sec: buf.Modtime},
|
{Sec: buf.Modtime},
|
||||||
|
|||||||
+4
@@ -300,6 +300,10 @@ func Uname(uname *Utsname) error {
|
|||||||
//sys Pathconf(path string, name int) (val int, err error)
|
//sys Pathconf(path string, name int) (val int, err error)
|
||||||
//sys pread(fd int, p []byte, offset int64) (n int, err error)
|
//sys pread(fd int, p []byte, offset int64) (n int, err error)
|
||||||
//sys pwrite(fd int, p []byte, offset int64) (n int, err error)
|
//sys pwrite(fd int, p []byte, offset int64) (n int, err error)
|
||||||
|
//sys readv(fd int, iovecs []Iovec) (n int, err error)
|
||||||
|
//sys writev(fd int, iovecs []Iovec) (n int, err error)
|
||||||
|
//sys preadv(fd int, iovecs []Iovec, offset int64) (n int, err error)
|
||||||
|
//sys pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error)
|
||||||
//sys read(fd int, p []byte) (n int, err error)
|
//sys read(fd int, p []byte) (n int, err error)
|
||||||
//sys Readlink(path string, buf []byte) (n int, err error)
|
//sys Readlink(path string, buf []byte) (n int, err error)
|
||||||
//sys Readlinkat(dirfd int, path string, buf []byte) (n int, err error)
|
//sys Readlinkat(dirfd int, path string, buf []byte) (n int, err error)
|
||||||
|
|||||||
+59
-2
@@ -353,8 +353,10 @@ const (
|
|||||||
AUDIT_MAC_IPSEC_EVENT = 0x587
|
AUDIT_MAC_IPSEC_EVENT = 0x587
|
||||||
AUDIT_MAC_MAP_ADD = 0x581
|
AUDIT_MAC_MAP_ADD = 0x581
|
||||||
AUDIT_MAC_MAP_DEL = 0x582
|
AUDIT_MAC_MAP_DEL = 0x582
|
||||||
|
AUDIT_MAC_OBJ_CONTEXTS = 0x592
|
||||||
AUDIT_MAC_POLICY_LOAD = 0x57b
|
AUDIT_MAC_POLICY_LOAD = 0x57b
|
||||||
AUDIT_MAC_STATUS = 0x57c
|
AUDIT_MAC_STATUS = 0x57c
|
||||||
|
AUDIT_MAC_TASK_CONTEXTS = 0x591
|
||||||
AUDIT_MAC_UNLBL_ALLOW = 0x57e
|
AUDIT_MAC_UNLBL_ALLOW = 0x57e
|
||||||
AUDIT_MAC_UNLBL_STCADD = 0x588
|
AUDIT_MAC_UNLBL_STCADD = 0x588
|
||||||
AUDIT_MAC_UNLBL_STCDEL = 0x589
|
AUDIT_MAC_UNLBL_STCDEL = 0x589
|
||||||
@@ -591,8 +593,13 @@ const (
|
|||||||
CAN_CTRLMODE_LOOPBACK = 0x1
|
CAN_CTRLMODE_LOOPBACK = 0x1
|
||||||
CAN_CTRLMODE_ONE_SHOT = 0x8
|
CAN_CTRLMODE_ONE_SHOT = 0x8
|
||||||
CAN_CTRLMODE_PRESUME_ACK = 0x40
|
CAN_CTRLMODE_PRESUME_ACK = 0x40
|
||||||
|
CAN_CTRLMODE_RESTRICTED = 0x800
|
||||||
CAN_CTRLMODE_TDC_AUTO = 0x200
|
CAN_CTRLMODE_TDC_AUTO = 0x200
|
||||||
CAN_CTRLMODE_TDC_MANUAL = 0x400
|
CAN_CTRLMODE_TDC_MANUAL = 0x400
|
||||||
|
CAN_CTRLMODE_XL = 0x1000
|
||||||
|
CAN_CTRLMODE_XL_TDC_AUTO = 0x2000
|
||||||
|
CAN_CTRLMODE_XL_TDC_MANUAL = 0x4000
|
||||||
|
CAN_CTRLMODE_XL_TMS = 0x8000
|
||||||
CAN_EFF_FLAG = 0x80000000
|
CAN_EFF_FLAG = 0x80000000
|
||||||
CAN_EFF_ID_BITS = 0x1d
|
CAN_EFF_ID_BITS = 0x1d
|
||||||
CAN_EFF_MASK = 0x1fffffff
|
CAN_EFF_MASK = 0x1fffffff
|
||||||
@@ -800,6 +807,8 @@ const (
|
|||||||
DEVLINK_PORT_FN_CAP_IPSEC_PACKET = 0x8
|
DEVLINK_PORT_FN_CAP_IPSEC_PACKET = 0x8
|
||||||
DEVLINK_PORT_FN_CAP_MIGRATABLE = 0x2
|
DEVLINK_PORT_FN_CAP_MIGRATABLE = 0x2
|
||||||
DEVLINK_PORT_FN_CAP_ROCE = 0x1
|
DEVLINK_PORT_FN_CAP_ROCE = 0x1
|
||||||
|
DEVLINK_RATE_TCS_MAX = 0x8
|
||||||
|
DEVLINK_RATE_TC_INDEX_MAX = 0x7
|
||||||
DEVLINK_SB_THRESHOLD_TO_ALPHA_MAX = 0x14
|
DEVLINK_SB_THRESHOLD_TO_ALPHA_MAX = 0x14
|
||||||
DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS = 0x3
|
DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS = 0x3
|
||||||
DEVMEM_MAGIC = 0x454d444d
|
DEVMEM_MAGIC = 0x454d444d
|
||||||
@@ -1186,6 +1195,7 @@ const (
|
|||||||
ETH_P_MPLS_UC = 0x8847
|
ETH_P_MPLS_UC = 0x8847
|
||||||
ETH_P_MRP = 0x88e3
|
ETH_P_MRP = 0x88e3
|
||||||
ETH_P_MVRP = 0x88f5
|
ETH_P_MVRP = 0x88f5
|
||||||
|
ETH_P_MXLGSW = 0x88c3
|
||||||
ETH_P_NCSI = 0x88f8
|
ETH_P_NCSI = 0x88f8
|
||||||
ETH_P_NSH = 0x894f
|
ETH_P_NSH = 0x894f
|
||||||
ETH_P_PAE = 0x888e
|
ETH_P_PAE = 0x888e
|
||||||
@@ -1218,6 +1228,7 @@ const (
|
|||||||
ETH_P_WCCP = 0x883e
|
ETH_P_WCCP = 0x883e
|
||||||
ETH_P_X25 = 0x805
|
ETH_P_X25 = 0x805
|
||||||
ETH_P_XDSA = 0xf8
|
ETH_P_XDSA = 0xf8
|
||||||
|
ETH_P_YT921X = 0x9988
|
||||||
ET_CORE = 0x4
|
ET_CORE = 0x4
|
||||||
ET_DYN = 0x3
|
ET_DYN = 0x3
|
||||||
ET_EXEC = 0x2
|
ET_EXEC = 0x2
|
||||||
@@ -1258,6 +1269,7 @@ const (
|
|||||||
FALLOC_FL_NO_HIDE_STALE = 0x4
|
FALLOC_FL_NO_HIDE_STALE = 0x4
|
||||||
FALLOC_FL_PUNCH_HOLE = 0x2
|
FALLOC_FL_PUNCH_HOLE = 0x2
|
||||||
FALLOC_FL_UNSHARE_RANGE = 0x40
|
FALLOC_FL_UNSHARE_RANGE = 0x40
|
||||||
|
FALLOC_FL_WRITE_ZEROES = 0x80
|
||||||
FALLOC_FL_ZERO_RANGE = 0x10
|
FALLOC_FL_ZERO_RANGE = 0x10
|
||||||
FANOTIFY_METADATA_VERSION = 0x3
|
FANOTIFY_METADATA_VERSION = 0x3
|
||||||
FAN_ACCESS = 0x1
|
FAN_ACCESS = 0x1
|
||||||
@@ -1477,6 +1489,7 @@ const (
|
|||||||
GRND_INSECURE = 0x4
|
GRND_INSECURE = 0x4
|
||||||
GRND_NONBLOCK = 0x1
|
GRND_NONBLOCK = 0x1
|
||||||
GRND_RANDOM = 0x2
|
GRND_RANDOM = 0x2
|
||||||
|
GUEST_MEMFD_MAGIC = 0x474d454d
|
||||||
HDIO_DRIVE_CMD = 0x31f
|
HDIO_DRIVE_CMD = 0x31f
|
||||||
HDIO_DRIVE_CMD_AEB = 0x31e
|
HDIO_DRIVE_CMD_AEB = 0x31e
|
||||||
HDIO_DRIVE_CMD_HDR_SIZE = 0x4
|
HDIO_DRIVE_CMD_HDR_SIZE = 0x4
|
||||||
@@ -1517,6 +1530,7 @@ const (
|
|||||||
HDIO_SET_XFER = 0x306
|
HDIO_SET_XFER = 0x306
|
||||||
HDIO_TRISTATE_HWIF = 0x31b
|
HDIO_TRISTATE_HWIF = 0x31b
|
||||||
HDIO_UNREGISTER_HWIF = 0x32a
|
HDIO_UNREGISTER_HWIF = 0x32a
|
||||||
|
HIDIOCTL_LAST = 0xd
|
||||||
HID_MAX_DESCRIPTOR_SIZE = 0x1000
|
HID_MAX_DESCRIPTOR_SIZE = 0x1000
|
||||||
HOSTFS_SUPER_MAGIC = 0xc0ffee
|
HOSTFS_SUPER_MAGIC = 0xc0ffee
|
||||||
HPFS_SUPER_MAGIC = 0xf995e849
|
HPFS_SUPER_MAGIC = 0xf995e849
|
||||||
@@ -1809,6 +1823,8 @@ const (
|
|||||||
KEXEC_ARCH_X86_64 = 0x3e0000
|
KEXEC_ARCH_X86_64 = 0x3e0000
|
||||||
KEXEC_CRASH_HOTPLUG_SUPPORT = 0x8
|
KEXEC_CRASH_HOTPLUG_SUPPORT = 0x8
|
||||||
KEXEC_FILE_DEBUG = 0x8
|
KEXEC_FILE_DEBUG = 0x8
|
||||||
|
KEXEC_FILE_FORCE_DTB = 0x20
|
||||||
|
KEXEC_FILE_NO_CMA = 0x10
|
||||||
KEXEC_FILE_NO_INITRAMFS = 0x4
|
KEXEC_FILE_NO_INITRAMFS = 0x4
|
||||||
KEXEC_FILE_ON_CRASH = 0x2
|
KEXEC_FILE_ON_CRASH = 0x2
|
||||||
KEXEC_FILE_UNLOAD = 0x1
|
KEXEC_FILE_UNLOAD = 0x1
|
||||||
@@ -1905,6 +1921,7 @@ const (
|
|||||||
LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON = 0x2
|
LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON = 0x2
|
||||||
LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF = 0x1
|
LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF = 0x1
|
||||||
LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF = 0x4
|
LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF = 0x4
|
||||||
|
LANDLOCK_RESTRICT_SELF_TSYNC = 0x8
|
||||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = 0x1
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = 0x1
|
||||||
LANDLOCK_SCOPE_SIGNAL = 0x2
|
LANDLOCK_SCOPE_SIGNAL = 0x2
|
||||||
LINUX_REBOOT_CMD_CAD_OFF = 0x0
|
LINUX_REBOOT_CMD_CAD_OFF = 0x0
|
||||||
@@ -2412,6 +2429,7 @@ const (
|
|||||||
NN_PRXFPREG = "LINUX"
|
NN_PRXFPREG = "LINUX"
|
||||||
NN_RISCV_CSR = "LINUX"
|
NN_RISCV_CSR = "LINUX"
|
||||||
NN_RISCV_TAGGED_ADDR_CTRL = "LINUX"
|
NN_RISCV_TAGGED_ADDR_CTRL = "LINUX"
|
||||||
|
NN_RISCV_USER_CFI = "LINUX"
|
||||||
NN_RISCV_VECTOR = "LINUX"
|
NN_RISCV_VECTOR = "LINUX"
|
||||||
NN_S390_CTRS = "LINUX"
|
NN_S390_CTRS = "LINUX"
|
||||||
NN_S390_GS_BC = "LINUX"
|
NN_S390_GS_BC = "LINUX"
|
||||||
@@ -2493,6 +2511,7 @@ const (
|
|||||||
NT_PRXFPREG = 0x46e62b7f
|
NT_PRXFPREG = 0x46e62b7f
|
||||||
NT_RISCV_CSR = 0x900
|
NT_RISCV_CSR = 0x900
|
||||||
NT_RISCV_TAGGED_ADDR_CTRL = 0x902
|
NT_RISCV_TAGGED_ADDR_CTRL = 0x902
|
||||||
|
NT_RISCV_USER_CFI = 0x903
|
||||||
NT_RISCV_VECTOR = 0x901
|
NT_RISCV_VECTOR = 0x901
|
||||||
NT_S390_CTRS = 0x304
|
NT_S390_CTRS = 0x304
|
||||||
NT_S390_GS_BC = 0x30c
|
NT_S390_GS_BC = 0x30c
|
||||||
@@ -2515,6 +2534,7 @@ const (
|
|||||||
NT_X86_SHSTK = 0x204
|
NT_X86_SHSTK = 0x204
|
||||||
NT_X86_XSAVE_LAYOUT = 0x205
|
NT_X86_XSAVE_LAYOUT = 0x205
|
||||||
NT_X86_XSTATE = 0x202
|
NT_X86_XSTATE = 0x202
|
||||||
|
NULL_FS_MAGIC = 0x4e554c4c
|
||||||
OCFS2_SUPER_MAGIC = 0x7461636f
|
OCFS2_SUPER_MAGIC = 0x7461636f
|
||||||
OCRNL = 0x8
|
OCRNL = 0x8
|
||||||
OFDEL = 0x80
|
OFDEL = 0x80
|
||||||
@@ -2594,6 +2614,7 @@ const (
|
|||||||
PERF_ATTR_SIZE_VER6 = 0x78
|
PERF_ATTR_SIZE_VER6 = 0x78
|
||||||
PERF_ATTR_SIZE_VER7 = 0x80
|
PERF_ATTR_SIZE_VER7 = 0x80
|
||||||
PERF_ATTR_SIZE_VER8 = 0x88
|
PERF_ATTR_SIZE_VER8 = 0x88
|
||||||
|
PERF_ATTR_SIZE_VER9 = 0x90
|
||||||
PERF_AUX_FLAG_COLLISION = 0x8
|
PERF_AUX_FLAG_COLLISION = 0x8
|
||||||
PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT = 0x0
|
PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT = 0x0
|
||||||
PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW = 0x100
|
PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW = 0x100
|
||||||
@@ -2629,6 +2650,7 @@ const (
|
|||||||
PERF_MEM_LVLNUM_ANY_CACHE = 0xb
|
PERF_MEM_LVLNUM_ANY_CACHE = 0xb
|
||||||
PERF_MEM_LVLNUM_CXL = 0x9
|
PERF_MEM_LVLNUM_CXL = 0x9
|
||||||
PERF_MEM_LVLNUM_IO = 0xa
|
PERF_MEM_LVLNUM_IO = 0xa
|
||||||
|
PERF_MEM_LVLNUM_L0 = 0x7
|
||||||
PERF_MEM_LVLNUM_L1 = 0x1
|
PERF_MEM_LVLNUM_L1 = 0x1
|
||||||
PERF_MEM_LVLNUM_L2 = 0x2
|
PERF_MEM_LVLNUM_L2 = 0x2
|
||||||
PERF_MEM_LVLNUM_L2_MHB = 0x5
|
PERF_MEM_LVLNUM_L2_MHB = 0x5
|
||||||
@@ -2662,6 +2684,23 @@ const (
|
|||||||
PERF_MEM_OP_PFETCH = 0x8
|
PERF_MEM_OP_PFETCH = 0x8
|
||||||
PERF_MEM_OP_SHIFT = 0x0
|
PERF_MEM_OP_SHIFT = 0x0
|
||||||
PERF_MEM_OP_STORE = 0x4
|
PERF_MEM_OP_STORE = 0x4
|
||||||
|
PERF_MEM_REGION_L_NON_SHARE = 0x3
|
||||||
|
PERF_MEM_REGION_L_SHARE = 0x2
|
||||||
|
PERF_MEM_REGION_MEM0 = 0x8
|
||||||
|
PERF_MEM_REGION_MEM1 = 0x9
|
||||||
|
PERF_MEM_REGION_MEM2 = 0xa
|
||||||
|
PERF_MEM_REGION_MEM3 = 0xb
|
||||||
|
PERF_MEM_REGION_MEM4 = 0xc
|
||||||
|
PERF_MEM_REGION_MEM5 = 0xd
|
||||||
|
PERF_MEM_REGION_MEM6 = 0xe
|
||||||
|
PERF_MEM_REGION_MEM7 = 0xf
|
||||||
|
PERF_MEM_REGION_MMIO = 0x7
|
||||||
|
PERF_MEM_REGION_NA = 0x0
|
||||||
|
PERF_MEM_REGION_O_IO = 0x4
|
||||||
|
PERF_MEM_REGION_O_NON_SHARE = 0x6
|
||||||
|
PERF_MEM_REGION_O_SHARE = 0x5
|
||||||
|
PERF_MEM_REGION_RSVD = 0x1
|
||||||
|
PERF_MEM_REGION_SHIFT = 0x2e
|
||||||
PERF_MEM_REMOTE_REMOTE = 0x1
|
PERF_MEM_REMOTE_REMOTE = 0x1
|
||||||
PERF_MEM_REMOTE_SHIFT = 0x25
|
PERF_MEM_REMOTE_SHIFT = 0x25
|
||||||
PERF_MEM_SNOOPX_FWD = 0x1
|
PERF_MEM_SNOOPX_FWD = 0x1
|
||||||
@@ -2776,6 +2815,10 @@ const (
|
|||||||
PR_CAP_AMBIENT_IS_SET = 0x1
|
PR_CAP_AMBIENT_IS_SET = 0x1
|
||||||
PR_CAP_AMBIENT_LOWER = 0x3
|
PR_CAP_AMBIENT_LOWER = 0x3
|
||||||
PR_CAP_AMBIENT_RAISE = 0x2
|
PR_CAP_AMBIENT_RAISE = 0x2
|
||||||
|
PR_CFI_BRANCH_LANDING_PADS = 0x0
|
||||||
|
PR_CFI_DISABLE = 0x2
|
||||||
|
PR_CFI_ENABLE = 0x1
|
||||||
|
PR_CFI_LOCK = 0x4
|
||||||
PR_ENDIAN_BIG = 0x0
|
PR_ENDIAN_BIG = 0x0
|
||||||
PR_ENDIAN_LITTLE = 0x1
|
PR_ENDIAN_LITTLE = 0x1
|
||||||
PR_ENDIAN_PPC_LITTLE = 0x2
|
PR_ENDIAN_PPC_LITTLE = 0x2
|
||||||
@@ -2798,6 +2841,7 @@ const (
|
|||||||
PR_FUTEX_HASH_GET_SLOTS = 0x2
|
PR_FUTEX_HASH_GET_SLOTS = 0x2
|
||||||
PR_FUTEX_HASH_SET_SLOTS = 0x1
|
PR_FUTEX_HASH_SET_SLOTS = 0x1
|
||||||
PR_GET_AUXV = 0x41555856
|
PR_GET_AUXV = 0x41555856
|
||||||
|
PR_GET_CFI = 0x50
|
||||||
PR_GET_CHILD_SUBREAPER = 0x25
|
PR_GET_CHILD_SUBREAPER = 0x25
|
||||||
PR_GET_DUMPABLE = 0x3
|
PR_GET_DUMPABLE = 0x3
|
||||||
PR_GET_ENDIAN = 0x13
|
PR_GET_ENDIAN = 0x13
|
||||||
@@ -2834,6 +2878,7 @@ const (
|
|||||||
PR_MDWE_REFUSE_EXEC_GAIN = 0x1
|
PR_MDWE_REFUSE_EXEC_GAIN = 0x1
|
||||||
PR_MPX_DISABLE_MANAGEMENT = 0x2c
|
PR_MPX_DISABLE_MANAGEMENT = 0x2c
|
||||||
PR_MPX_ENABLE_MANAGEMENT = 0x2b
|
PR_MPX_ENABLE_MANAGEMENT = 0x2b
|
||||||
|
PR_MTE_STORE_ONLY = 0x80000
|
||||||
PR_MTE_TAG_MASK = 0x7fff8
|
PR_MTE_TAG_MASK = 0x7fff8
|
||||||
PR_MTE_TAG_SHIFT = 0x3
|
PR_MTE_TAG_SHIFT = 0x3
|
||||||
PR_MTE_TCF_ASYNC = 0x4
|
PR_MTE_TCF_ASYNC = 0x4
|
||||||
@@ -2877,6 +2922,10 @@ const (
|
|||||||
PR_RISCV_V_VSTATE_CTRL_NEXT_MASK = 0xc
|
PR_RISCV_V_VSTATE_CTRL_NEXT_MASK = 0xc
|
||||||
PR_RISCV_V_VSTATE_CTRL_OFF = 0x1
|
PR_RISCV_V_VSTATE_CTRL_OFF = 0x1
|
||||||
PR_RISCV_V_VSTATE_CTRL_ON = 0x2
|
PR_RISCV_V_VSTATE_CTRL_ON = 0x2
|
||||||
|
PR_RSEQ_SLICE_EXTENSION = 0x4f
|
||||||
|
PR_RSEQ_SLICE_EXTENSION_GET = 0x1
|
||||||
|
PR_RSEQ_SLICE_EXTENSION_SET = 0x2
|
||||||
|
PR_RSEQ_SLICE_EXT_ENABLE = 0x1
|
||||||
PR_SCHED_CORE = 0x3e
|
PR_SCHED_CORE = 0x3e
|
||||||
PR_SCHED_CORE_CREATE = 0x1
|
PR_SCHED_CORE_CREATE = 0x1
|
||||||
PR_SCHED_CORE_GET = 0x0
|
PR_SCHED_CORE_GET = 0x0
|
||||||
@@ -2886,6 +2935,7 @@ const (
|
|||||||
PR_SCHED_CORE_SCOPE_THREAD_GROUP = 0x1
|
PR_SCHED_CORE_SCOPE_THREAD_GROUP = 0x1
|
||||||
PR_SCHED_CORE_SHARE_FROM = 0x3
|
PR_SCHED_CORE_SHARE_FROM = 0x3
|
||||||
PR_SCHED_CORE_SHARE_TO = 0x2
|
PR_SCHED_CORE_SHARE_TO = 0x2
|
||||||
|
PR_SET_CFI = 0x51
|
||||||
PR_SET_CHILD_SUBREAPER = 0x24
|
PR_SET_CHILD_SUBREAPER = 0x24
|
||||||
PR_SET_DUMPABLE = 0x4
|
PR_SET_DUMPABLE = 0x4
|
||||||
PR_SET_ENDIAN = 0x14
|
PR_SET_ENDIAN = 0x14
|
||||||
@@ -2951,11 +3001,14 @@ const (
|
|||||||
PR_SVE_SET_VL_ONEXEC = 0x40000
|
PR_SVE_SET_VL_ONEXEC = 0x40000
|
||||||
PR_SVE_VL_INHERIT = 0x20000
|
PR_SVE_VL_INHERIT = 0x20000
|
||||||
PR_SVE_VL_LEN_MASK = 0xffff
|
PR_SVE_VL_LEN_MASK = 0xffff
|
||||||
|
PR_SYS_DISPATCH_EXCLUSIVE_ON = 0x1
|
||||||
|
PR_SYS_DISPATCH_INCLUSIVE_ON = 0x2
|
||||||
PR_SYS_DISPATCH_OFF = 0x0
|
PR_SYS_DISPATCH_OFF = 0x0
|
||||||
PR_SYS_DISPATCH_ON = 0x1
|
PR_SYS_DISPATCH_ON = 0x1
|
||||||
PR_TAGGED_ADDR_ENABLE = 0x1
|
PR_TAGGED_ADDR_ENABLE = 0x1
|
||||||
PR_TASK_PERF_EVENTS_DISABLE = 0x1f
|
PR_TASK_PERF_EVENTS_DISABLE = 0x1f
|
||||||
PR_TASK_PERF_EVENTS_ENABLE = 0x20
|
PR_TASK_PERF_EVENTS_ENABLE = 0x20
|
||||||
|
PR_THP_DISABLE_EXCEPT_ADVISED = 0x2
|
||||||
PR_TIMER_CREATE_RESTORE_IDS = 0x4d
|
PR_TIMER_CREATE_RESTORE_IDS = 0x4d
|
||||||
PR_TIMER_CREATE_RESTORE_IDS_GET = 0x2
|
PR_TIMER_CREATE_RESTORE_IDS_GET = 0x2
|
||||||
PR_TIMER_CREATE_RESTORE_IDS_OFF = 0x0
|
PR_TIMER_CREATE_RESTORE_IDS_OFF = 0x0
|
||||||
@@ -2987,8 +3040,10 @@ const (
|
|||||||
PTP_STRICT_FLAGS = 0x8
|
PTP_STRICT_FLAGS = 0x8
|
||||||
PTP_SYS_OFFSET_EXTENDED = 0xc4c03d09
|
PTP_SYS_OFFSET_EXTENDED = 0xc4c03d09
|
||||||
PTP_SYS_OFFSET_EXTENDED2 = 0xc4c03d12
|
PTP_SYS_OFFSET_EXTENDED2 = 0xc4c03d12
|
||||||
|
PTP_SYS_OFFSET_EXTENDED_CYCLES = 0xc4c03d16
|
||||||
PTP_SYS_OFFSET_PRECISE = 0xc0403d08
|
PTP_SYS_OFFSET_PRECISE = 0xc0403d08
|
||||||
PTP_SYS_OFFSET_PRECISE2 = 0xc0403d11
|
PTP_SYS_OFFSET_PRECISE2 = 0xc0403d11
|
||||||
|
PTP_SYS_OFFSET_PRECISE_CYCLES = 0xc0403d15
|
||||||
PTRACE_ATTACH = 0x10
|
PTRACE_ATTACH = 0x10
|
||||||
PTRACE_CONT = 0x7
|
PTRACE_CONT = 0x7
|
||||||
PTRACE_DETACH = 0x11
|
PTRACE_DETACH = 0x11
|
||||||
@@ -3330,8 +3385,9 @@ const (
|
|||||||
RWF_DSYNC = 0x2
|
RWF_DSYNC = 0x2
|
||||||
RWF_HIPRI = 0x1
|
RWF_HIPRI = 0x1
|
||||||
RWF_NOAPPEND = 0x20
|
RWF_NOAPPEND = 0x20
|
||||||
|
RWF_NOSIGNAL = 0x100
|
||||||
RWF_NOWAIT = 0x8
|
RWF_NOWAIT = 0x8
|
||||||
RWF_SUPPORTED = 0xff
|
RWF_SUPPORTED = 0x1ff
|
||||||
RWF_SYNC = 0x4
|
RWF_SYNC = 0x4
|
||||||
RWF_WRITE_LIFE_NOT_SET = 0x0
|
RWF_WRITE_LIFE_NOT_SET = 0x0
|
||||||
SCHED_BATCH = 0x3
|
SCHED_BATCH = 0x3
|
||||||
@@ -3714,7 +3770,7 @@ const (
|
|||||||
TASKSTATS_GENL_NAME = "TASKSTATS"
|
TASKSTATS_GENL_NAME = "TASKSTATS"
|
||||||
TASKSTATS_GENL_VERSION = 0x1
|
TASKSTATS_GENL_VERSION = 0x1
|
||||||
TASKSTATS_TYPE_MAX = 0x6
|
TASKSTATS_TYPE_MAX = 0x6
|
||||||
TASKSTATS_VERSION = 0x10
|
TASKSTATS_VERSION = 0x11
|
||||||
TCIFLUSH = 0x0
|
TCIFLUSH = 0x0
|
||||||
TCIOFF = 0x2
|
TCIOFF = 0x2
|
||||||
TCIOFLUSH = 0x2
|
TCIOFLUSH = 0x2
|
||||||
@@ -4052,6 +4108,7 @@ const (
|
|||||||
XDP_FLAGS_REPLACE = 0x10
|
XDP_FLAGS_REPLACE = 0x10
|
||||||
XDP_FLAGS_SKB_MODE = 0x2
|
XDP_FLAGS_SKB_MODE = 0x2
|
||||||
XDP_FLAGS_UPDATE_IF_NOEXIST = 0x1
|
XDP_FLAGS_UPDATE_IF_NOEXIST = 0x1
|
||||||
|
XDP_MAX_TX_SKB_BUDGET = 0x9
|
||||||
XDP_MMAP_OFFSETS = 0x1
|
XDP_MMAP_OFFSETS = 0x1
|
||||||
XDP_OPTIONS = 0x8
|
XDP_OPTIONS = 0x8
|
||||||
XDP_OPTIONS_ZEROCOPY = 0x1
|
XDP_OPTIONS_ZEROCOPY = 0x1
|
||||||
|
|||||||
+6
-1
@@ -159,6 +159,7 @@ const (
|
|||||||
NFDBITS = 0x20
|
NFDBITS = 0x20
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x8008b70d
|
||||||
NS_GET_MNTNS_ID = 0x8008b705
|
NS_GET_MNTNS_ID = 0x8008b705
|
||||||
NS_GET_NSTYPE = 0xb703
|
NS_GET_NSTYPE = 0xb703
|
||||||
NS_GET_OWNER_UID = 0xb704
|
NS_GET_OWNER_UID = 0xb704
|
||||||
@@ -305,6 +306,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x4028700f
|
RTC_WKALM_SET = 0x4028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -352,6 +354,7 @@ const (
|
|||||||
SO_ERROR = 0x4
|
SO_ERROR = 0x4
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x9
|
SO_KEEPALIVE = 0x9
|
||||||
SO_LINGER = 0xd
|
SO_LINGER = 0xd
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -596,6 +599,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x59)
|
EDESTADDRREQ = syscall.Errno(0x59)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x7a)
|
EDQUOT = syscall.Errno(0x7a)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4a)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x75)
|
||||||
EHOSTDOWN = syscall.Errno(0x70)
|
EHOSTDOWN = syscall.Errno(0x70)
|
||||||
EHOSTUNREACH = syscall.Errno(0x71)
|
EHOSTUNREACH = syscall.Errno(0x71)
|
||||||
EHWPOISON = syscall.Errno(0x85)
|
EHWPOISON = syscall.Errno(0x85)
|
||||||
@@ -819,7 +824,7 @@ var errorList = [...]struct {
|
|||||||
{114, "EALREADY", "operation already in progress"},
|
{114, "EALREADY", "operation already in progress"},
|
||||||
{115, "EINPROGRESS", "operation now in progress"},
|
{115, "EINPROGRESS", "operation now in progress"},
|
||||||
{116, "ESTALE", "stale file handle"},
|
{116, "ESTALE", "stale file handle"},
|
||||||
{117, "EUCLEAN", "structure needs cleaning"},
|
{117, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{118, "ENOTNAM", "not a XENIX named type file"},
|
{118, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{119, "ENAVAIL", "no XENIX semaphores available"},
|
{119, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{120, "EISNAM", "is a named type file"},
|
{120, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -159,6 +159,7 @@ const (
|
|||||||
NFDBITS = 0x40
|
NFDBITS = 0x40
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x8008b70d
|
||||||
NS_GET_MNTNS_ID = 0x8008b705
|
NS_GET_MNTNS_ID = 0x8008b705
|
||||||
NS_GET_NSTYPE = 0xb703
|
NS_GET_NSTYPE = 0xb703
|
||||||
NS_GET_OWNER_UID = 0xb704
|
NS_GET_OWNER_UID = 0xb704
|
||||||
@@ -306,6 +307,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x4028700f
|
RTC_WKALM_SET = 0x4028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -353,6 +355,7 @@ const (
|
|||||||
SO_ERROR = 0x4
|
SO_ERROR = 0x4
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x9
|
SO_KEEPALIVE = 0x9
|
||||||
SO_LINGER = 0xd
|
SO_LINGER = 0xd
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -596,6 +599,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x59)
|
EDESTADDRREQ = syscall.Errno(0x59)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x7a)
|
EDQUOT = syscall.Errno(0x7a)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4a)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x75)
|
||||||
EHOSTDOWN = syscall.Errno(0x70)
|
EHOSTDOWN = syscall.Errno(0x70)
|
||||||
EHOSTUNREACH = syscall.Errno(0x71)
|
EHOSTUNREACH = syscall.Errno(0x71)
|
||||||
EHWPOISON = syscall.Errno(0x85)
|
EHWPOISON = syscall.Errno(0x85)
|
||||||
@@ -819,7 +824,7 @@ var errorList = [...]struct {
|
|||||||
{114, "EALREADY", "operation already in progress"},
|
{114, "EALREADY", "operation already in progress"},
|
||||||
{115, "EINPROGRESS", "operation now in progress"},
|
{115, "EINPROGRESS", "operation now in progress"},
|
||||||
{116, "ESTALE", "stale file handle"},
|
{116, "ESTALE", "stale file handle"},
|
||||||
{117, "EUCLEAN", "structure needs cleaning"},
|
{117, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{118, "ENOTNAM", "not a XENIX named type file"},
|
{118, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{119, "ENAVAIL", "no XENIX semaphores available"},
|
{119, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{120, "EISNAM", "is a named type file"},
|
{120, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -156,6 +156,7 @@ const (
|
|||||||
NFDBITS = 0x20
|
NFDBITS = 0x20
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x8008b70d
|
||||||
NS_GET_MNTNS_ID = 0x8008b705
|
NS_GET_MNTNS_ID = 0x8008b705
|
||||||
NS_GET_NSTYPE = 0xb703
|
NS_GET_NSTYPE = 0xb703
|
||||||
NS_GET_OWNER_UID = 0xb704
|
NS_GET_OWNER_UID = 0xb704
|
||||||
@@ -311,6 +312,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x4028700f
|
RTC_WKALM_SET = 0x4028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -358,6 +360,7 @@ const (
|
|||||||
SO_ERROR = 0x4
|
SO_ERROR = 0x4
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x9
|
SO_KEEPALIVE = 0x9
|
||||||
SO_LINGER = 0xd
|
SO_LINGER = 0xd
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -601,6 +604,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x59)
|
EDESTADDRREQ = syscall.Errno(0x59)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x7a)
|
EDQUOT = syscall.Errno(0x7a)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4a)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x75)
|
||||||
EHOSTDOWN = syscall.Errno(0x70)
|
EHOSTDOWN = syscall.Errno(0x70)
|
||||||
EHOSTUNREACH = syscall.Errno(0x71)
|
EHOSTUNREACH = syscall.Errno(0x71)
|
||||||
EHWPOISON = syscall.Errno(0x85)
|
EHWPOISON = syscall.Errno(0x85)
|
||||||
@@ -824,7 +829,7 @@ var errorList = [...]struct {
|
|||||||
{114, "EALREADY", "operation already in progress"},
|
{114, "EALREADY", "operation already in progress"},
|
||||||
{115, "EINPROGRESS", "operation now in progress"},
|
{115, "EINPROGRESS", "operation now in progress"},
|
||||||
{116, "ESTALE", "stale file handle"},
|
{116, "ESTALE", "stale file handle"},
|
||||||
{117, "EUCLEAN", "structure needs cleaning"},
|
{117, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{118, "ENOTNAM", "not a XENIX named type file"},
|
{118, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{119, "ENAVAIL", "no XENIX semaphores available"},
|
{119, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{120, "EISNAM", "is a named type file"},
|
{120, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -161,6 +161,7 @@ const (
|
|||||||
NFDBITS = 0x40
|
NFDBITS = 0x40
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x8008b70d
|
||||||
NS_GET_MNTNS_ID = 0x8008b705
|
NS_GET_MNTNS_ID = 0x8008b705
|
||||||
NS_GET_NSTYPE = 0xb703
|
NS_GET_NSTYPE = 0xb703
|
||||||
NS_GET_OWNER_UID = 0xb704
|
NS_GET_OWNER_UID = 0xb704
|
||||||
@@ -304,6 +305,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x4028700f
|
RTC_WKALM_SET = 0x4028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -351,6 +353,7 @@ const (
|
|||||||
SO_ERROR = 0x4
|
SO_ERROR = 0x4
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x9
|
SO_KEEPALIVE = 0x9
|
||||||
SO_LINGER = 0xd
|
SO_LINGER = 0xd
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -598,6 +601,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x59)
|
EDESTADDRREQ = syscall.Errno(0x59)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x7a)
|
EDQUOT = syscall.Errno(0x7a)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4a)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x75)
|
||||||
EHOSTDOWN = syscall.Errno(0x70)
|
EHOSTDOWN = syscall.Errno(0x70)
|
||||||
EHOSTUNREACH = syscall.Errno(0x71)
|
EHOSTUNREACH = syscall.Errno(0x71)
|
||||||
EHWPOISON = syscall.Errno(0x85)
|
EHWPOISON = syscall.Errno(0x85)
|
||||||
@@ -821,7 +826,7 @@ var errorList = [...]struct {
|
|||||||
{114, "EALREADY", "operation already in progress"},
|
{114, "EALREADY", "operation already in progress"},
|
||||||
{115, "EINPROGRESS", "operation now in progress"},
|
{115, "EINPROGRESS", "operation now in progress"},
|
||||||
{116, "ESTALE", "stale file handle"},
|
{116, "ESTALE", "stale file handle"},
|
||||||
{117, "EUCLEAN", "structure needs cleaning"},
|
{117, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{118, "ENOTNAM", "not a XENIX named type file"},
|
{118, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{119, "ENAVAIL", "no XENIX semaphores available"},
|
{119, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{120, "EISNAM", "is a named type file"},
|
{120, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -160,6 +160,7 @@ const (
|
|||||||
NFDBITS = 0x40
|
NFDBITS = 0x40
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x8008b70d
|
||||||
NS_GET_MNTNS_ID = 0x8008b705
|
NS_GET_MNTNS_ID = 0x8008b705
|
||||||
NS_GET_NSTYPE = 0xb703
|
NS_GET_NSTYPE = 0xb703
|
||||||
NS_GET_OWNER_UID = 0xb704
|
NS_GET_OWNER_UID = 0xb704
|
||||||
@@ -298,6 +299,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x4028700f
|
RTC_WKALM_SET = 0x4028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -345,6 +347,7 @@ const (
|
|||||||
SO_ERROR = 0x4
|
SO_ERROR = 0x4
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x9
|
SO_KEEPALIVE = 0x9
|
||||||
SO_LINGER = 0xd
|
SO_LINGER = 0xd
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -588,6 +591,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x59)
|
EDESTADDRREQ = syscall.Errno(0x59)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x7a)
|
EDQUOT = syscall.Errno(0x7a)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4a)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x75)
|
||||||
EHOSTDOWN = syscall.Errno(0x70)
|
EHOSTDOWN = syscall.Errno(0x70)
|
||||||
EHOSTUNREACH = syscall.Errno(0x71)
|
EHOSTUNREACH = syscall.Errno(0x71)
|
||||||
EHWPOISON = syscall.Errno(0x85)
|
EHWPOISON = syscall.Errno(0x85)
|
||||||
@@ -811,7 +816,7 @@ var errorList = [...]struct {
|
|||||||
{114, "EALREADY", "operation already in progress"},
|
{114, "EALREADY", "operation already in progress"},
|
||||||
{115, "EINPROGRESS", "operation now in progress"},
|
{115, "EINPROGRESS", "operation now in progress"},
|
||||||
{116, "ESTALE", "stale file handle"},
|
{116, "ESTALE", "stale file handle"},
|
||||||
{117, "EUCLEAN", "structure needs cleaning"},
|
{117, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{118, "ENOTNAM", "not a XENIX named type file"},
|
{118, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{119, "ENAVAIL", "no XENIX semaphores available"},
|
{119, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{120, "EISNAM", "is a named type file"},
|
{120, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -156,6 +156,7 @@ const (
|
|||||||
NFDBITS = 0x20
|
NFDBITS = 0x20
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x4008b70d
|
||||||
NS_GET_MNTNS_ID = 0x4008b705
|
NS_GET_MNTNS_ID = 0x4008b705
|
||||||
NS_GET_NSTYPE = 0x2000b703
|
NS_GET_NSTYPE = 0x2000b703
|
||||||
NS_GET_OWNER_UID = 0x2000b704
|
NS_GET_OWNER_UID = 0x2000b704
|
||||||
@@ -304,6 +305,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x8028700f
|
RTC_WKALM_SET = 0x8028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -351,6 +353,7 @@ const (
|
|||||||
SO_ERROR = 0x1007
|
SO_ERROR = 0x1007
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x8
|
SO_KEEPALIVE = 0x8
|
||||||
SO_LINGER = 0x80
|
SO_LINGER = 0x80
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -597,6 +600,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x60)
|
EDESTADDRREQ = syscall.Errno(0x60)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x46d)
|
EDQUOT = syscall.Errno(0x46d)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4d)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x87)
|
||||||
EHOSTDOWN = syscall.Errno(0x93)
|
EHOSTDOWN = syscall.Errno(0x93)
|
||||||
EHOSTUNREACH = syscall.Errno(0x94)
|
EHOSTUNREACH = syscall.Errno(0x94)
|
||||||
EHWPOISON = syscall.Errno(0xa8)
|
EHWPOISON = syscall.Errno(0xa8)
|
||||||
@@ -814,7 +819,7 @@ var errorList = [...]struct {
|
|||||||
{132, "ENOBUFS", "no buffer space available"},
|
{132, "ENOBUFS", "no buffer space available"},
|
||||||
{133, "EISCONN", "transport endpoint is already connected"},
|
{133, "EISCONN", "transport endpoint is already connected"},
|
||||||
{134, "ENOTCONN", "transport endpoint is not connected"},
|
{134, "ENOTCONN", "transport endpoint is not connected"},
|
||||||
{135, "EUCLEAN", "structure needs cleaning"},
|
{135, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{137, "ENOTNAM", "not a XENIX named type file"},
|
{137, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{138, "ENAVAIL", "no XENIX semaphores available"},
|
{138, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{139, "EISNAM", "is a named type file"},
|
{139, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -156,6 +156,7 @@ const (
|
|||||||
NFDBITS = 0x40
|
NFDBITS = 0x40
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x4008b70d
|
||||||
NS_GET_MNTNS_ID = 0x4008b705
|
NS_GET_MNTNS_ID = 0x4008b705
|
||||||
NS_GET_NSTYPE = 0x2000b703
|
NS_GET_NSTYPE = 0x2000b703
|
||||||
NS_GET_OWNER_UID = 0x2000b704
|
NS_GET_OWNER_UID = 0x2000b704
|
||||||
@@ -304,6 +305,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x8028700f
|
RTC_WKALM_SET = 0x8028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -351,6 +353,7 @@ const (
|
|||||||
SO_ERROR = 0x1007
|
SO_ERROR = 0x1007
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x8
|
SO_KEEPALIVE = 0x8
|
||||||
SO_LINGER = 0x80
|
SO_LINGER = 0x80
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -597,6 +600,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x60)
|
EDESTADDRREQ = syscall.Errno(0x60)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x46d)
|
EDQUOT = syscall.Errno(0x46d)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4d)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x87)
|
||||||
EHOSTDOWN = syscall.Errno(0x93)
|
EHOSTDOWN = syscall.Errno(0x93)
|
||||||
EHOSTUNREACH = syscall.Errno(0x94)
|
EHOSTUNREACH = syscall.Errno(0x94)
|
||||||
EHWPOISON = syscall.Errno(0xa8)
|
EHWPOISON = syscall.Errno(0xa8)
|
||||||
@@ -814,7 +819,7 @@ var errorList = [...]struct {
|
|||||||
{132, "ENOBUFS", "no buffer space available"},
|
{132, "ENOBUFS", "no buffer space available"},
|
||||||
{133, "EISCONN", "transport endpoint is already connected"},
|
{133, "EISCONN", "transport endpoint is already connected"},
|
||||||
{134, "ENOTCONN", "transport endpoint is not connected"},
|
{134, "ENOTCONN", "transport endpoint is not connected"},
|
||||||
{135, "EUCLEAN", "structure needs cleaning"},
|
{135, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{137, "ENOTNAM", "not a XENIX named type file"},
|
{137, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{138, "ENAVAIL", "no XENIX semaphores available"},
|
{138, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{139, "EISNAM", "is a named type file"},
|
{139, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -156,6 +156,7 @@ const (
|
|||||||
NFDBITS = 0x40
|
NFDBITS = 0x40
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x4008b70d
|
||||||
NS_GET_MNTNS_ID = 0x4008b705
|
NS_GET_MNTNS_ID = 0x4008b705
|
||||||
NS_GET_NSTYPE = 0x2000b703
|
NS_GET_NSTYPE = 0x2000b703
|
||||||
NS_GET_OWNER_UID = 0x2000b704
|
NS_GET_OWNER_UID = 0x2000b704
|
||||||
@@ -304,6 +305,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x8028700f
|
RTC_WKALM_SET = 0x8028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -351,6 +353,7 @@ const (
|
|||||||
SO_ERROR = 0x1007
|
SO_ERROR = 0x1007
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x8
|
SO_KEEPALIVE = 0x8
|
||||||
SO_LINGER = 0x80
|
SO_LINGER = 0x80
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -597,6 +600,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x60)
|
EDESTADDRREQ = syscall.Errno(0x60)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x46d)
|
EDQUOT = syscall.Errno(0x46d)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4d)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x87)
|
||||||
EHOSTDOWN = syscall.Errno(0x93)
|
EHOSTDOWN = syscall.Errno(0x93)
|
||||||
EHOSTUNREACH = syscall.Errno(0x94)
|
EHOSTUNREACH = syscall.Errno(0x94)
|
||||||
EHWPOISON = syscall.Errno(0xa8)
|
EHWPOISON = syscall.Errno(0xa8)
|
||||||
@@ -814,7 +819,7 @@ var errorList = [...]struct {
|
|||||||
{132, "ENOBUFS", "no buffer space available"},
|
{132, "ENOBUFS", "no buffer space available"},
|
||||||
{133, "EISCONN", "transport endpoint is already connected"},
|
{133, "EISCONN", "transport endpoint is already connected"},
|
||||||
{134, "ENOTCONN", "transport endpoint is not connected"},
|
{134, "ENOTCONN", "transport endpoint is not connected"},
|
||||||
{135, "EUCLEAN", "structure needs cleaning"},
|
{135, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{137, "ENOTNAM", "not a XENIX named type file"},
|
{137, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{138, "ENAVAIL", "no XENIX semaphores available"},
|
{138, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{139, "EISNAM", "is a named type file"},
|
{139, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
+6
-1
@@ -156,6 +156,7 @@ const (
|
|||||||
NFDBITS = 0x20
|
NFDBITS = 0x20
|
||||||
NLDLY = 0x100
|
NLDLY = 0x100
|
||||||
NOFLSH = 0x80
|
NOFLSH = 0x80
|
||||||
|
NS_GET_ID = 0x4008b70d
|
||||||
NS_GET_MNTNS_ID = 0x4008b705
|
NS_GET_MNTNS_ID = 0x4008b705
|
||||||
NS_GET_NSTYPE = 0x2000b703
|
NS_GET_NSTYPE = 0x2000b703
|
||||||
NS_GET_OWNER_UID = 0x2000b704
|
NS_GET_OWNER_UID = 0x2000b704
|
||||||
@@ -304,6 +305,7 @@ const (
|
|||||||
RTC_WKALM_SET = 0x8028700f
|
RTC_WKALM_SET = 0x8028700f
|
||||||
SCM_DEVMEM_DMABUF = 0x4f
|
SCM_DEVMEM_DMABUF = 0x4f
|
||||||
SCM_DEVMEM_LINEAR = 0x4e
|
SCM_DEVMEM_LINEAR = 0x4e
|
||||||
|
SCM_INQ = 0x54
|
||||||
SCM_TIMESTAMPING = 0x25
|
SCM_TIMESTAMPING = 0x25
|
||||||
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
SCM_TIMESTAMPING_OPT_STATS = 0x36
|
||||||
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
SCM_TIMESTAMPING_PKTINFO = 0x3a
|
||||||
@@ -351,6 +353,7 @@ const (
|
|||||||
SO_ERROR = 0x1007
|
SO_ERROR = 0x1007
|
||||||
SO_INCOMING_CPU = 0x31
|
SO_INCOMING_CPU = 0x31
|
||||||
SO_INCOMING_NAPI_ID = 0x38
|
SO_INCOMING_NAPI_ID = 0x38
|
||||||
|
SO_INQ = 0x54
|
||||||
SO_KEEPALIVE = 0x8
|
SO_KEEPALIVE = 0x8
|
||||||
SO_LINGER = 0x80
|
SO_LINGER = 0x80
|
||||||
SO_LOCK_FILTER = 0x2c
|
SO_LOCK_FILTER = 0x2c
|
||||||
@@ -597,6 +600,8 @@ const (
|
|||||||
EDESTADDRREQ = syscall.Errno(0x60)
|
EDESTADDRREQ = syscall.Errno(0x60)
|
||||||
EDOTDOT = syscall.Errno(0x49)
|
EDOTDOT = syscall.Errno(0x49)
|
||||||
EDQUOT = syscall.Errno(0x46d)
|
EDQUOT = syscall.Errno(0x46d)
|
||||||
|
EFSBADCRC = syscall.Errno(0x4d)
|
||||||
|
EFSCORRUPTED = syscall.Errno(0x87)
|
||||||
EHOSTDOWN = syscall.Errno(0x93)
|
EHOSTDOWN = syscall.Errno(0x93)
|
||||||
EHOSTUNREACH = syscall.Errno(0x94)
|
EHOSTUNREACH = syscall.Errno(0x94)
|
||||||
EHWPOISON = syscall.Errno(0xa8)
|
EHWPOISON = syscall.Errno(0xa8)
|
||||||
@@ -814,7 +819,7 @@ var errorList = [...]struct {
|
|||||||
{132, "ENOBUFS", "no buffer space available"},
|
{132, "ENOBUFS", "no buffer space available"},
|
||||||
{133, "EISCONN", "transport endpoint is already connected"},
|
{133, "EISCONN", "transport endpoint is already connected"},
|
||||||
{134, "ENOTCONN", "transport endpoint is not connected"},
|
{134, "ENOTCONN", "transport endpoint is not connected"},
|
||||||
{135, "EUCLEAN", "structure needs cleaning"},
|
{135, "EFSCORRUPTED", "structure needs cleaning"},
|
||||||
{137, "ENOTNAM", "not a XENIX named type file"},
|
{137, "ENOTNAM", "not a XENIX named type file"},
|
||||||
{138, "ENAVAIL", "no XENIX semaphores available"},
|
{138, "ENAVAIL", "no XENIX semaphores available"},
|
||||||
{139, "EISNAM", "is a named type file"},
|
{139, "EISNAM", "is a named type file"},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user