Compare commits

...

15 Commits

52 changed files with 1924 additions and 427 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ WORKDIR /app
RUN CGO_ENABLED=0 make build
FROM alpine:3.22
FROM alpine:3.23
RUN apk add --no-cache \
ca-certificates \
+11 -11
View File
@@ -3,7 +3,7 @@ module coopcloud.tech/abra
go 1.26.0
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
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/charmbracelet/bubbletea v1.3.10
@@ -13,14 +13,14 @@ require (
github.com/docker/cli v28.4.0+incompatible
github.com/docker/docker v28.5.2+incompatible
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.0
github.com/google/go-cmp v0.7.0
github.com/leonelquinteros/gotext v1.7.2
github.com/moby/sys/signal v0.7.1
github.com/moby/term v0.5.2
github.com/pkg/errors v0.9.1
github.com/schollz/progressbar/v3 v3.19.0
golang.org/x/term v0.41.0
golang.org/x/term v0.43.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2
)
@@ -60,7 +60,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // 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/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-logr/logr v1.4.3 // 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/runc v1.1.13 // 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/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
@@ -124,10 +124,10 @@ require (
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/text v0.36.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/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
@@ -155,9 +155,9 @@ require (
github.com/stretchr/testify v1.11.1
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sys v0.42.0
golang.org/x/sys v0.44.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/spf13/cobra => github.com/decentral1se/cobra v1.10.2-i18n
replace github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2
+22 -22
View File
@@ -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.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
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-20250818180036-0ec1b205b5ca/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977 h1:J7I0HFjwVAj/kkX6lwSTHmlXDRjQRsdIFNUUqu55ADY=
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/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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-i18n/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/decentral1se/cobra v1.10.2 h1:MZ8Ifi/jRels9sZrpSccDbUlK++3b2HlBODfv0Bh6x0=
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/go.mod h1:530V+lNoPhKtkrX2fIVsIfLhkl47CuiOM7HRgi7C+SU=
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/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/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
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 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
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/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.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
github.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc=
github.com/go-git/go-git/v5 v5.19.0/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/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=
@@ -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/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/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
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.1-0.20171018195549-f15c970de5b7/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-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.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-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-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
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 h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
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-20190226205417-e64efc72b421/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-20220722155257-8c9f86f7a55f/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.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.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-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.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
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.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.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.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+11 -4
View File
@@ -23,11 +23,18 @@ buildGo126Module rec {
];
env.CGO_ENABLED = 0;
ldflags = [
"-s -w -X 'main.Commit=${rev}' -X 'main.Version=${version}'"
];
buildPhase = ''
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 = ''
export ABRA_DIR="$out"
$out/bin/abra autocomplete bash >abra.bash
+1 -1
View File
@@ -3,7 +3,7 @@ version: "3.8"
services:
app:
image: nginx:1.21.0
image: nginx:1.31.0
secrets:
- test_pass_one
- test_pass_two
+1 -1
View File
@@ -3,7 +3,7 @@ version: "3.8"
services:
app:
image: nginx:1.29.0
image: nginx:1.31.0
networks:
- proxy
deploy:
+3 -3
View File
@@ -3,16 +3,16 @@ kind: pipeline
name: coopcloud.tech/tagcmp
steps:
- name: gofmt
image: golang:1.21
image: golang:1.26
commands:
- test -z "$(gofmt -l .)"
- name: go build
image: golang:1.21
image: golang:1.26
commands:
- go build -v .
- name: go test
image: golang:1.21
image: golang:1.26
commands:
- go test . -cover
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
+4
View File
@@ -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.
## 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
```go
+198 -10
View File
@@ -3,19 +3,25 @@ package chroot
import (
"errors"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/helper/polyfill"
)
// 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 {
underlying billy.Filesystem
base string
}
const maxFollowedSymlinks = 8 // Aligns with POSIX_SYMLOOP_MAX
// New creates a new filesystem wrapping up the given 'fs'.
// The created filesystem has its base in the given ChrootHelperectory of the
// underlying filesystem.
@@ -34,15 +40,184 @@ func (fs *ChrootHelper) underlyingPath(filename string) (string, error) {
return fs.Join(fs.Root(), filename), nil
}
func isCrossBoundaries(path string) bool {
path = filepath.ToSlash(path)
path = filepath.Clean(path)
func (fs *ChrootHelper) followedPath(filename string, followFinal bool, op string) (string, error) {
fullpath, err := fs.underlyingPath(filename)
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) {
fullpath, err := fs.underlyingPath(filename)
fullpath, err := fs.followedPath(filename, true, "create")
if err != nil {
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) {
fullpath, err := fs.underlyingPath(filename)
fullpath, err := fs.followedPath(filename, true, "open")
if err != nil {
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) {
fullpath, err := fs.underlyingPath(filename)
fullpath, err := fs.followedPath(filename, !isCreateExclusive(flag), "open")
if err != nil {
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) {
fullpath, err := fs.underlyingPath(filename)
fullpath, err := fs.followedPath(filename, true, "stat")
if err != nil {
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 {
@@ -135,7 +314,7 @@ func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, 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 {
return nil, err
}
@@ -241,6 +420,11 @@ type file struct {
name string
}
type fileInfo struct {
os.FileInfo
name string
}
func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File {
filename = fs.Join(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 {
return f.name
}
func (fi fileInfo) Name() string {
return fi.name
}
+5
View File
@@ -24,6 +24,9 @@ var Default = &ChrootOS{}
// New returns a new OS filesystem.
// By default paths are deduplicated, but still enforced
// 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 {
o := &options{
deduplicatePath: true,
@@ -47,6 +50,8 @@ func WithBoundOS() Option {
}
// WithChrootOS returns the option of using a Chroot filesystem OS.
//
// Deprecated: use WithBoundOS instead.
func WithChrootOS() Option {
return func(o *options) {
o.Type = ChrootOSFS
+85 -15
View File
@@ -20,6 +20,7 @@
package osfs
import (
"errors"
"fmt"
"os"
"path/filepath"
@@ -29,6 +30,31 @@ import (
"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
// a base dir.
// 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) {
filename = fs.expandDot(filename)
fn, err := fs.abs(filename)
if err != nil {
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) {
path = fs.expandDot(path)
dir, err := fs.abs(path)
if err != nil {
return nil, err
@@ -71,6 +99,12 @@ func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, 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)
if err != nil {
return err
@@ -89,6 +123,7 @@ func (fs *BoundOS) Rename(from, to string) error {
}
func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error {
path = fs.expandDot(path)
dir, err := fs.abs(path)
if err != nil {
return err
@@ -101,6 +136,7 @@ func (fs *BoundOS) Open(filename string) (billy.File, error) {
}
func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) {
filename = fs.expandDot(filename)
filename, err := fs.abs(filename)
if err != nil {
return nil, err
@@ -109,6 +145,11 @@ func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) {
}
func (fs *BoundOS) Remove(filename string) error {
if fs.isBaseDir(filename) {
return ErrBaseDirCannotBeRemoved
}
filename = fs.expandDot(filename)
fn, err := fs.abs(filename)
if err != nil {
return err
@@ -122,6 +163,7 @@ func (fs *BoundOS) Remove(filename string) error {
func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) {
if dir != "" {
var err error
dir = fs.expandDot(dir)
dir, err = fs.abs(dir)
if err != nil {
return nil, err
@@ -144,6 +186,11 @@ func (fs *BoundOS) Join(elem ...string) string {
}
func (fs *BoundOS) RemoveAll(path string) error {
if fs.isBaseDir(path) {
return ErrBaseDirCannotBeRemoved
}
path = fs.expandDot(path)
dir, err := fs.abs(path)
if err != nil {
return err
@@ -152,6 +199,7 @@ func (fs *BoundOS) RemoveAll(path string) error {
}
func (fs *BoundOS) Symlink(target, link string) error {
link = fs.expandDot(link)
ln, err := fs.abs(link)
if err != nil {
return err
@@ -164,6 +212,7 @@ func (fs *BoundOS) Symlink(target, link string) error {
}
func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) {
filename = fs.expandDot(filename)
filename = filepath.Clean(filename)
if !filepath.IsAbs(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) {
link = fs.expandDot(link)
if !filepath.IsAbs(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 {
path = fs.expandDot(path)
abspath, err := fs.abs(path)
if err != nil {
return err
@@ -199,7 +250,7 @@ func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) {
if err != nil {
return nil, err
}
return New(joined), nil
return New(joined, WithBoundOS()), nil
}
// Root returns the current base dir of the billy.Filesystem.
@@ -220,6 +271,37 @@ func (fs *BoundOS) createDir(fullpath string) error {
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.
// Relative paths won't be allowed to ascend the base dir, so `../file` will become
// `/working-dir/file`.
@@ -233,7 +315,7 @@ func (fs *BoundOS) abs(filename string) (string, error) {
path, err := securejoin.SecureJoin(fs.baseDir, filename)
if err != nil {
return "", nil
return "", err
}
if fs.deduplicatePath {
@@ -246,24 +328,12 @@ func (fs *BoundOS) abs(filename string) (string, error) {
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
// a dir that is within the fs.baseDir, by first evaluating any symlinks
// that either filename or fs.baseDir may contain.
func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) {
// "/" contains all others.
if fs.baseDir == "/" {
if fs.baseDir == "/" || fs.baseDir == filename {
return true, nil
}
dir, err := filepath.EvalSymlinks(filepath.Dir(filename))
+10
View File
@@ -14,6 +14,8 @@ import (
// 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.
//
// Deprecated: use New with WithBoundOS instead.
//
// Behaviours of note:
// 1. A "soft chroot" translates the base dir to "/" for the purposes of the
// fs abstraction.
@@ -24,6 +26,14 @@ import (
type ChrootOS struct{}
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)
}
+19 -24
View File
@@ -16,8 +16,6 @@ import (
// can but returns the first error it encounters. If the path does not exist,
// RemoveAll returns nil (no error).
func RemoveAll(fs billy.Basic, path string) error {
fs, path = getUnderlyingAndPath(fs, path)
if r, ok := fs.(removerAll); ok {
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?
dir, serr := fs.Stat(path)
dir, serr := lstat(fs, path)
if serr != nil {
if errors.Is(serr, os.ErrNotExist) {
return nil
@@ -48,8 +46,8 @@ func removeAll(fs billy.Basic, path string) error {
return serr
}
if !dir.IsDir() {
// Not a directory; return the error from Remove.
if dir.Mode()&os.ModeSymlink != 0 || !dir.IsDir() {
// Not a directory we should recurse into; return the error from Remove.
return err
}
@@ -62,7 +60,7 @@ func removeAll(fs billy.Basic, path string) error {
fis, err := dirfs.ReadDir(path)
if err != nil {
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
}
@@ -91,7 +89,18 @@ func removeAll(fs billy.Basic, path string) error {
}
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.
@@ -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
// chance the file doesn't exist yet - keeps the number of tries in
// TempFile to a minimum.
var rand uint32
var randmu sync.Mutex
var (
rand uint32
randmu sync.Mutex
)
func reseed() uint32 {
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
@@ -220,22 +231,6 @@ func getTempDir(fs billy.Basic) string {
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.
// A successful call returns err == nil, not err == EOF.
// Because ReadFile reads the whole file, it does not treat an EOF from Read
+113 -94
View File
@@ -5,7 +5,7 @@ import (
"context"
"errors"
"fmt"
"io"
"slices"
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
@@ -20,6 +20,7 @@ const (
beginpgp string = "-----BEGIN PGP SIGNATURE-----"
endpgp string = "-----END PGP SIGNATURE-----"
headerpgp string = "gpgsig"
headerpgp256 string = "gpgsig-sha256"
headerencoding string = "encoding"
// 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
// commit, a pointer to the previous commit(s), etc.
// 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 {
// Hash of the commit object.
Hash plumbing.Hash
@@ -66,6 +72,9 @@ type Commit struct {
ExtraHeaders []ExtraHeader
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
@@ -98,8 +107,8 @@ func (h ExtraHeader) Format(f fmt.State, verb rune) {
func parseExtraHeader(line []byte) (ExtraHeader, bool) {
split := bytes.SplitN(line, []byte{' '}, 2)
out := ExtraHeader {
Key: string(bytes.TrimRight(split[0], "\n")),
out := ExtraHeader{
Key: string(bytes.TrimRight(split[0], "\n")),
Value: "",
}
@@ -181,6 +190,11 @@ func (c *Commit) NumParents() int {
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.
func (c *Commit) Parent(i int) (*Commit, error) {
if len(c.ParentHashes) == 0 || i > len(c.ParentHashes)-1 {
@@ -227,14 +241,23 @@ func (c *Commit) Type() plumbing.ObjectType {
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.
func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
if o.Type() != plumbing.CommitObject {
return ErrUnsupportedObject
}
c.reset()
c.Hash = o.Hash()
c.Encoding = defaultUtf8CommitMessageEncoding
c.src = o
reader, err := o.Reader()
if err != nil {
@@ -245,97 +268,17 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r)
var message bool
var mergetag bool
var pgpsig bool
var msgbuf bytes.Buffer
var extraheader *ExtraHeader = nil
for {
line, err := r.ReadBytes('\n')
if err != nil && err != io.EOF {
s := &commitScanner{r: r, c: c}
for state := scanTree; state != nil; {
state, err = state(s)
if err != nil {
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
}
@@ -344,11 +287,73 @@ func (c *Commit) Encode(o plumbing.EncodedObject) error {
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 {
if c.matchesSource() {
return stripObjectSignatures(o, c.src, plumbing.CommitObject)
}
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) {
o.SetType(plumbing.CommitObject)
w, err := o.Writer()
@@ -407,7 +412,9 @@ func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
}
for _, header := range c.ExtraHeaders {
if isStandardHeader(header.Key) {
continue
}
if _, err = fmt.Fprintf(w, "\n%s", header); err != nil {
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
// keyring and returns openpgp.Entity associated with verifying key on success.
func (c *Commit) Verify(armoredKeyRing string) (*openpgp.Entity, error) {
if countSignatureBlocks([]byte(c.PGPSignature)) > 1 {
return nil, ErrMultipleSignatures
}
keyRingReader := strings.NewReader(armoredKeyRing)
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
if err != nil {
+377
View File
@@ -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
View File
@@ -1,6 +1,13 @@
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 (
signatureTypeUnknown signatureType = iota
@@ -100,3 +107,116 @@ func parseSignedBytes(b []byte) (int, signatureType) {
}
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
View File
@@ -1,9 +1,8 @@
package object
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
@@ -13,6 +12,10 @@ import (
"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
// 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
@@ -39,6 +42,9 @@ type Tag struct {
Target plumbing.Hash
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.
@@ -77,13 +83,20 @@ func (t *Tag) Type() plumbing.ObjectType {
return plumbing.TagObject
}
func (t *Tag) reset() {
storer := t.s
*t = Tag{s: storer}
}
// Decode transforms a plumbing.EncodedObject into a Tag struct.
func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
if o.Type() != plumbing.TagObject {
return ErrUnsupportedObject
}
t.reset()
t.Hash = o.Hash()
t.src = o
reader, err := o.Reader()
if err != nil {
@@ -94,42 +107,15 @@ func (t *Tag) Decode(o plumbing.EncodedObject) (err error) {
r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r)
for {
var line []byte
line, err = r.ReadBytes('\n')
if err != nil && err != io.EOF {
scanner := &tagScanner{r: r, t: t}
for state := scanTagObject; state != nil; {
state, err = state(scanner)
if err != nil {
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)
if err != nil {
return err
}
data := scanner.msgbuf.Bytes()
if sm, _ := parseSignedBytes(data); sm >= 0 {
t.PGPSignature = string(data[sm:])
data = data[:sm]
@@ -144,11 +130,54 @@ func (t *Tag) Encode(o plumbing.EncodedObject) error {
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 {
if t.matchesSource() {
return stripObjectSignatures(o, t.src, plumbing.TagObject)
}
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) {
o.SetType(plumbing.TagObject)
w, err := o.Writer()
@@ -158,16 +187,26 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
defer ioutil.CheckClose(w, &err)
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 {
return err
}
if err = t.Tagger.Encode(w); err != nil {
return err
if !isZeroSignature(t.Tagger) {
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
}
@@ -175,11 +214,12 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
return err
}
// Note that this is highly sensitive to what it sent along in the message.
// Message *always* needs to end with a newline, or else the message and the
// signature will be concatenated into a corrupt object. Since this is a
// lower-level method, we assume you know what you are doing and have already
// done the needful on the message in the caller.
// Note that this is highly sensitive to what is sent along in the
// message. Message *always* needs to end with a newline, or else the
// message and the trailing signature will be concatenated into a
// corrupt object. Since this is a lower-level method, we assume you
// know what you are doing and have already done the needful on the
// message in the caller.
if includeSig {
if _, err = fmt.Fprint(w, t.PGPSignature); err != nil {
return err
@@ -189,6 +229,10 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
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
// different type of object ErrUnsupportedObject will be returned.
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
// 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) {
keyRingReader := strings.NewReader(armoredKeyRing)
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader)
+237
View File
@@ -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
}
}
}
+95 -33
View File
@@ -29,6 +29,7 @@ var (
ErrDirectoryNotFound = errors.New("directory not found")
ErrEntryNotFound = errors.New("entry not found")
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
@@ -37,9 +38,9 @@ type Tree struct {
Entries []TreeEntry
Hash plumbing.Hash
s storer.EncodedObjectStorer
m map[string]*TreeEntry
t map[string]*Tree // tree path cache
s storer.EncodedObjectStorer
t map[string]*Tree // tree path cache
entriesSorted bool
}
// GetTree gets a tree from an object storer and decodes it.
@@ -182,16 +183,43 @@ func (t *Tree) dir(baseName string) (*Tree, error) {
}
func (t *Tree) entry(baseName string) (*TreeEntry, error) {
if t.m == nil {
t.buildMap()
}
entry, ok := t.m[baseName]
if !ok {
if t.entriesSorted {
if entry := t.searchEntry(baseName); entry != nil {
return entry, nil
}
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
@@ -212,20 +240,25 @@ func (t *Tree) Type() plumbing.ObjectType {
return plumbing.TreeObject
}
func (t *Tree) reset() {
storer := t.s
*t = Tree{s: storer}
}
// Decode transform an plumbing.EncodedObject into a Tree struct
func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
if o.Type() != plumbing.TreeObject {
return ErrUnsupportedObject
}
t.reset()
t.Hash = o.Hash()
// assume tree is sorted as a valid tree should always be sorted.
t.entriesSorted = true
if o.Size() == 0 {
return nil
}
t.Entries = nil
t.m = nil
reader, err := o.Reader()
if err != nil {
return err
@@ -235,10 +268,14 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
r := sync.GetBufioReader(reader)
defer sync.PutBufioReader(r)
var prevSortName string
for {
str, err := r.ReadString(' ')
if err != nil {
if err == io.EOF {
if len(str) != 0 {
return fmt.Errorf("%w: missing mode terminator", ErrMalformedTree)
}
break
}
@@ -248,25 +285,41 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
mode, err := filemode.New(str)
if err != nil {
return err
return fmt.Errorf("%w: malformed mode", ErrMalformedTree)
}
mode = canonicalTreeMode(mode)
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
}
if len(name) == 1 {
return fmt.Errorf("%w: empty filename", ErrMalformedTree)
}
var hash plumbing.Hash
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
}
baseName := name[:len(name)-1]
t.Entries = append(t.Entries, TreeEntry{
entry := TreeEntry{
Hash: hash,
Mode: mode,
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
@@ -279,21 +332,37 @@ func (s TreeEntrySorter) Len() int {
}
func (s TreeEntrySorter) Less(i, j int) bool {
name1 := s[i].Name
name2 := s[j].Name
if s[i].Mode == filemode.Dir {
name1 += "/"
}
if s[j].Mode == filemode.Dir {
name2 += "/"
}
return name1 < name2
return treeEntrySortName(&s[i]) < treeEntrySortName(&s[j])
}
func (s TreeEntrySorter) Swap(i, j int) {
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.
// The tree entries must be sorted by name.
func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
@@ -329,13 +398,6 @@ func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
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
func (t *Tree) Diff(to *Tree) (Changes, error) {
return t.DiffContext(context.Background(), to)
+147 -21
View File
@@ -7,7 +7,6 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"net/url"
"reflect"
@@ -24,6 +23,33 @@ import (
"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
func applyHeadersToRequest(req *http.Request, content *bytes.Buffer, host string, requestType string) {
req.Header.Add("User-Agent", capability.DefaultAgent())
@@ -54,12 +80,15 @@ func advertisedReferences(ctx context.Context, s *session, serviceName string) (
s.ApplyAuthToRequest(req)
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 {
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)
if err = NewErr(res); err != nil {
@@ -96,6 +125,7 @@ type client struct {
client *http.Client
transports *lru.Cache
mutex sync.RWMutex
follow RedirectPolicy
}
// 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
// before the provided transport is added to the cache.
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 (
@@ -150,12 +185,16 @@ func NewClientWithOptions(c *http.Client, opts *ClientOptions) transport.Transpo
}
cl := &client{
client: c,
follow: FollowInitialRedirects,
}
if opts != nil {
if opts.CacheMaxEntries > 0 {
cl.transports = lru.New(opts.CacheMaxEntries)
}
if opts.RedirectPolicy != "" {
cl.follow = opts.RedirectPolicy
}
}
return cl
}
@@ -289,14 +328,9 @@ func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (*
}
}
httpClient = &http.Client{
Transport: transport,
CheckRedirect: c.client.CheckRedirect,
Jar: c.client.Jar,
Timeout: c.client.Timeout,
}
httpClient = c.cloneHTTPClient(transport)
} else {
httpClient = c.client
httpClient = c.cloneHTTPClient(c.client.Transport)
}
s := &session{
@@ -324,30 +358,122 @@ func (s *session) ApplyAuthToRequest(req *http.Request) {
s.auth.SetAuth(req)
}
func (s *session) ModifyEndpointIfRedirect(res *http.Response) {
func (s *session) ModifyEndpointIfRedirect(res *http.Response) error {
if res.Request == nil {
return
return nil
}
if s.endpoint == nil {
return fmt.Errorf("http redirect: nil endpoint")
}
r := res.Request
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 {
h = r.URL.Host
return err
}
if p != "" {
port, err := strconv.Atoi(p)
if err == nil {
s.endpoint.Port = port
}
if host != s.endpoint.Host || effectivePort(r.URL.Scheme, port) != effectivePort(s.endpoint.Protocol, s.endpoint.Port) {
s.endpoint.User = ""
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.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 {
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088
FROM golang:1.26@sha256:313faae491b410a35402c05d35e7518ae99103d957308e940e1ae2cfa0aac29b
ENV GOOS=linux
ENV GOARCH=arm
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088
FROM golang:1.26@sha256:313faae491b410a35402c05d35e7518ae99103d957308e940e1ae2cfa0aac29b
ENV GOOS=linux
ENV GOARCH=arm64
-5
View File
@@ -12,7 +12,6 @@ package sha1cd
// Original: https://github.com/golang/go/blob/master/src/crypto/sha1/sha1.go
import (
"crypto"
"encoding/binary"
"errors"
"hash"
@@ -20,10 +19,6 @@ import (
shared "github.com/pjbgf/sha1cd/internal"
)
func init() {
crypto.RegisterHash(crypto.SHA1, New)
}
// The size of a SHA-1 checksum in bytes.
const Size = shared.Size
+2 -2
View File
@@ -37,9 +37,9 @@ func block(dig *digest, p []byte) {
chunk := p[:shared.Chunk]
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 {
dig.col = true
+4 -4
View File
@@ -11,11 +11,11 @@
// Reference implementations:
// - 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) \
VPEXTRD $3, abcd, ((index*20)+0)(target); \
VPEXTRD $2, abcd, ((index*20)+4)(target); \
VPEXTRD $1, abcd, ((index*20)+8)(target); \
VPEXTRD $0, abcd, ((index*20)+12)(target); \
VPSHUFD $0x1B, abcd, X8; \
VMOVDQU X8, ((index*20)+0)(target); \
MOVL e, ((index*20)+16)(target);
#define LOADM1(m1, index, target) \
+2 -2
View File
@@ -34,8 +34,8 @@ func block(dig *digest, p []byte) {
blockARM64(dig.h[:], chunk, m1[:], cs[:])
rectifyCompressionState(m1, &cs)
col := checkCollision(m1, cs, dig.h)
rectifyCompressionState(&m1, &cs)
col := checkCollision(&m1, &cs, &dig.h)
if col {
dig.col = true
+13 -12
View File
@@ -127,7 +127,8 @@ func blockGeneric(dig *digest, p []byte) {
}
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 {
dig.col = true
hi++
@@ -143,23 +144,23 @@ func blockGeneric(dig *digest, p []byte) {
//go:noinline
func checkCollision(
m1 [shared.Rounds]uint32,
cs [shared.PreStepState][shared.WordBuffers]uint32,
h [shared.WordBuffers]uint32,
m1 *[shared.Rounds]uint32,
cs *[shared.PreStepState][shared.WordBuffers]uint32,
h *[shared.WordBuffers]uint32,
) bool {
if mask := ubc.CalculateDvMask(m1); mask != 0 {
dvs := ubc.SHA1_dvs()
for i := 0; dvs[i].DvType != 0; i++ {
if (mask & ((uint32)(1) << uint32(dvs[i].MaskB))) != 0 {
var csState [shared.WordBuffers]uint32
var csState *[shared.WordBuffers]uint32
switch dvs[i].TestT {
case 58:
csState = cs[1]
csState = &cs[1]
case 65:
csState = cs[2]
csState = &cs[2]
case 0:
csState = cs[0]
csState = &cs[0]
default:
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
// m2 is a secondary message created XORing with
// ubc's DM prior to the SHA recompression step.
m1, dvs[i].Dm,
m1, &dvs[i].Dm,
csState,
h)
@@ -182,8 +183,8 @@ func checkCollision(
}
//go:nosplit
func hasCollided(step uint32, m1, dm [shared.Rounds]uint32,
state [shared.WordBuffers]uint32, h [shared.WordBuffers]uint32) bool {
func hasCollided(step uint32, m1, dm *[shared.Rounds]uint32,
state *[shared.WordBuffers]uint32, h *[shared.WordBuffers]uint32) bool {
// Intermediary Hash Value.
ihv := [shared.WordBuffers]uint32{}
@@ -282,7 +283,7 @@ func hasCollided(step uint32, m1, dm [shared.Rounds]uint32,
//
//go:nosplit
func rectifyCompressionState(
m1 [shared.Rounds]uint32,
m1 *[shared.Rounds]uint32,
cs *[shared.PreStepState][shared.WordBuffers]uint32,
) {
if cs == nil {
+4 -1
View File
@@ -29,7 +29,10 @@ type DvInfo struct {
// bitconditions for that DV have been met.
//
//go:nosplit
func CalculateDvMask(W [80]uint32) uint32 {
func CalculateDvMask(W *[80]uint32) uint32 {
if W == nil {
return 0
}
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[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
View File
@@ -925,15 +925,10 @@ func (c *Command) execute(a []string) (err error) {
// Also say we need help if the command isn't runnable.
helpVal, err := c.Flags().GetBool(helpFlagName)
if err != nil {
// NOTE(d1): temporarily hardcoding "ayuda" as a replacement for "help"
// source of the pain: https://github.com/spf13/cobra/issues/2359
helpVal, err = c.Flags().GetBool("ayuda")
if err != nil {
// 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
}
// 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 {
@@ -1231,8 +1226,7 @@ func (c *Command) InitDefaultHelpFlag() {
} else {
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"})
}
}
+1 -1
View File
@@ -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).
// Enforce minimum padding and packet size.
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPacketSize)
// Enforce block size.
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
+7 -3
View File
@@ -274,10 +274,14 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
}
// 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
for _, algo := range algorithmsForKeyFormat(keyFormat) {
if slices.Contains(as.Algorithms(), underlyingAlgo(algo)) {
keyAlgos = append(keyAlgos, algo)
for _, signerAlgo := range as.Algorithms() {
if idx := slices.IndexFunc(supportedKeyAlgos, func(algo string) bool {
return underlyingAlgo(algo) == signerAlgo
}); idx >= 0 {
keyAlgos = append(keyAlgos, supportedKeyAlgos[idx])
}
}
+10 -3
View File
@@ -6,6 +6,7 @@ package hpack
import (
"fmt"
"strings"
)
// headerFieldTable implements a list of HeaderFields.
@@ -54,10 +55,16 @@ func (t *headerFieldTable) len() int {
// addEntry adds a new entry.
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
t.byName[f.Name] = id
t.byNameValue[pairNameValue{f.Name, f.Value}] = id
t.ents = append(t.ents, f)
t.byName[f2.Name] = id
t.byNameValue[pairNameValue{f2.Name, f2.Value}] = id
t.ents = append(t.ents, f2)
}
// evictOldest evicts the n oldest entries in the table.
+3 -6
View File
@@ -718,9 +718,6 @@ func canRetryError(err error) bool {
}
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)
if err != nil {
return nil, err
@@ -2861,6 +2858,9 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
var seenMaxConcurrentStreams bool
err := f.ForeachSetting(func(s Setting) error {
if err := s.Valid(); err != nil {
return err
}
switch s.ID {
case SettingMaxFrameSize:
cc.maxFrameSize = s.Val
@@ -2892,9 +2892,6 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
cc.henc.SetMaxDynamicTableSize(s.Val)
cc.peerMaxHeaderTableSize = s.Val
case SettingEnableConnectProtocol:
if err := s.Valid(); err != nil {
return err
}
// If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
// we require that it do so in the first SETTINGS frame.
//
+2
View File
@@ -6,6 +6,8 @@
package cpu
import "runtime"
func doinit() {
setMinimalFeatures()
+26
View File
@@ -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
View File
@@ -4,10 +4,6 @@
package cpu
import (
"golang.org/x/sys/windows"
)
func doinit() {
// 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
@@ -18,25 +14,25 @@ func doinit() {
ARM64.HasASIMD = 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.HasPMULL = true
ARM64.HasSHA1 = true
ARM64.HasSHA2 = true
}
ARM64.HasSHA3 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
ARM64.HasCRC32 = windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
ARM64.HasSHA512 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
ARM64.HasATOMICS = windows.IsProcessorFeaturePresent(windows.PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
ARM64.HasSHA3 = isProcessorFeaturePresent(_PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
ARM64.HasCRC32 = isProcessorFeaturePresent(_PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
ARM64.HasSHA512 = isProcessorFeaturePresent(_PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
ARM64.HasATOMICS = isProcessorFeaturePresent(_PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
if isProcessorFeaturePresent(_PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
ARM64.HasASIMDDP = 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.HasSM3 = true
}
ARM64.HasSVE = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
ARM64.HasSVE2 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
ARM64.HasJSCVT = windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
ARM64.HasSVE = isProcessorFeaturePresent(_PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
ARM64.HasSVE2 = isProcessorFeaturePresent(_PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
ARM64.HasJSCVT = isProcessorFeaturePresent(_PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
}
+48
View File
@@ -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
View File
@@ -13,11 +13,19 @@ import (
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
func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set)))
// CPUSetDynamic represents a bit mask of CPUs, to be used with [SchedGetaffinityDynamic],
// [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 {
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.
// If pid is 0 the calling thread is used.
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.
// If pid is 0 the calling thread is used.
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.
@@ -45,9 +53,7 @@ func (s *CPUSet) Zero() {
// will silently ignore any invalid CPU bits in [CPUSet] so this is an
// efficient way of resetting the CPU affinity of a process.
func (s *CPUSet) Fill() {
for i := range s {
s[i] = ^cpuMask(0)
}
cpuMaskFill(s[:])
}
func cpuBitsIndex(cpu int) int {
@@ -58,24 +64,27 @@ func cpuBitsMask(cpu int) cpuMask {
return cpuMask(1 << (uint(cpu) % _NCPUBITS))
}
// Set adds cpu to the set s.
func (s *CPUSet) Set(cpu int) {
func cpuMaskFill(s []cpuMask) {
for i := range s {
s[i] = ^cpuMask(0)
}
}
func cpuMaskSet(s []cpuMask, cpu int) {
i := cpuBitsIndex(cpu)
if i < len(s) {
s[i] |= cpuBitsMask(cpu)
}
}
// Clear removes cpu from the set s.
func (s *CPUSet) Clear(cpu int) {
func cpuMaskClear(s []cpuMask, cpu int) {
i := cpuBitsIndex(cpu)
if i < len(s) {
s[i] &^= cpuBitsMask(cpu)
}
}
// IsSet reports whether cpu is in the set s.
func (s *CPUSet) IsSet(cpu int) bool {
func cpuMaskIsSet(s []cpuMask, cpu int) bool {
i := cpuBitsIndex(cpu)
if i < len(s) {
return s[i]&cpuBitsMask(cpu) != 0
@@ -83,11 +92,98 @@ func (s *CPUSet) IsSet(cpu int) bool {
return false
}
// Count returns the number of CPUs in the set s.
func (s *CPUSet) Count() int {
func cpuMaskCount(s []cpuMask) int {
c := 0
for _, b := range s {
c += bits.OnesCount64(uint64(b))
}
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
View File
@@ -51,7 +51,7 @@ if [[ "$GOOS" = "linux" ]]; then
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
set -e
$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
fi
+6 -2
View File
@@ -2644,8 +2644,12 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) {
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, 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 {
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
View File
@@ -82,6 +82,9 @@ func Time(t *Time_t) (Time_t, error) {
}
func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{
{Sec: buf.Actime},
{Sec: buf.Modtime},
+3
View File
@@ -113,6 +113,9 @@ func Time(t *Time_t) (Time_t, error) {
}
func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{
{Sec: buf.Actime},
{Sec: buf.Modtime},
+3
View File
@@ -150,6 +150,9 @@ func Time(t *Time_t) (Time_t, error) {
}
func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{
{Sec: buf.Actime},
{Sec: buf.Modtime},
+3
View File
@@ -112,6 +112,9 @@ func Time(t *Time_t) (Time_t, error) {
}
func Utime(path string, buf *Utimbuf) error {
if buf == nil {
return Utimes(path, nil)
}
tv := []Timeval{
{Sec: buf.Actime},
{Sec: buf.Modtime},
+2 -2
View File
@@ -2241,8 +2241,8 @@ func Mseal(b []byte, flags uint) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func setMemPolicy(mode int, mask *CPUSet, size int) (err error) {
_, _, e1 := Syscall(SYS_SET_MEMPOLICY, uintptr(mode), uintptr(unsafe.Pointer(mask)), uintptr(size))
func setMemPolicy(mode int, mask unsafe.Pointer, size uintptr) (err error) {
_, _, e1 := Syscall(SYS_SET_MEMPOLICY, uintptr(mode), uintptr(mask), uintptr(size))
if e1 != 0 {
err = errnoErr(e1)
}
+1 -36
View File
@@ -163,42 +163,7 @@ func (p *Proc) Addr() uintptr {
// (according to the semantics of the specific function being called) before consulting
// the error. The error will be guaranteed to contain windows.Errno.
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
switch len(a) {
case 0:
return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
case 1:
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0)
case 2:
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0)
case 3:
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2])
case 4:
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0)
case 5:
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0)
case 6:
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5])
case 7:
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
case 8:
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
case 9:
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
case 10:
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
case 11:
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
case 12:
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
case 13:
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
case 14:
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
case 15:
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
default:
panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")
}
return syscall.SyscallN(p.Addr(), a...)
}
// A LazyDLL implements access to a single DLL.
+5 -1
View File
@@ -1438,13 +1438,17 @@ func GetSecurityInfo(handle Handle, objectType SE_OBJECT_TYPE, securityInformati
}
// GetNamedSecurityInfo queries the security information for a given named object and returns the self-relative security
// descriptor result on the Go heap.
// descriptor result on the Go heap. The security descriptor might be nil, even when err is nil, if the object exists
// but has no security descriptor.
func GetNamedSecurityInfo(objectName string, objectType SE_OBJECT_TYPE, securityInformation SECURITY_INFORMATION) (sd *SECURITY_DESCRIPTOR, err error) {
var winHeapSD *SECURITY_DESCRIPTOR
err = getNamedSecurityInfo(objectName, objectType, securityInformation, nil, nil, nil, nil, &winHeapSD)
if err != nil {
return
}
if winHeapSD == nil {
return nil, nil
}
defer LocalFree(Handle(unsafe.Pointer(winHeapSD)))
return winHeapSD.copySelfRelativeSecurityDescriptor(), nil
}
+10 -3
View File
@@ -892,9 +892,13 @@ const socket_error = uintptr(^uint32(0))
//sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar
//sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx
//sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex
//sys GetIfTable2Ex(level uint32, table **MibIfTable2) (errcode error) = iphlpapi.GetIfTable2Ex
//sys GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) = iphlpapi.GetIpForwardEntry2
//sys GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) = iphlpapi.GetIpForwardTable2
//sys GetIpInterfaceEntry(row *MibIpInterfaceRow) (errcode error) = iphlpapi.GetIpInterfaceEntry
//sys GetIpInterfaceTable(family uint16, table **MibIpInterfaceTable) (errcode error) = iphlpapi.GetIpInterfaceTable
//sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry
//sys GetUnicastIpAddressTable(family uint16, table **MibUnicastIpAddressTable) (errcode error) = iphlpapi.GetUnicastIpAddressTable
//sys FreeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable
//sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange
//sys NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyRouteChange2
@@ -1693,10 +1697,13 @@ func NewNTUnicodeString(s string) (*NTUnicodeString, error) {
if err != nil {
return nil, err
}
n := uint16(len(s16) * 2)
n := len(s16) * 2
if n > (1<<16)-1 {
return nil, syscall.EINVAL
}
return &NTUnicodeString{
Length: n - 2, // subtract 2 bytes for the NULL terminator
MaximumLength: n,
Length: uint16(n) - 2, // subtract 2 bytes for the NULL terminator
MaximumLength: uint16(n),
Buffer: &s16[0],
}, nil
}
+29
View File
@@ -2320,6 +2320,21 @@ type MibIfRow2 struct {
OutQLen uint64
}
// MIB_IF_TABLE_LEVEL enumeration from netioapi.h or
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ne-netioapi-mib_if_table_level.
const (
MibIfTableNormal = 0
MibIfTableRaw = 1
MibIfTableNormalWithoutStatistics = 2
)
// MibIfTable2 contains a table of logical and physical interface entries. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_if_table2.
type MibIfTable2 struct {
NumEntries uint32
Table [1]MibIfRow2
}
// IP_ADDRESS_PREFIX stores an IP address prefix. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-ip_address_prefix.
type IpAddressPrefix struct {
@@ -2413,6 +2428,13 @@ type MibUnicastIpAddressRow struct {
CreationTimeStamp Filetime
}
// MibUnicastIpAddressTable contains a table of unicast IP address entries. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_table.
type MibUnicastIpAddressTable struct {
NumEntries uint32
Table [1]MibUnicastIpAddressRow
}
const ScopeLevelCount = 16
// MIB_IPINTERFACE_ROW stores interface management information for a particular IP address family on a network interface.
@@ -2455,6 +2477,13 @@ type MibIpInterfaceRow struct {
DisableDefaultRoutes uint8
}
// MibIpInterfaceTable contains a table of IP interface entries. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipinterface_table.
type MibIpInterfaceTable struct {
NumEntries uint32
Table [1]MibIpInterfaceRow
}
// Console related constants used for the mode parameter to SetConsoleMode. See
// https://docs.microsoft.com/en-us/windows/console/setconsolemode for details.
+36
View File
@@ -188,9 +188,13 @@ var (
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
procGetIfEntry = modiphlpapi.NewProc("GetIfEntry")
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
procGetIfTable2Ex = modiphlpapi.NewProc("GetIfTable2Ex")
procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2")
procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2")
procGetIpInterfaceEntry = modiphlpapi.NewProc("GetIpInterfaceEntry")
procGetIpInterfaceTable = modiphlpapi.NewProc("GetIpInterfaceTable")
procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry")
procGetUnicastIpAddressTable = modiphlpapi.NewProc("GetUnicastIpAddressTable")
procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange")
procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2")
procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange")
@@ -1674,6 +1678,14 @@ func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) {
return
}
func GetIfTable2Ex(level uint32, table **MibIfTable2) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetIfTable2Ex.Addr(), uintptr(level), uintptr(unsafe.Pointer(table)))
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetIpForwardEntry2.Addr(), uintptr(unsafe.Pointer(row)))
if r0 != 0 {
@@ -1690,6 +1702,22 @@ func GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode erro
return
}
func GetIpInterfaceEntry(row *MibIpInterfaceRow) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetIpInterfaceEntry.Addr(), uintptr(unsafe.Pointer(row)))
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func GetIpInterfaceTable(family uint16, table **MibIpInterfaceTable) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetIpInterfaceTable.Addr(), uintptr(family), uintptr(unsafe.Pointer(table)))
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressEntry.Addr(), uintptr(unsafe.Pointer(row)))
if r0 != 0 {
@@ -1698,6 +1726,14 @@ func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) {
return
}
func GetUnicastIpAddressTable(family uint16, table **MibUnicastIpAddressTable) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressTable.Addr(), uintptr(family), uintptr(unsafe.Pointer(table)))
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
var _p0 uint32
if initialNotification {
+14 -14
View File
@@ -1,4 +1,4 @@
# coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
# coopcloud.tech/tagcmp v0.0.0-20260515102403-c26951b55977
## explicit; go 1.16
coopcloud.tech/tagcmp
# dario.cat/mergo v1.0.2
@@ -283,16 +283,16 @@ github.com/go-git/gcfg
github.com/go-git/gcfg/scanner
github.com/go-git/gcfg/token
github.com/go-git/gcfg/types
# github.com/go-git/go-billy/v5 v5.8.0
## explicit; go 1.24.0
# github.com/go-git/go-billy/v5 v5.9.0
## explicit; go 1.25.0
github.com/go-git/go-billy/v5
github.com/go-git/go-billy/v5/helper/chroot
github.com/go-git/go-billy/v5/helper/polyfill
github.com/go-git/go-billy/v5/memfs
github.com/go-git/go-billy/v5/osfs
github.com/go-git/go-billy/v5/util
# github.com/go-git/go-git/v5 v5.17.2
## explicit; go 1.24.0
# github.com/go-git/go-git/v5 v5.19.0
## explicit; go 1.25.0
github.com/go-git/go-git/v5
github.com/go-git/go-git/v5/config
github.com/go-git/go-git/v5/internal/path_util
@@ -492,7 +492,7 @@ github.com/opencontainers/image-spec/specs-go/v1
## explicit; go 1.18
# github.com/opencontainers/runtime-spec v1.1.0
## explicit
# github.com/pjbgf/sha1cd v0.5.0
# github.com/pjbgf/sha1cd v0.6.0
## explicit; go 1.22
github.com/pjbgf/sha1cd
github.com/pjbgf/sha1cd/internal
@@ -541,7 +541,7 @@ github.com/sirupsen/logrus
# github.com/skeema/knownhosts v1.3.2
## explicit; go 1.24.0
github.com/skeema/knownhosts
# github.com/spf13/cobra v1.10.1 => github.com/decentral1se/cobra v1.10.2-i18n
# github.com/spf13/cobra v1.10.1 => github.com/decentral1se/cobra v1.10.2
## explicit; go 1.15
github.com/spf13/cobra
github.com/spf13/cobra/doc
@@ -662,7 +662,7 @@ go.yaml.in/yaml/v2
# go.yaml.in/yaml/v3 v3.0.4
## explicit; go 1.16
go.yaml.in/yaml/v3
# golang.org/x/crypto v0.49.0
# golang.org/x/crypto v0.50.0
## explicit; go 1.25.0
golang.org/x/crypto/argon2
golang.org/x/crypto/blake2b
@@ -680,13 +680,13 @@ golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/agent
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
golang.org/x/crypto/ssh/knownhosts
# golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
# golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
## explicit; go 1.25.0
golang.org/x/exp/slices
golang.org/x/exp/slog
golang.org/x/exp/slog/internal
golang.org/x/exp/slog/internal/buffer
# golang.org/x/net v0.52.0
# golang.org/x/net v0.53.0
## explicit; go 1.25.0
golang.org/x/net/context
golang.org/x/net/http/httpguts
@@ -699,7 +699,7 @@ golang.org/x/net/internal/socks
golang.org/x/net/internal/timeseries
golang.org/x/net/proxy
golang.org/x/net/trace
# golang.org/x/sys v0.42.0
# golang.org/x/sys v0.44.0
## explicit; go 1.25.0
golang.org/x/sys/cpu
golang.org/x/sys/execabs
@@ -707,10 +707,10 @@ golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
golang.org/x/sys/windows/registry
# golang.org/x/term v0.41.0
# golang.org/x/term v0.43.0
## explicit; go 1.25.0
golang.org/x/term
# golang.org/x/text v0.35.0
# golang.org/x/text v0.36.0
## explicit; go 1.25.0
golang.org/x/text/cases
golang.org/x/text/internal
@@ -857,4 +857,4 @@ gotest.tools/v3/internal/cleanup
gotest.tools/v3/internal/difflib
gotest.tools/v3/internal/format
gotest.tools/v3/internal/source
# github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2-i18n
# github.com/spf13/cobra => github.com/decentral1se/cobra v1.10.2