From 58534228a9ef550f8aa4da84ddcd69d3e60a3c7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:03:19 +0000 Subject: [PATCH 01/98] chore(deps): bump actions/download-artifact from 6 to 7 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index d73b510e5df..961becd8b8d 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -256,7 +256,7 @@ jobs: - name: Checkout cli/cli uses: actions/checkout@v6 - name: Merge built artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 - name: Checkout documentation site uses: actions/checkout@v6 with: From aba49aab399cb8a485575523219ab6f6da669b60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:03:27 +0000 Subject: [PATCH 02/98] chore(deps): bump actions/upload-artifact from 5 to 6 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deployment.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index d73b510e5df..523261bc0c4 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -62,7 +62,7 @@ jobs: run: | go run ./cmd/gen-docs --website --doc-path dist/manual tar -czvf dist/manual.tar.gz -C dist -- manual - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: linux if-no-files-found: error @@ -134,7 +134,7 @@ jobs: run: | shopt -s failglob script/pkgmacos "$TAG_NAME" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: macos if-no-files-found: error @@ -238,7 +238,7 @@ jobs: Get-ChildItem -Path .\dist -Filter *.msi | ForEach-Object { .\script\sign.ps1 $_.FullName } - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: windows if-no-files-found: error From bd0177b039175e9399401cbe4ce960fef79945c0 Mon Sep 17 00:00:00 2001 From: Mikel Olasagasti Uranga Date: Wed, 21 Jan 2026 23:12:55 +0100 Subject: [PATCH 03/98] Fix fmt.Errorf format argument in ParseFullReference The error path passed an int to fmt.Errorf with %q, which expects a string. Use the original reference string to avoid a format mismatch. Signed-off-by: Mikel Olasagasti Uranga --- pkg/cmd/pr/shared/finder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/pr/shared/finder.go b/pkg/cmd/pr/shared/finder.go index 1879fff625a..697133680d8 100644 --- a/pkg/cmd/pr/shared/finder.go +++ b/pkg/cmd/pr/shared/finder.go @@ -349,7 +349,7 @@ func ParseFullReference(s string) (ghrepo.Interface, int, error) { number, err := strconv.Atoi(m[3]) if err != nil { - return nil, 0, fmt.Errorf("invalid reference: %q", number) + return nil, 0, fmt.Errorf("invalid reference: %q", s) } owner := m[1] From 0d8a697c7ccd5314a8c5240de339d0e8c2044a12 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 21 Jan 2026 23:30:43 +0000 Subject: [PATCH 04/98] fix(pr/shared): improve `ParseFullReference` error message Signed-off-by: Babak K. Shandiz --- pkg/cmd/pr/shared/finder.go | 2 +- pkg/cmd/pr/shared/finder_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/pr/shared/finder.go b/pkg/cmd/pr/shared/finder.go index 697133680d8..ea2c075ace7 100644 --- a/pkg/cmd/pr/shared/finder.go +++ b/pkg/cmd/pr/shared/finder.go @@ -349,7 +349,7 @@ func ParseFullReference(s string) (ghrepo.Interface, int, error) { number, err := strconv.Atoi(m[3]) if err != nil { - return nil, 0, fmt.Errorf("invalid reference: %q", s) + return nil, 0, fmt.Errorf("invalid reference: %q; %w", s, err) } owner := m[1] diff --git a/pkg/cmd/pr/shared/finder_test.go b/pkg/cmd/pr/shared/finder_test.go index 3f9ba6f5082..af34370609f 100644 --- a/pkg/cmd/pr/shared/finder_test.go +++ b/pkg/cmd/pr/shared/finder_test.go @@ -160,6 +160,11 @@ func TestParseFullReference(t *testing.T) { arg: "OWNER/#123", wantErr: `invalid reference: "OWNER/#123"`, }, + { + name: "invalid full form, too large number", + arg: "OWNER/REPO#9999999999999999999", + wantErr: `invalid reference: "OWNER/REPO#9999999999999999999"; strconv.Atoi: parsing "9999999999999999999": value out of range`, + }, } for _, tt := range tests { From c1c5ac82d84159f5c5f3802209908a343e25d6ef Mon Sep 17 00:00:00 2001 From: seth <117851610+Sethispr@users.noreply.github.com> Date: Thu, 22 Jan 2026 05:44:11 -0800 Subject: [PATCH 05/98] docs: lint source.md Fixes: - MD022 / blanks-around-headings Headings should be surrounded by blank lines - MD031 / blanks-around-fences Fenced code blocks should be surrounded by blank lines --- docs/source.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source.md b/docs/source.md index 8a83f244885..8486a3dbe35 100644 --- a/docs/source.md +++ b/docs/source.md @@ -18,6 +18,7 @@ 3. Build and install #### Unix-like systems + ```sh # installs to '/usr/local' by default; sudo may be required, or sudo -E for configured go environments $ make install @@ -27,15 +28,18 @@ ``` #### Windows + ```pwsh # build the `bin\gh.exe` binary > go run script\build.go ``` + There is no install step available on Windows. 4. Run `gh version` to check if it worked. #### Windows + Run `bin\gh version` to check if it worked. ## Cross-compiling binaries for different platforms @@ -44,10 +48,12 @@ You can use any platform with Go installed to build a binary that is intended fo or CPU architecture. This is achieved by setting environment variables such as GOOS and GOARCH. For example, to compile the `gh` binary for the 32-bit Raspberry Pi OS: + ```sh # on a Unix-like system: $ GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 make clean bin/gh ``` + ```pwsh # on Windows, pass environment variables as arguments to the build script: > go run script\build.go clean bin\gh GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 From 6811f941f397c6f26ddf31781a7938bf24c5e051 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:30:02 +0000 Subject: [PATCH 06/98] chore(deps): bump github.com/sigstore/sigstore from 1.10.0 to 1.10.4 Bumps [github.com/sigstore/sigstore](https://github.com/sigstore/sigstore) from 1.10.0 to 1.10.4. - [Release notes](https://github.com/sigstore/sigstore/releases) - [Commits](https://github.com/sigstore/sigstore/compare/v1.10.0...v1.10.4) --- updated-dependencies: - dependency-name: github.com/sigstore/sigstore dependency-version: 1.10.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index da2e232cd81..a5cef598d5b 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( golang.org/x/term v0.38.0 golang.org/x/text v0.32.0 google.golang.org/grpc v1.77.0 - google.golang.org/protobuf v1.36.10 + google.golang.org/protobuf v1.36.11 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v3 v3.0.1 ) @@ -161,7 +161,7 @@ require ( github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect github.com/sigstore/rekor v1.4.3 // indirect github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect - github.com/sigstore/sigstore v1.10.0 // indirect + github.com/sigstore/sigstore v1.10.4 // indirect github.com/sigstore/timestamp-authority/v2 v2.0.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.10.0 // indirect diff --git a/go.sum b/go.sum index d75c03c9c4c..7ddc419491b 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= -github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow= -github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -466,8 +466,8 @@ github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0= github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o= github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= github.com/sigstore/rekor-tiles/v2 v2.0.1/go.mod h1:Pjsbhzj5hc3MKY8FfVTYHBUHQEnP0ozC4huatu4x7OU= -github.com/sigstore/sigstore v1.10.0 h1:lQrmdzqlR8p9SCfWIpFoGUqdXEzJSZT2X+lTXOMPaQI= -github.com/sigstore/sigstore v1.10.0/go.mod h1:Ygq+L/y9Bm3YnjpJTlQrOk/gXyrjkpn3/AEJpmk1n9Y= +github.com/sigstore/sigstore v1.10.4 h1:ytOmxMgLdcUed3w1SbbZOgcxqwMG61lh1TmZLN+WeZE= +github.com/sigstore/sigstore v1.10.4/go.mod h1:tDiyrdOref3q6qJxm2G+JHghqfmvifB7hw+EReAfnbI= github.com/sigstore/sigstore-go v1.1.4 h1:wTTsgCHOfqiEzVyBYA6mDczGtBkN7cM8mPpjJj5QvMg= github.com/sigstore/sigstore-go v1.1.4/go.mod h1:2U/mQOT9cjjxrtIUeKDVhL+sHBKsnWddn8URlswdBsg= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.0 h1:UOHpiyezCj5RuixgIvCV3QyuxIGQT+N6nGZEXA7OTTY= @@ -638,8 +638,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 56b2a151280eba79a7f9c3429c61f4632fbf44bc Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 22 Jan 2026 20:49:25 +0000 Subject: [PATCH 07/98] chore: update licenses Signed-off-by: Babak K. Shandiz --- third-party-licenses.darwin.md | 4 ++-- third-party-licenses.linux.md | 4 ++-- third-party-licenses.windows.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index dd169fc8dd8..c44040186af 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -143,7 +143,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/sigstore/rekor-tiles/v2](https://pkg.go.dev/github.com/sigstore/rekor-tiles/v2) ([Apache-2.0](https://github.com/sigstore/rekor-tiles/blob/v2.0.1/LICENSE)) - [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.4.3/LICENSE)) - [github.com/sigstore/sigstore-go/pkg](https://pkg.go.dev/github.com/sigstore/sigstore-go/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore-go/blob/v1.1.4/LICENSE)) -- [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.0/LICENSE)) +- [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.4/LICENSE)) - [github.com/sigstore/timestamp-authority/v2/pkg/verification](https://pkg.go.dev/github.com/sigstore/timestamp-authority/v2/pkg/verification) ([Apache-2.0](https://github.com/sigstore/timestamp-authority/blob/v2.0.3/LICENSE)) - [github.com/sirupsen/logrus](https://pkg.go.dev/github.com/sirupsen/logrus) ([MIT](https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) @@ -181,7 +181,7 @@ Some packages may only be included on certain architectures or operating systems - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/3a174f9686a8/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/f26f9409b101/googleapis/rpc/LICENSE)) - [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.77.0/LICENSE)) -- [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.10/LICENSE)) +- [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) [cli/cli]: https://github.com/cli/cli diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 51f490b5817..ad464e9f55f 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -143,7 +143,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/sigstore/rekor-tiles/v2](https://pkg.go.dev/github.com/sigstore/rekor-tiles/v2) ([Apache-2.0](https://github.com/sigstore/rekor-tiles/blob/v2.0.1/LICENSE)) - [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.4.3/LICENSE)) - [github.com/sigstore/sigstore-go/pkg](https://pkg.go.dev/github.com/sigstore/sigstore-go/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore-go/blob/v1.1.4/LICENSE)) -- [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.0/LICENSE)) +- [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.4/LICENSE)) - [github.com/sigstore/timestamp-authority/v2/pkg/verification](https://pkg.go.dev/github.com/sigstore/timestamp-authority/v2/pkg/verification) ([Apache-2.0](https://github.com/sigstore/timestamp-authority/blob/v2.0.3/LICENSE)) - [github.com/sirupsen/logrus](https://pkg.go.dev/github.com/sirupsen/logrus) ([MIT](https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) @@ -181,7 +181,7 @@ Some packages may only be included on certain architectures or operating systems - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/3a174f9686a8/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/f26f9409b101/googleapis/rpc/LICENSE)) - [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.77.0/LICENSE)) -- [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.10/LICENSE)) +- [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) [cli/cli]: https://github.com/cli/cli diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 85e245a627d..60b3bb675d7 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -146,7 +146,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/sigstore/rekor-tiles/v2](https://pkg.go.dev/github.com/sigstore/rekor-tiles/v2) ([Apache-2.0](https://github.com/sigstore/rekor-tiles/blob/v2.0.1/LICENSE)) - [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.4.3/LICENSE)) - [github.com/sigstore/sigstore-go/pkg](https://pkg.go.dev/github.com/sigstore/sigstore-go/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore-go/blob/v1.1.4/LICENSE)) -- [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.0/LICENSE)) +- [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.4/LICENSE)) - [github.com/sigstore/timestamp-authority/v2/pkg/verification](https://pkg.go.dev/github.com/sigstore/timestamp-authority/v2/pkg/verification) ([Apache-2.0](https://github.com/sigstore/timestamp-authority/blob/v2.0.3/LICENSE)) - [github.com/sirupsen/logrus](https://pkg.go.dev/github.com/sirupsen/logrus) ([MIT](https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) @@ -184,7 +184,7 @@ Some packages may only be included on certain architectures or operating systems - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/3a174f9686a8/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/f26f9409b101/googleapis/rpc/LICENSE)) - [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.77.0/LICENSE)) -- [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.10/LICENSE)) +- [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) [cli/cli]: https://github.com/cli/cli From d4a4bc4dda6b18c06db6f33b5c7573b18ad50f8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:59:01 +0000 Subject: [PATCH 08/98] chore(deps): bump github.com/theupdateframework/go-tuf/v2 Bumps [github.com/theupdateframework/go-tuf/v2](https://github.com/theupdateframework/go-tuf) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/theupdateframework/go-tuf/releases) - [Commits](https://github.com/theupdateframework/go-tuf/compare/v2.3.0...v2.3.1) --- updated-dependencies: - dependency-name: github.com/theupdateframework/go-tuf/v2 dependency-version: 2.3.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 8 +++----- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a5cef598d5b..700ff6f9746 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/cli/cli/v2 -go 1.25.0 - -toolchain go1.25.5 +go 1.25.5 require ( github.com/AlecAivazis/survey/v2 v2.3.7 @@ -48,7 +46,7 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - github.com/theupdateframework/go-tuf/v2 v2.3.0 + github.com/theupdateframework/go-tuf/v2 v2.3.1 github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/yuin/goldmark v1.7.16 github.com/zalando/go-keyring v0.2.6 @@ -155,7 +153,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rodaine/table v1.3.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect diff --git a/go.sum b/go.sum index 7ddc419491b..32a6eb761b5 100644 --- a/go.sum +++ b/go.sum @@ -448,8 +448,8 @@ github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGq github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= -github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= -github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= @@ -505,8 +505,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= -github.com/theupdateframework/go-tuf/v2 v2.3.0 h1:gt3X8xT8qu/HT4w+n1jgv+p7koi5ad8XEkLXXZqG9AA= -github.com/theupdateframework/go-tuf/v2 v2.3.0/go.mod h1:xW8yNvgXRncmovMLvBxKwrKpsOwJZu/8x+aB0KtFcdw= +github.com/theupdateframework/go-tuf/v2 v2.3.1 h1:fReZUTLvPdqIL8Rd9xEKPmaxig8GIXe0kS4RSEaRfaM= +github.com/theupdateframework/go-tuf/v2 v2.3.1/go.mod h1:9S0Srkf3c13FelsOyt5OyG3ZZDq9OJDA4IILavrt72Y= github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI= From 0162ee2c7a86452c2a37204430543a9f31acd86f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:59:20 +0000 Subject: [PATCH 09/98] chore(deps): bump github.com/sigstore/rekor from 1.4.3 to 1.5.0 Bumps [github.com/sigstore/rekor](https://github.com/sigstore/rekor) from 1.4.3 to 1.5.0. - [Release notes](https://github.com/sigstore/rekor/releases) - [Changelog](https://github.com/sigstore/rekor/blob/main/CHANGELOG.md) - [Commits](https://github.com/sigstore/rekor/compare/v1.4.3...v1.5.0) --- updated-dependencies: - dependency-name: github.com/sigstore/rekor dependency-version: 1.5.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 18 ++++---- go.sum | 134 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/go.mod b/go.mod index a5cef598d5b..beab3c3e771 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( golang.org/x/sync v0.19.0 golang.org/x/term v0.38.0 golang.org/x/text v0.32.0 - google.golang.org/grpc v1.77.0 + google.golang.org/grpc v1.78.0 google.golang.org/protobuf v1.36.11 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v3 v3.0.1 @@ -100,12 +100,12 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.24.1 // indirect - github.com/go-openapi/errors v0.22.4 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/errors v0.22.6 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect github.com/go-openapi/loads v0.23.2 // indirect github.com/go-openapi/runtime v0.29.2 // indirect - github.com/go-openapi/spec v0.22.1 // indirect + github.com/go-openapi/spec v0.22.3 // indirect github.com/go-openapi/strfmt v0.25.0 // indirect github.com/go-openapi/swag v0.25.4 // indirect github.com/go-openapi/swag/cmdutils v0.25.4 // indirect @@ -159,7 +159,7 @@ require ( github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect - github.com/sigstore/rekor v1.4.3 // indirect + github.com/sigstore/rekor v1.5.0 // indirect github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect github.com/sigstore/sigstore v1.10.4 // indirect github.com/sigstore/timestamp-authority/v2 v2.0.3 // indirect @@ -180,9 +180,9 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect + golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/tools v0.39.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect ) diff --git a/go.sum b/go.sum index 7ddc419491b..7c722911e53 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeX al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= @@ -12,8 +12,8 @@ cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -56,34 +56,36 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= -github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= -github.com/aws/aws-sdk-go-v2/config v1.31.20 h1:/jWF4Wu90EhKCgjTdy1DGxcbcbNrjfBHvksEL79tfQc= -github.com/aws/aws-sdk-go-v2/config v1.31.20/go.mod h1:95Hh1Tc5VYKL9NJ7tAkDcqeKt+MCXQB1hQZaRdJIZE0= -github.com/aws/aws-sdk-go-v2/credentials v1.18.24 h1:iJ2FmPT35EaIB0+kMa6TnQ+PwG5A1prEdAw+PsMzfHg= -github.com/aws/aws-sdk-go-v2/credentials v1.18.24/go.mod h1:U91+DrfjAiXPDEGYhh/x29o4p0qHX5HDqG7y5VViv64= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M= +github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= +github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= +github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg= -github.com/aws/aws-sdk-go-v2/service/kms v1.48.2 h1:aL8Y/AbB6I+uw0MjLbdo68NQ8t5lNs3CY3S848HpETk= -github.com/aws/aws-sdk-go-v2/service/kms v1.48.2/go.mod h1:VJcNH6BLr+3VJwinRKdotLOMglHO8mIKlD3ea5c7hbw= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 h1:NjShtS1t8r5LUfFVtFeI8xLAHQNTa7UI0VawXlrBMFQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.3/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 h1:gTsnx0xXNQ6SBbymoDvcoRHL+q4l/dAFsQuKfDWSaGc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= -github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 h1:HK5ON3KmQV2HcAunnx4sKLB9aPf3gKGwVAf7xnx0QT0= -github.com/aws/aws-sdk-go-v2/service/sts v1.40.2/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= -github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= -github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.1 h1:U0asSZ3ifpuIehDPkRI2rxHbmFUMplDA2VeR9Uogrmw= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.1/go.mod h1:NZo9WJqQ0sxQ1Yqu1IwCHQFQunTms2MlVgejg16S1rY= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= @@ -198,8 +200,8 @@ github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeekl github.com/gdamore/tcell/v2 v2.13.4 h1:k4fdtdHGvLsLr2RttPnWEGTZEkEuTaL+rL6AOVFyRWU= github.com/gdamore/tcell/v2 v2.13.4/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= +github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -209,18 +211,18 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= -github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM= -github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= -github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= +github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= +github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4= github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= -github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k= -github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA= +github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= +github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= @@ -282,10 +284,10 @@ github.com/google/trillian v1.7.2 h1:EPBxc4YWY4Ak8tcuhyFleY+zYlbCDCa4Sn24e1Ka8Js github.com/google/trillian v1.7.2/go.mod h1:mfQJW4qRH6/ilABtPYNBerVJAJ/upxHLX81zxNQw05s= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= +github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -462,22 +464,22 @@ github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZV github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0= -github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o= +github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= +github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= github.com/sigstore/rekor-tiles/v2 v2.0.1/go.mod h1:Pjsbhzj5hc3MKY8FfVTYHBUHQEnP0ozC4huatu4x7OU= github.com/sigstore/sigstore v1.10.4 h1:ytOmxMgLdcUed3w1SbbZOgcxqwMG61lh1TmZLN+WeZE= github.com/sigstore/sigstore v1.10.4/go.mod h1:tDiyrdOref3q6qJxm2G+JHghqfmvifB7hw+EReAfnbI= github.com/sigstore/sigstore-go v1.1.4 h1:wTTsgCHOfqiEzVyBYA6mDczGtBkN7cM8mPpjJj5QvMg= github.com/sigstore/sigstore-go v1.1.4/go.mod h1:2U/mQOT9cjjxrtIUeKDVhL+sHBKsnWddn8URlswdBsg= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.0 h1:UOHpiyezCj5RuixgIvCV3QyuxIGQT+N6nGZEXA7OTTY= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.0/go.mod h1:U0CZmA2psabDa8DdiV7yXab0AHODzfKqvD2isH7Hrvw= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.0 h1:fq4+8Y4YadxeF8mzhoMRPZ1mVvDYXmI3BfS0vlkPT7M= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.0/go.mod h1:u05nqPWY05lmcdHhv2lPaWTH3FGUhJzO7iW2hbboK3Q= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.3 h1:D/FRl5J9UYAJPGZRAJbP0dH78pfwWnKsyCSBwFBU8CI= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.3/go.mod h1:2GIWuNvTRMvrzd0Nl8RNqxrt9H7X0OBStwOSzGYRjYw= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.3 h1:k5VMLf/ms7hh6MLgVoorM0K+hSMwZLXoywlxh4CXqP8= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.3/go.mod h1:S1Bp3dmP7jYlXcGLAxG81wRbE01NIZING8ZIy0dJlAI= github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.0 h1:iUEf5MZYOuXGnXxdF/WrarJrk0DTVHqeIOjYdtpVXtc= github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.0/go.mod h1:i6vg5JfEQix46R1rhQlrKmUtJoeH91drltyYOJEk1T4= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.0 h1:dUvPv/MP23ZPIXZUW45kvCIgC0ZRfYxEof57AB6bAtU= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.0/go.mod h1:fR/gDdPvJWGWL70/NgBBIL1O0/3Wma6JHs3tSSYg3s4= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.3 h1:lJSdaC/aOlFHlvqmmV696n1HdXLMLEKGwpNZMV0sKts= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.3/go.mod h1:b2rV9qPbt/jv/Yy75AIOZThP8j+pe1ZdLEjOwmjPdoA= github.com/sigstore/timestamp-authority/v2 v2.0.3 h1:sRyYNtdED/ttLCMdaYnwpf0zre1A9chvjTnCmWWxN8Y= github.com/sigstore/timestamp-authority/v2 v2.0.3/go.mod h1:mDaHxkt3HmZYoIlwYj4QWo0RUr7VjYU52aVO5f5Qb3I= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -582,10 +584,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -628,16 +630,16 @@ golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= -google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= +google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 1318fe94d7380cbcd23b5c53bf8e0b75b6f846e1 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 22 Jan 2026 21:02:13 +0000 Subject: [PATCH 10/98] chore: update licenses Signed-off-by: Babak K. Shandiz --- third-party-licenses.darwin.md | 4 ++-- third-party-licenses.linux.md | 4 ++-- third-party-licenses.windows.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index c44040186af..e7e1a95eca0 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -134,7 +134,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/rivo/uniseg](https://pkg.go.dev/github.com/rivo/uniseg) ([MIT](https://github.com/rivo/uniseg/blob/v0.4.7/LICENSE.txt)) - [github.com/rodaine/table](https://pkg.go.dev/github.com/rodaine/table) ([MIT](https://github.com/rodaine/table/blob/v1.3.0/license)) - [github.com/russross/blackfriday/v2](https://pkg.go.dev/github.com/russross/blackfriday/v2) ([BSD-2-Clause](https://github.com/russross/blackfriday/blob/v2.1.0/LICENSE.txt)) -- [github.com/secure-systems-lab/go-securesystemslib](https://pkg.go.dev/github.com/secure-systems-lab/go-securesystemslib) ([MIT](https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.9.1/LICENSE)) +- [github.com/secure-systems-lab/go-securesystemslib](https://pkg.go.dev/github.com/secure-systems-lab/go-securesystemslib) ([MIT](https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.10.0/LICENSE)) - [github.com/shibumi/go-pathspec](https://pkg.go.dev/github.com/shibumi/go-pathspec) ([Apache-2.0](https://github.com/shibumi/go-pathspec/blob/v1.3.0/LICENSE)) - [github.com/shopspring/decimal](https://pkg.go.dev/github.com/shopspring/decimal) ([MIT](https://github.com/shopspring/decimal/blob/v1.4.0/LICENSE)) - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) @@ -151,7 +151,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/stretchr/objx](https://pkg.go.dev/github.com/stretchr/objx) ([MIT](https://github.com/stretchr/objx/blob/v0.5.2/LICENSE)) - [github.com/stretchr/testify](https://pkg.go.dev/github.com/stretchr/testify) ([MIT](https://github.com/stretchr/testify/blob/v1.11.1/LICENSE)) -- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.0/LICENSE)) +- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.1/LICENSE)) - [github.com/thlib/go-timezone-local/tzlocal](https://pkg.go.dev/github.com/thlib/go-timezone-local/tzlocal) ([Unlicense](https://github.com/thlib/go-timezone-local/blob/v0.0.6/LICENSE)) - [github.com/transparency-dev/formats/log](https://pkg.go.dev/github.com/transparency-dev/formats/log) ([Apache-2.0](https://github.com/transparency-dev/formats/blob/404c0d5b696c/LICENSE)) - [github.com/transparency-dev/merkle](https://pkg.go.dev/github.com/transparency-dev/merkle) ([Apache-2.0](https://github.com/transparency-dev/merkle/blob/v0.0.2/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index ad464e9f55f..855ba501824 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -134,7 +134,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/rivo/uniseg](https://pkg.go.dev/github.com/rivo/uniseg) ([MIT](https://github.com/rivo/uniseg/blob/v0.4.7/LICENSE.txt)) - [github.com/rodaine/table](https://pkg.go.dev/github.com/rodaine/table) ([MIT](https://github.com/rodaine/table/blob/v1.3.0/license)) - [github.com/russross/blackfriday/v2](https://pkg.go.dev/github.com/russross/blackfriday/v2) ([BSD-2-Clause](https://github.com/russross/blackfriday/blob/v2.1.0/LICENSE.txt)) -- [github.com/secure-systems-lab/go-securesystemslib](https://pkg.go.dev/github.com/secure-systems-lab/go-securesystemslib) ([MIT](https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.9.1/LICENSE)) +- [github.com/secure-systems-lab/go-securesystemslib](https://pkg.go.dev/github.com/secure-systems-lab/go-securesystemslib) ([MIT](https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.10.0/LICENSE)) - [github.com/shibumi/go-pathspec](https://pkg.go.dev/github.com/shibumi/go-pathspec) ([Apache-2.0](https://github.com/shibumi/go-pathspec/blob/v1.3.0/LICENSE)) - [github.com/shopspring/decimal](https://pkg.go.dev/github.com/shopspring/decimal) ([MIT](https://github.com/shopspring/decimal/blob/v1.4.0/LICENSE)) - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) @@ -151,7 +151,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/stretchr/objx](https://pkg.go.dev/github.com/stretchr/objx) ([MIT](https://github.com/stretchr/objx/blob/v0.5.2/LICENSE)) - [github.com/stretchr/testify](https://pkg.go.dev/github.com/stretchr/testify) ([MIT](https://github.com/stretchr/testify/blob/v1.11.1/LICENSE)) -- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.0/LICENSE)) +- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.1/LICENSE)) - [github.com/thlib/go-timezone-local/tzlocal](https://pkg.go.dev/github.com/thlib/go-timezone-local/tzlocal) ([Unlicense](https://github.com/thlib/go-timezone-local/blob/v0.0.6/LICENSE)) - [github.com/transparency-dev/formats/log](https://pkg.go.dev/github.com/transparency-dev/formats/log) ([Apache-2.0](https://github.com/transparency-dev/formats/blob/404c0d5b696c/LICENSE)) - [github.com/transparency-dev/merkle](https://pkg.go.dev/github.com/transparency-dev/merkle) ([Apache-2.0](https://github.com/transparency-dev/merkle/blob/v0.0.2/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 60b3bb675d7..cae6a27b48b 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -137,7 +137,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/rivo/uniseg](https://pkg.go.dev/github.com/rivo/uniseg) ([MIT](https://github.com/rivo/uniseg/blob/v0.4.7/LICENSE.txt)) - [github.com/rodaine/table](https://pkg.go.dev/github.com/rodaine/table) ([MIT](https://github.com/rodaine/table/blob/v1.3.0/license)) - [github.com/russross/blackfriday/v2](https://pkg.go.dev/github.com/russross/blackfriday/v2) ([BSD-2-Clause](https://github.com/russross/blackfriday/blob/v2.1.0/LICENSE.txt)) -- [github.com/secure-systems-lab/go-securesystemslib](https://pkg.go.dev/github.com/secure-systems-lab/go-securesystemslib) ([MIT](https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.9.1/LICENSE)) +- [github.com/secure-systems-lab/go-securesystemslib](https://pkg.go.dev/github.com/secure-systems-lab/go-securesystemslib) ([MIT](https://github.com/secure-systems-lab/go-securesystemslib/blob/v0.10.0/LICENSE)) - [github.com/shibumi/go-pathspec](https://pkg.go.dev/github.com/shibumi/go-pathspec) ([Apache-2.0](https://github.com/shibumi/go-pathspec/blob/v1.3.0/LICENSE)) - [github.com/shopspring/decimal](https://pkg.go.dev/github.com/shopspring/decimal) ([MIT](https://github.com/shopspring/decimal/blob/v1.4.0/LICENSE)) - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) @@ -154,7 +154,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/stretchr/objx](https://pkg.go.dev/github.com/stretchr/objx) ([MIT](https://github.com/stretchr/objx/blob/v0.5.2/LICENSE)) - [github.com/stretchr/testify](https://pkg.go.dev/github.com/stretchr/testify) ([MIT](https://github.com/stretchr/testify/blob/v1.11.1/LICENSE)) -- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.0/LICENSE)) +- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.1/LICENSE)) - [github.com/thlib/go-timezone-local/tzlocal](https://pkg.go.dev/github.com/thlib/go-timezone-local/tzlocal) ([Unlicense](https://github.com/thlib/go-timezone-local/blob/v0.0.6/LICENSE)) - [github.com/transparency-dev/formats/log](https://pkg.go.dev/github.com/transparency-dev/formats/log) ([Apache-2.0](https://github.com/transparency-dev/formats/blob/404c0d5b696c/LICENSE)) - [github.com/transparency-dev/merkle](https://pkg.go.dev/github.com/transparency-dev/merkle) ([Apache-2.0](https://github.com/transparency-dev/merkle/blob/v0.0.2/LICENSE)) From 09e66252fd4340b880d674e77a8cbfe0b181048e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:22:34 +0000 Subject: [PATCH 11/98] chore(deps): bump goreleaser/goreleaser-action from 6.0.0 to 6.4.0 Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 6.0.0 to 6.4.0. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/286f3b13b1b49da4ac219696163fb8c1c93e1200...e435ccd777264be153ace6237001ef4d979d3a7a) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-version: 6.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deployment.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 7243534fb70..8728bc5ec04 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -50,7 +50,7 @@ jobs: with: go-version-file: 'go.mod' - name: Install GoReleaser - uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: # The version is pinned not only for security purposes, but also to avoid breaking # our scripts, which rely on the specific file names generated by GoReleaser. @@ -111,7 +111,7 @@ jobs: security set-key-partition-list -S "apple-tool:,apple:,codesign:" -s -k "$keychain_password" "$keychain" rm "$RUNNER_TEMP/cert.p12" - name: Install GoReleaser - uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: # The version is pinned not only for security purposes, but also to avoid breaking # our scripts, which rely on the specific file names generated by GoReleaser. @@ -173,7 +173,7 @@ jobs: with: go-version-file: 'go.mod' - name: Install GoReleaser - uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: # The version is pinned not only for security purposes, but also to avoid breaking # our scripts, which rely on the specific file names generated by GoReleaser. From b9c85fff68baaa687886aaac3fbc50be8a3931ec Mon Sep 17 00:00:00 2001 From: cuiweixie Date: Mon, 26 Jan 2026 22:25:00 +0800 Subject: [PATCH 12/98] typo: dont to don't --- api/queries_projects_v2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/queries_projects_v2.go b/api/queries_projects_v2.go index 47ee87c8357..0126c1caa3b 100644 --- a/api/queries_projects_v2.go +++ b/api/queries_projects_v2.go @@ -320,7 +320,7 @@ func CurrentUserProjectsV2(client *Client, hostname string) ([]ProjectV2, error) return projectsV2, nil } -// When querying ProjectsV2 fields we generally dont want to show the user +// When querying ProjectsV2 fields we generally don't want to show the user // scope errors and field does not exist errors. ProjectsV2IgnorableError // checks against known error strings to see if an error can be safely ignored. // Due to the fact that the GraphQLClient can return multiple types of errors From be1e21095ca01a5684eebf2c133d0434b2f62f51 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 21 Nov 2025 08:37:43 -0700 Subject: [PATCH 13/98] prshared: named prompt interface parameters Updated the Prompt interface in survey.go to include parameter names for all methods, improving code readability and clarity. --- pkg/cmd/pr/shared/survey.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/pr/shared/survey.go b/pkg/cmd/pr/shared/survey.go index 4b66bb0fa89..56885487e22 100644 --- a/pkg/cmd/pr/shared/survey.go +++ b/pkg/cmd/pr/shared/survey.go @@ -35,11 +35,11 @@ const ( ) type Prompt interface { - Input(string, string) (string, error) - Select(string, string, []string) (int, error) - MarkdownEditor(string, string, bool) (string, error) - Confirm(string, bool) (bool, error) - MultiSelect(string, []string, []string) ([]int, error) + Input(prompt string, defaultValue string) (string, error) + Select(prompt string, defaultValue string, options []string) (int, error) + MarkdownEditor(prompt string, defaultValue string, blankAllowed bool) (string, error) + Confirm(prompt string, defaultValue bool) (bool, error) + MultiSelect(prompt string, defaults []string, options []string) ([]int, error) } func ConfirmIssueSubmission(p Prompt, allowPreview bool, allowMetadata bool) (Action, error) { From 0beb74bf729349c09fb3e8144226639bb0472fe6 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:01:55 -0700 Subject: [PATCH 14/98] MultiSelectWithSearch initial implementation Initial implementation of MultiSelectWithSearch: - Implement by survey and accessible prompters. They use the same internal func under the hood. - Implement in `gh preview prompter` for initial testing and demonstration - Implement interface changes across the codebase and mocks to satisfy compiler. - Implement tests for new MultiSelectWithSearch prompter --- internal/prompter/accessible_prompter_test.go | 162 ++++++++++++++++++ internal/prompter/prompter.go | 154 +++++++++++++++++ internal/prompter/prompter_mock.go | 86 +++++++++- internal/prompter/test.go | 25 ++- pkg/cmd/pr/shared/survey.go | 5 + pkg/cmd/preview/prompter/prompter.go | 77 +++++++-- 6 files changed, 478 insertions(+), 31 deletions(-) diff --git a/internal/prompter/accessible_prompter_test.go b/internal/prompter/accessible_prompter_test.go index 770ff0e7691..03baa34b743 100644 --- a/internal/prompter/accessible_prompter_test.go +++ b/internal/prompter/accessible_prompter_test.go @@ -224,6 +224,167 @@ func TestAccessiblePrompter(t *testing.T) { assert.Equal(t, []int{1}, multiSelectValues) }) + t.Run("MultiSelectWithSearch - basic flow", func(t *testing.T) { + console := newTestVirtualTerminal(t) + p := newTestAccessiblePrompter(t, console) + persistentOptions := []string{"persistent-option-1"} + searchFunc := func(input string) ([]string, []string, int, error) { + var searchResultKeys []string + var searchResultLabels []string + + // Initial search with no input + if input == "" { + moreResults := 2 + searchResultKeys = []string{"initial-result-1", "initial-result-2"} + searchResultLabels = []string{"Initial Result Label 1", "Initial Result Label 2"} + return searchResultKeys, searchResultLabels, moreResults, nil + } + + // Subsequent search with input + moreResults := 0 + searchResultKeys = []string{"search-result-1", "search-result-2"} + searchResultLabels = []string{"Search Result Label 1", "Search Result Label 2"} + return searchResultKeys, searchResultLabels, moreResults, nil + } + + go func() { + // Wait for prompt to appear + _, err := console.ExpectString("Select an option \r\n") + require.NoError(t, err) + + // Select the search option, which will always be the first option + _, err = console.SendLine("1") + require.NoError(t, err) + + // Submit search + _, err = console.SendLine("0") + require.NoError(t, err) + + // Wait for the search prompt to appear + _, err = console.ExpectString("Search for an option") + require.NoError(t, err) + + // Enter some search text to trigger the search + _, err = console.SendLine("search text") + require.NoError(t, err) + + // Wait for the multiselect prompt to re-appear after search + _, err = console.ExpectString("Select an option \r\n") + require.NoError(t, err) + + // Select the first search result + _, err = console.SendLine("2") + require.NoError(t, err) + + // This confirms selections + _, err = console.SendLine("0") + require.NoError(t, err) + }() + multiSelectValues, err := p.MultiSelectWithSearch("Select an option", "Search for an option", []string{}, persistentOptions, searchFunc) + require.NoError(t, err) + assert.Equal(t, []string{"search-result-1"}, multiSelectValues) + }) + + t.Run("MultiSelectWithSearch - defaults are pre-selected", func(t *testing.T) { + console := newTestVirtualTerminal(t) + p := newTestAccessiblePrompter(t, console) + initialSearchResultKeys := []string{"initial-result-1"} + initialSearchResultLabels := []string{"Initial Result Label 1"} + defaultOptions := initialSearchResultKeys + searchFunc := func(input string) ([]string, []string, int, error) { + // Initial search with no input + if input == "" { + moreResults := 2 + return initialSearchResultKeys, initialSearchResultLabels, moreResults, nil + } + + // No search selected, so this should fail the test. + t.FailNow() + return nil, nil, 0, nil + } + + go func() { + // Wait for prompt to appear + _, err := console.ExpectString("Select an option (default: Initial Result Label 1) \r\n") + require.NoError(t, err) + + // This confirms default selections + _, err = console.SendLine("0") + require.NoError(t, err) + }() + multiSelectValues, err := p.MultiSelectWithSearch("Select an option", "Search for an option", defaultOptions, initialSearchResultKeys, searchFunc) + require.NoError(t, err) + assert.Equal(t, defaultOptions, multiSelectValues) + }) + + t.Run("MultiSelectWithSearch - selected options persist between searches", func(t *testing.T) { + console := newTestVirtualTerminal(t) + p := newTestAccessiblePrompter(t, console) + initialSearchResultKeys := []string{"initial-result-1"} + initialSearchResultLabels := []string{"Initial Result Label 1"} + moreResultKeys := []string{"more-result-1"} + moreResultLabels := []string{"More Result Label 1"} + + searchFunc := func(input string) ([]string, []string, int, error) { + // Initial search with no input + if input == "" { + moreResults := 2 + return initialSearchResultKeys, initialSearchResultLabels, moreResults, nil + } + + // Subsequent search with input "more" + if input == "more" { + return moreResultKeys, moreResultLabels, 0, nil + } + + // No other searches expected + t.FailNow() + return nil, nil, 0, nil + } + + go func() { + // Wait for prompt to appear + _, err := console.ExpectString("Select an option \r\n") + require.NoError(t, err) + + // Select one of our initial search results + _, err = console.SendLine("2") + require.NoError(t, err) + + // Select to search + _, err = console.SendLine("1") + require.NoError(t, err) + + // Submit the search selection + _, err = console.SendLine("0") + require.NoError(t, err) + + // Wait for the search prompt to appear + _, err = console.ExpectString("Search for an option") + require.NoError(t, err) + + // Enter some search text to trigger the search + _, err = console.SendLine("more") + require.NoError(t, err) + + // Wait for the multiselect prompt to re-appear after search + _, err = console.ExpectString("Select up to") + require.NoError(t, err) + + // Select the new option from the new search results + _, err = console.SendLine("3") + require.NoError(t, err) + + // Submit selections + _, err = console.SendLine("0") + require.NoError(t, err) + }() + multiSelectValues, err := p.MultiSelectWithSearch("Select an option", "Search for an option", []string{}, []string{}, searchFunc) + require.NoError(t, err) + expectedValues := append(initialSearchResultKeys, moreResultKeys...) + assert.Equal(t, expectedValues, multiSelectValues) + }) + t.Run("Input", func(t *testing.T) { console := newTestVirtualTerminal(t) p := newTestAccessiblePrompter(t, console) @@ -642,6 +803,7 @@ func newTestVirtualTerminal(t *testing.T) *expect.Console { failOnExpectError(t), failOnSendError(t), expect.WithDefaultTimeout(time.Second), + // expect.WithLogger(log.New(os.Stdout, "", 0)), } console, err := expect.NewConsole(consoleOpts...) diff --git a/internal/prompter/prompter.go b/internal/prompter/prompter.go index c2233fd9266..dddb49035e3 100644 --- a/internal/prompter/prompter.go +++ b/internal/prompter/prompter.go @@ -21,6 +21,15 @@ type Prompter interface { Select(prompt string, defaultValue string, options []string) (int, error) // MultiSelect prompts the user to select one or more options from a list of options. MultiSelect(prompt string, defaults []string, options []string) ([]int, error) + // MultiSelectWithSearch is MultiSelect with an added search option to the list, + // prompting the user for text input to filter the options via the searchFunc. + // Items selected in the search are persisted in the list after subsequent searches. + // Items passed in persistentOptions are always shown in the list, even when not selected. + // Unlike MultiSelect, MultiselectWithSearch returns the selected option strings, + // not their indices, since the list of options is dynamic. + // The searchFunc args and return values are: func(query) (map[keys]labels, moreResultsCount, searchError) + // Where the selected keys are eventually returned by MultiSelectWithSearch and the labels are what is shown to the user in the prompt. + MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) // Input prompts the user to enter a string value. Input(prompt string, defaultValue string) (string, error) // Password prompts the user to enter a password. @@ -320,6 +329,10 @@ func (p *accessiblePrompter) MarkdownEditor(prompt, defaultValue string, blankAl return text, nil } +func (p *accessiblePrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { + return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc) +} + type surveyPrompter struct { prompter *ghPrompter.Prompter stdin ghPrompter.FileReader @@ -336,6 +349,147 @@ func (p *surveyPrompter) MultiSelect(prompt string, defaultValues, options []str return p.prompter.MultiSelect(prompt, defaultValues, options) } +func (p *surveyPrompter) MultiSelectWithSearch(prompt string, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { + return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc) +} + +func multiSelectWithSearch(p Prompter, prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { + selectedOptions := defaultValues + + // The optionKeyLabels map is used to uniquely identify optionKeyLabels + // and provide optional display labels. + optionKeyLabels := make(map[string]string) + for _, k := range selectedOptions { + optionKeyLabels[k] = k + } + + searchResultKeys, searchResultLabels, moreResults, err := searchFunc("") + if err != nil { + return nil, fmt.Errorf("failed to search: %w", err) + } + + for i, k := range searchResultKeys { + optionKeyLabels[k] = searchResultLabels[i] + } + + for { + // Build dynamic option list -> search sentinel, selections, search results, persistent options. + optionKeys := make([]string, 0, 1+len(selectedOptions)+len(searchResultKeys)+len(persistentValues)) + optionLabels := make([]string, 0, len(optionKeys)) + + // 1. Search sentinel. + optionKeys = append(optionKeys, "") + if moreResults > 0 { + optionLabels = append(optionLabels, fmt.Sprintf("Search (%d more)", moreResults)) + } else { + optionLabels = append(optionLabels, "Search") + } + + // 2. Selections + for _, k := range selectedOptions { + l := optionKeyLabels[k] + + if l == "" { + l = k + } + + optionKeys = append(optionKeys, k) + optionLabels = append(optionLabels, l) + } + + // 3. Search results + for _, k := range searchResultKeys { + // It's already selected or persistent, if we add here we'll have duplicates. + if slices.Contains(selectedOptions, k) || slices.Contains(persistentValues, k) { + continue + } + + l := optionKeyLabels[k] + if l == "" { + l = k + } + optionKeys = append(optionKeys, k) + optionLabels = append(optionLabels, l) + } + + // 4. Persistent options + for _, k := range persistentValues { + if slices.Contains(selectedOptions, k) { + continue + } + + l := optionKeyLabels[k] + if l == "" { + l = k + } + + optionKeys = append(optionKeys, k) + optionLabels = append(optionLabels, l) + } + + selectedOptionLabels := make([]string, len(selectedOptions)) + for i, k := range selectedOptions { + l := optionKeyLabels[k] + if l == "" { + l = k + } + selectedOptionLabels[i] = l + } + + selectedIdxs, err := p.MultiSelect(prompt, selectedOptionLabels, optionLabels) + if err != nil { + return nil, err + } + + pickedSearch := false + var newSelectedOptions []string + for _, idx := range selectedIdxs { + if idx == 0 { // Search sentinel selected + pickedSearch = true + continue + } + + if idx < 0 || idx >= len(optionKeys) { + continue + } + + key := optionKeys[idx] + if key == "" { + continue + } + + newSelectedOptions = append(newSelectedOptions, key) + } + + selectedOptions = newSelectedOptions + for _, k := range selectedOptions { + if _, ok := optionKeyLabels[k]; !ok { + optionKeyLabels[k] = k + } + } + + if pickedSearch { + query, err := p.Input(searchPrompt, "") + if err != nil { + return nil, err + } + + searchResultKeys, searchResultLabels, moreResults, err = searchFunc(query) + if err != nil { + return nil, err + } + + for i, k := range searchResultKeys { + optionKeyLabels[k] = searchResultLabels[i] + } + + continue + } + + return selectedOptions, nil + } +} + func (p *surveyPrompter) Input(prompt, defaultValue string) (string, error) { return p.prompter.Input(prompt, defaultValue) } diff --git a/internal/prompter/prompter_mock.go b/internal/prompter/prompter_mock.go index b15f8bf96a7..4543004a07c 100644 --- a/internal/prompter/prompter_mock.go +++ b/internal/prompter/prompter_mock.go @@ -38,6 +38,9 @@ var _ Prompter = &PrompterMock{} // MultiSelectFunc: func(prompt string, defaults []string, options []string) ([]int, error) { // panic("mock out the MultiSelect method") // }, +// MultiSelectWithSearchFunc: func(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { +// panic("mock out the MultiSelectWithSearch method") +// }, // PasswordFunc: func(prompt string) (string, error) { // panic("mock out the Password method") // }, @@ -72,6 +75,9 @@ type PrompterMock struct { // MultiSelectFunc mocks the MultiSelect method. MultiSelectFunc func(prompt string, defaults []string, options []string) ([]int, error) + // MultiSelectWithSearchFunc mocks the MultiSelectWithSearch method. + MultiSelectWithSearchFunc func(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) + // PasswordFunc mocks the Password method. PasswordFunc func(prompt string) (string, error) @@ -123,6 +129,19 @@ type PrompterMock struct { // Options is the options argument value. Options []string } + // MultiSelectWithSearch holds details about calls to the MultiSelectWithSearch method. + MultiSelectWithSearch []struct { + // Prompt is the prompt argument value. + Prompt string + // SearchPrompt is the searchPrompt argument value. + SearchPrompt string + // Defaults is the defaults argument value. + Defaults []string + // PersistentOptions is the persistentOptions argument value. + PersistentOptions []string + // SearchFunc is the searchFunc argument value. + SearchFunc func(string) ([]string, []string, int, error) + } // Password holds details about calls to the Password method. Password []struct { // Prompt is the prompt argument value. @@ -138,15 +157,16 @@ type PrompterMock struct { Options []string } } - lockAuthToken sync.RWMutex - lockConfirm sync.RWMutex - lockConfirmDeletion sync.RWMutex - lockInput sync.RWMutex - lockInputHostname sync.RWMutex - lockMarkdownEditor sync.RWMutex - lockMultiSelect sync.RWMutex - lockPassword sync.RWMutex - lockSelect sync.RWMutex + lockAuthToken sync.RWMutex + lockConfirm sync.RWMutex + lockConfirmDeletion sync.RWMutex + lockInput sync.RWMutex + lockInputHostname sync.RWMutex + lockMarkdownEditor sync.RWMutex + lockMultiSelect sync.RWMutex + lockMultiSelectWithSearch sync.RWMutex + lockPassword sync.RWMutex + lockSelect sync.RWMutex } // AuthToken calls AuthTokenFunc. @@ -387,6 +407,54 @@ func (mock *PrompterMock) MultiSelectCalls() []struct { return calls } +// MultiSelectWithSearch calls MultiSelectWithSearchFunc. +func (mock *PrompterMock) MultiSelectWithSearch(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { + if mock.MultiSelectWithSearchFunc == nil { + panic("PrompterMock.MultiSelectWithSearchFunc: method is nil but Prompter.MultiSelectWithSearch was just called") + } + callInfo := struct { + Prompt string + SearchPrompt string + Defaults []string + PersistentOptions []string + SearchFunc func(string) ([]string, []string, int, error) + }{ + Prompt: prompt, + SearchPrompt: searchPrompt, + Defaults: defaults, + PersistentOptions: persistentOptions, + SearchFunc: searchFunc, + } + mock.lockMultiSelectWithSearch.Lock() + mock.calls.MultiSelectWithSearch = append(mock.calls.MultiSelectWithSearch, callInfo) + mock.lockMultiSelectWithSearch.Unlock() + return mock.MultiSelectWithSearchFunc(prompt, searchPrompt, defaults, persistentOptions, searchFunc) +} + +// MultiSelectWithSearchCalls gets all the calls that were made to MultiSelectWithSearch. +// Check the length with: +// +// len(mockedPrompter.MultiSelectWithSearchCalls()) +func (mock *PrompterMock) MultiSelectWithSearchCalls() []struct { + Prompt string + SearchPrompt string + Defaults []string + PersistentOptions []string + SearchFunc func(string) ([]string, []string, int, error) +} { + var calls []struct { + Prompt string + SearchPrompt string + Defaults []string + PersistentOptions []string + SearchFunc func(string) ([]string, []string, int, error) + } + mock.lockMultiSelectWithSearch.RLock() + calls = mock.calls.MultiSelectWithSearch + mock.lockMultiSelectWithSearch.RUnlock() + return calls +} + // Password calls PasswordFunc. func (mock *PrompterMock) Password(prompt string) (string, error) { if mock.PasswordFunc == nil { diff --git a/internal/prompter/test.go b/internal/prompter/test.go index dfa124fcad6..adaa0db6dad 100644 --- a/internal/prompter/test.go +++ b/internal/prompter/test.go @@ -25,10 +25,11 @@ func NewMockPrompter(t *testing.T) *MockPrompter { type MockPrompter struct { t *testing.T ghPrompter.PrompterMock - authTokenStubs []authTokenStub - confirmDeletionStubs []confirmDeletionStub - inputHostnameStubs []inputHostnameStub - markdownEditorStubs []markdownEditorStub + authTokenStubs []authTokenStub + confirmDeletionStubs []confirmDeletionStub + inputHostnameStubs []inputHostnameStub + markdownEditorStubs []markdownEditorStub + multiSelectWithSearchStubs []multiSelectWithSearchStub } type authTokenStub struct { @@ -49,6 +50,12 @@ type markdownEditorStub struct { fn func(string, string, bool) (string, error) } +type multiSelectWithSearchStub struct { + prompt string + searchPrompt string + fn func(string, string, []string, []string) ([]string, error) +} + func (m *MockPrompter) AuthToken() (string, error) { var s authTokenStub if len(m.authTokenStubs) == 0 { @@ -92,6 +99,16 @@ func (m *MockPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed return s.fn(prompt, defaultValue, blankAllowed) } +func (m *MockPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { + var s multiSelectWithSearchStub + if len(m.multiSelectWithSearchStubs) == 0 { + return nil, NoSuchPromptErr(prompt) + } + s = m.multiSelectWithSearchStubs[0] + m.multiSelectWithSearchStubs = m.multiSelectWithSearchStubs[1:len(m.multiSelectWithSearchStubs)] + return s.fn(prompt, searchPrompt, defaults, persistentOptions) +} + func (m *MockPrompter) RegisterAuthToken(stub func() (string, error)) { m.authTokenStubs = append(m.authTokenStubs, authTokenStub{fn: stub}) } diff --git a/pkg/cmd/pr/shared/survey.go b/pkg/cmd/pr/shared/survey.go index 56885487e22..5197d6acede 100644 --- a/pkg/cmd/pr/shared/survey.go +++ b/pkg/cmd/pr/shared/survey.go @@ -40,6 +40,7 @@ type Prompt interface { MarkdownEditor(prompt string, defaultValue string, blankAllowed bool) (string, error) Confirm(prompt string, defaultValue bool) (bool, error) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) + MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) } func ConfirmIssueSubmission(p Prompt, allowPreview bool, allowMetadata bool) (Action, error) { @@ -207,6 +208,7 @@ func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface // Populate the list of selectable assignees and their default selections. // This logic maps the default assignees from `state` to the corresponding actors or users // so that the correct display names are preselected in the prompt. + // TODO: KW21 This will need to go away since we're going to dynamically load assignees via search. var assignees []string var assigneesDefault []string if state.ActorAssignees { @@ -264,6 +266,9 @@ func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface fmt.Fprintln(io.ErrOut, "warning: no available reviewers") } } + // TODO: KW21 This will need to change to use MultiSelectWithSearch once it's implemented. + // MultiSelectWithSearch will return the selected strings directly instead of indices, + // so the logic here will need to be updated accordingly. if isChosen("Assignees") { if len(assignees) > 0 { selected, err := p.MultiSelect("Assignees", assigneesDefault, assignees) diff --git a/pkg/cmd/preview/prompter/prompter.go b/pkg/cmd/preview/prompter/prompter.go index 5b44a5cbf7e..35ff901d48c 100644 --- a/pkg/cmd/preview/prompter/prompter.go +++ b/pkg/cmd/preview/prompter/prompter.go @@ -2,6 +2,7 @@ package prompter import ( "fmt" + "strings" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/gh" @@ -25,32 +26,35 @@ func NewCmdPrompter(f *cmdutil.Factory, runF func(*prompterOptions) error) *cobr } const ( - selectPrompt = "select" - multiSelectPrompt = "multi-select" - inputPrompt = "input" - passwordPrompt = "password" - confirmPrompt = "confirm" - authTokenPrompt = "auth-token" - confirmDeletionPrompt = "confirm-deletion" - inputHostnamePrompt = "input-hostname" - markdownEditorPrompt = "markdown-editor" + selectPrompt = "select" + multiSelectPrompt = "multi-select" + multiSelectWithSearchPrompt = "multi-select-with-search" + inputPrompt = "input" + passwordPrompt = "password" + confirmPrompt = "confirm" + authTokenPrompt = "auth-token" + confirmDeletionPrompt = "confirm-deletion" + inputHostnamePrompt = "input-hostname" + markdownEditorPrompt = "markdown-editor" ) prompterTypeFuncMap := map[string]func(prompter.Prompter, *iostreams.IOStreams) error{ - selectPrompt: runSelect, - multiSelectPrompt: runMultiSelect, - inputPrompt: runInput, - passwordPrompt: runPassword, - confirmPrompt: runConfirm, - authTokenPrompt: runAuthToken, - confirmDeletionPrompt: runConfirmDeletion, - inputHostnamePrompt: runInputHostname, - markdownEditorPrompt: runMarkdownEditor, + selectPrompt: runSelect, + multiSelectPrompt: runMultiSelect, + multiSelectWithSearchPrompt: runMultiSelectWithSearch, + inputPrompt: runInput, + passwordPrompt: runPassword, + confirmPrompt: runConfirm, + authTokenPrompt: runAuthToken, + confirmDeletionPrompt: runConfirmDeletion, + inputHostnamePrompt: runInputHostname, + markdownEditorPrompt: runMarkdownEditor, } allPromptsOrder := []string{ selectPrompt, multiSelectPrompt, + multiSelectWithSearchPrompt, inputPrompt, passwordPrompt, confirmPrompt, @@ -70,6 +74,7 @@ func NewCmdPrompter(f *cmdutil.Factory, runF func(*prompterOptions) error) *cobr Available prompt types: - select - multi-select + - multi-select-with-search - input - password - confirm @@ -149,6 +154,42 @@ func runMultiSelect(p prompter.Prompter, io *iostreams.IOStreams) error { return nil } +func runMultiSelectWithSearch(p prompter.Prompter, io *iostreams.IOStreams) error { + fmt.Fprintln(io.Out, "Demonstrating Multi Select With Search") + persistentOptions := []string{"persistent-option-1"} + searchFunc := func(input string) ([]string, []string, int, error) { + var searchResultKeys []string + var searchResultLabels []string + + if input == "" { + moreResults := 2 // Indicate that there are more results available + searchResultKeys = []string{"initial-result-1", "initial-result-2"} + searchResultLabels = []string{"Initial Result Label 1", "Initial Result Label 2"} + return searchResultKeys, searchResultLabels, moreResults, nil + } + + // In a real implementation, this function would perform a search based on the input. + // Here, we return a static set of options for demonstration purposes. + moreResults := 0 + searchResultKeys = []string{"search-result-1", "search-result-2"} + searchResultLabels = []string{"Search Result Label 1", "Search Result Label 2"} + return searchResultKeys, searchResultLabels, moreResults, nil + } + + selections, err := p.MultiSelectWithSearch("Select an option", "Search for an option", []string{}, persistentOptions, searchFunc) + if err != nil { + return err + } + + if len(selections) == 0 { + fmt.Fprintln(io.Out, "No options selected.") + return nil + } + + fmt.Fprintf(io.Out, "Selected options: %s\n", strings.Join(selections, ", ")) + return nil +} + func runInput(p prompter.Prompter, io *iostreams.IOStreams) error { fmt.Fprintln(io.Out, "Demonstrating Text Input") text, err := p.Input("Favorite meal?", "Breakfast") From d04317c2737792eb68f16b2a313117f9bebf0ff9 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:43:27 -0700 Subject: [PATCH 15/98] Add dynamic assignee search to PR edit flow Introduces SuggestedAssignableActors API query and wires up a dynamic assignee search function in the PR edit command. Updates Editable and EditPrompter interfaces to support search-based multi-select for assignees, improving the user experience when assigning users to pull requests. --- api/queries_pr.go | 108 ++++++++++++++++++++++++++++++++++ pkg/cmd/pr/edit/edit.go | 36 ++++++++++++ pkg/cmd/pr/shared/editable.go | 56 +++++++++++++----- 3 files changed, 186 insertions(+), 14 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index de98c661f57..d994d3aae06 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -701,6 +701,114 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in return client.REST(repo.RepoHost(), "DELETE", path, buf, nil) } +// SuggestedAssignableActors fetches up to 10 suggested actors for a specific assignable +// (Issue or PullRequest) node ID. `assignableID` is the GraphQL node ID for the Issue/PR. +// If query is empty, the query variable is passed as null to omit filtering. +func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignableID string, query string) ([]AssignableActor, error) { + type responseData struct { + Viewer struct { + ID string + Login string + Name string + } `graphql:"viewer"` + Node struct { + Issue struct { + SuggestedActors struct { + Nodes []struct { + User struct { + ID string + Login string + Name string + TypeName string `graphql:"__typename"` + } `graphql:"... on User"` + Bot struct { + ID string + Login string + TypeName string `graphql:"__typename"` + } `graphql:"... on Bot"` + } + } `graphql:"suggestedActors(first: 10, query: $query)"` + } `graphql:"... on Issue"` + PullRequest struct { + SuggestedActors struct { + Nodes []struct { + User struct { + ID string + Login string + Name string + TypeName string `graphql:"__typename"` + } `graphql:"... on User"` + Bot struct { + ID string + Login string + TypeName string `graphql:"__typename"` + } `graphql:"... on Bot"` + } + } `graphql:"suggestedActors(first: 10, query: $query)"` + } `graphql:"... on PullRequest"` + } `graphql:"node(id: $id)"` + } + + variables := map[string]interface{}{ + "id": githubv4.ID(assignableID), + } + if query != "" { + variables["query"] = githubv4.String(query) + } else { + variables["query"] = (*githubv4.String)(nil) + } + + var result responseData + if err := client.Query(repo.RepoHost(), "SuggestedAssignableActors", &result, variables); err != nil { + return nil, err + } + + var nodes []struct { + User struct { + ID string + Login string + Name string + TypeName string `graphql:"__typename"` + } `graphql:"... on User"` + Bot struct { + ID string + Login string + TypeName string `graphql:"__typename"` + } `graphql:"... on Bot"` + } + + if result.Node.PullRequest.SuggestedActors.Nodes != nil { + nodes = result.Node.PullRequest.SuggestedActors.Nodes + } else if result.Node.Issue.SuggestedActors.Nodes != nil { + nodes = result.Node.Issue.SuggestedActors.Nodes + } + + actors := make([]AssignableActor, 0, len(nodes)+1) // +1 in case we add viewer + viewer := result.Viewer + viewerLogin := viewer.Login + viewerIncluded := false + + for _, n := range nodes { + if n.User.TypeName == "User" && n.User.Login != "" { + actors = append(actors, AssignableUser{id: n.User.ID, login: n.User.Login, name: n.User.Name}) + if query == "" && viewerLogin != "" && n.User.Login == viewerLogin { + viewerIncluded = true + } + } else if n.Bot.TypeName == "Bot" && n.Bot.Login != "" { + actors = append(actors, AssignableBot{id: n.Bot.ID, login: n.Bot.Login}) + if query == "" && viewerLogin != "" && n.Bot.Login == viewerLogin { + viewerIncluded = true + } + } + } + + // When query is blank, append viewer if not already present. + if query == "" && viewerLogin != "" && !viewerIncluded { + actors = append(actors, AssignableUser{id: viewer.ID, login: viewer.Login, name: viewer.Name}) + } + return actors, nil +} + func UpdatePullRequestBranch(client *Client, repo ghrepo.Interface, params githubv4.UpdatePullRequestBranchInput) error { var mutation struct { UpdatePullRequestBranch struct { diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 11cb74b1ffd..f62ca096b8a 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -292,6 +292,10 @@ func editRun(opts *EditOptions) error { apiClient := api.NewClientFromHTTP(httpClient) + // Wire up search functions for assignees and reviewers. + // TODO: Wire up reviewer search func. + editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, pr.ID) + opts.IO.StartProgressIndicator() err = opts.Fetcher.EditableOptionsFetch(apiClient, repo, &editable, opts.Detector.ProjectsV1()) opts.IO.StopProgressIndicator() @@ -331,6 +335,38 @@ func editRun(opts *EditOptions) error { return nil } +func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, assignableID string) func(string) ([]string, []string, int, error) { + searchFunc := func(input string) ([]string, []string, int, error) { + actors, err := api.SuggestedAssignableActors( + apiClient, + repo, + assignableID, + input) + if err != nil { + return nil, nil, 0, err + } + + var logins []string + var displayNames []string + + for _, a := range actors { + if a.Login() != "" { + logins = append(logins, a.Login()) + } else { + continue + } + + if a.DisplayName() != "" { + displayNames = append(displayNames, a.DisplayName()) + } else { + displayNames = append(displayNames, a.Login()) + } + } + return logins, displayNames, 0, nil + } + return searchFunc +} + func updatePullRequest(httpClient *http.Client, repo ghrepo.Interface, id string, number int, editable shared.Editable) error { var wg errgroup.Group wg.Go(func() error { diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index 5cf55ad37e9..c69e5a0b5ac 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -10,15 +10,17 @@ import ( ) type Editable struct { - Title EditableString - Body EditableString - Base EditableString - Reviewers EditableSlice - Assignees EditableAssignees - Labels EditableSlice - Projects EditableProjects - Milestone EditableString - Metadata api.RepoMetadataResult + Title EditableString + Body EditableString + Base EditableString + Reviewers EditableSlice + ReviewerSearchFunc func(string) ([]string, []string, error) + Assignees EditableAssignees + AssigneeSearchFunc func(string) ([]string, []string, int, error) + Labels EditableSlice + Projects EditableProjects + Milestone EditableString + Metadata api.RepoMetadataResult } type EditableString struct { @@ -277,6 +279,7 @@ type EditPrompter interface { Input(string, string) (string, error) MarkdownEditor(string, string, bool) (string, error) MultiSelect(string, []string, []string) ([]int, error) + MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) Confirm(string, bool) (bool, error) } @@ -302,10 +305,24 @@ func EditFieldsSurvey(p EditPrompter, editable *Editable, editorCommand string) } } if editable.Assignees.Edited { - editable.Assignees.Value, err = multiSelectSurvey( - p, "Assignees", editable.Assignees.Default, editable.Assignees.Options) - if err != nil { - return err + if editable.AssigneeSearchFunc != nil { + editable.Assignees.Options = []string{} + editable.Assignees.Value, err = p.MultiSelectWithSearch( + "Assignees", + "Search assignees", + editable.Assignees.DefaultLogins, + // No persistent options required here as teams cannot be assignees. + []string{}, + editable.AssigneeSearchFunc) + if err != nil { + return err + } + } else { + editable.Assignees.Value, err = multiSelectSurvey( + p, "Assignees", editable.Assignees.Default, editable.Assignees.Options) + if err != nil { + return err + } } } if editable.Labels.Edited { @@ -408,10 +425,21 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, teamReviewers = true } } + + fetchAssignees := false + if editable.Assignees.Edited { + // Similar as above, this is likely an interactive flow if no Add/Remove slices are set. + // The addition here is that we also check for an assignee search func because + // if that is set, the prompter will handle dynamic fetching of assignees. + if len(editable.Assignees.Add) == 0 && len(editable.Assignees.Remove) == 0 && editable.AssigneeSearchFunc == nil { + fetchAssignees = true + } + } + input := api.RepoMetadataInput{ Reviewers: editable.Reviewers.Edited, TeamReviewers: teamReviewers, - Assignees: editable.Assignees.Edited, + Assignees: fetchAssignees, ActorAssignees: editable.Assignees.ActorAssignees, Labels: editable.Labels.Edited, ProjectsV1: editable.Projects.Edited && projectV1Support == gh.ProjectsV1Supported, From 7e7f8c6f6e15c280250f292bb19d732eb8075d93 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:19:34 -0700 Subject: [PATCH 16/98] Pass editable to assigneeSearchFunc and update metadata The assigneeSearchFunc now receives the editable struct to update its Metadata.AssignableActors field with suggested assignable actors. This change ensures that the editable struct has the necessary actor metadata for later PR updates. --- pkg/cmd/pr/edit/edit.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index f62ca096b8a..b327500fab4 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -294,7 +294,7 @@ func editRun(opts *EditOptions) error { // Wire up search functions for assignees and reviewers. // TODO: Wire up reviewer search func. - editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, pr.ID) + editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) opts.IO.StartProgressIndicator() err = opts.Fetcher.EditableOptionsFetch(apiClient, repo, &editable, opts.Detector.ProjectsV1()) @@ -335,7 +335,7 @@ func editRun(opts *EditOptions) error { return nil } -func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, assignableID string) func(string) ([]string, []string, int, error) { +func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable *shared.Editable, assignableID string) func(string) ([]string, []string, int, error) { searchFunc := func(input string) ([]string, []string, int, error) { actors, err := api.SuggestedAssignableActors( apiClient, @@ -361,6 +361,10 @@ func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, assignable } else { displayNames = append(displayNames, a.Login()) } + + // Update the assignable actors metadata in the editable struct + // so that updating the PR later can resolve the actor ID. + editable.Metadata.AssignableActors = append(editable.Metadata.AssignableActors, a) } return logins, displayNames, 0, nil } From 38578f7991220aae7829384708edfc9ca4b98201 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:48:58 -0700 Subject: [PATCH 17/98] Add comment describing logger Added a comment explaining how to enable logging in expect-based tests by using expect.WithLogger. This helps developers debug by printing characters read to stdout. --- internal/prompter/accessible_prompter_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/prompter/accessible_prompter_test.go b/internal/prompter/accessible_prompter_test.go index 03baa34b743..f205d042d88 100644 --- a/internal/prompter/accessible_prompter_test.go +++ b/internal/prompter/accessible_prompter_test.go @@ -803,6 +803,8 @@ func newTestVirtualTerminal(t *testing.T) *expect.Console { failOnExpectError(t), failOnSendError(t), expect.WithDefaultTimeout(time.Second), + // Use this logger to debug expect based tests by printing the + // characters being read to stdout. // expect.WithLogger(log.New(os.Stdout, "", 0)), } From d46f42a752e7348b57db708b88e6ea6d78bf08d9 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:07:47 -0700 Subject: [PATCH 18/98] Refactor MultiSelectWithSearch to use result struct Refactored the MultiSelectWithSearch function and related interfaces to use a MultiSelectSearchResult struct instead of multiple return values. This change improves clarity and extensibility of the search function signature, and updates all usages, mocks, and tests accordingly. --- internal/prompter/accessible_prompter_test.go | 81 +++++++++++++------ internal/prompter/prompter.go | 33 +++++--- internal/prompter/prompter_mock.go | 14 ++-- internal/prompter/test.go | 2 +- pkg/cmd/pr/edit/edit.go | 19 ++++- pkg/cmd/pr/shared/editable.go | 5 +- pkg/cmd/pr/shared/survey.go | 3 +- pkg/cmd/preview/prompter/prompter.go | 16 +++- 8 files changed, 122 insertions(+), 51 deletions(-) diff --git a/internal/prompter/accessible_prompter_test.go b/internal/prompter/accessible_prompter_test.go index f205d042d88..20ce5d844ce 100644 --- a/internal/prompter/accessible_prompter_test.go +++ b/internal/prompter/accessible_prompter_test.go @@ -228,26 +228,36 @@ func TestAccessiblePrompter(t *testing.T) { console := newTestVirtualTerminal(t) p := newTestAccessiblePrompter(t, console) persistentOptions := []string{"persistent-option-1"} - searchFunc := func(input string) ([]string, []string, int, error) { - var searchResultKeys []string - var searchResultLabels []string - - // Initial search with no input - if input == "" { - moreResults := 2 - searchResultKeys = []string{"initial-result-1", "initial-result-2"} - searchResultLabels = []string{"Initial Result Label 1", "Initial Result Label 2"} - return searchResultKeys, searchResultLabels, moreResults, nil + searchFunc := func(input string) prompter.MultiSelectSearchResult { + var searchResultKeys []string + var searchResultLabels []string + + // Initial search with no input + if input == "" { + moreResults := 2 + searchResultKeys = []string{"initial-result-1", "initial-result-2"} + searchResultLabels = []string{"Initial Result Label 1", "Initial Result Label 2"} + return prompter.MultiSelectSearchResult{ + Keys: searchResultKeys, + Labels: searchResultLabels, + MoreResults: moreResults, + Err: nil, } + } - // Subsequent search with input - moreResults := 0 - searchResultKeys = []string{"search-result-1", "search-result-2"} - searchResultLabels = []string{"Search Result Label 1", "Search Result Label 2"} - return searchResultKeys, searchResultLabels, moreResults, nil + // Subsequent search with input + moreResults := 0 + searchResultKeys = []string{"search-result-1", "search-result-2"} + searchResultLabels = []string{"Search Result Label 1", "Search Result Label 2"} + return prompter.MultiSelectSearchResult{ + Keys: searchResultKeys, + Labels: searchResultLabels, + MoreResults: moreResults, + Err: nil, } + } - go func() { + go func() { // Wait for prompt to appear _, err := console.ExpectString("Select an option \r\n") require.NoError(t, err) @@ -291,16 +301,26 @@ func TestAccessiblePrompter(t *testing.T) { initialSearchResultKeys := []string{"initial-result-1"} initialSearchResultLabels := []string{"Initial Result Label 1"} defaultOptions := initialSearchResultKeys - searchFunc := func(input string) ([]string, []string, int, error) { + searchFunc := func(input string) prompter.MultiSelectSearchResult { // Initial search with no input if input == "" { moreResults := 2 - return initialSearchResultKeys, initialSearchResultLabels, moreResults, nil + return prompter.MultiSelectSearchResult{ + Keys: initialSearchResultKeys, + Labels: initialSearchResultLabels, + MoreResults: moreResults, + Err: nil, + } } // No search selected, so this should fail the test. t.FailNow() - return nil, nil, 0, nil + return prompter.MultiSelectSearchResult{ + Keys: nil, + Labels: nil, + MoreResults: 0, + Err: nil, + } } go func() { @@ -325,21 +345,36 @@ func TestAccessiblePrompter(t *testing.T) { moreResultKeys := []string{"more-result-1"} moreResultLabels := []string{"More Result Label 1"} - searchFunc := func(input string) ([]string, []string, int, error) { + searchFunc := func(input string) prompter.MultiSelectSearchResult { // Initial search with no input if input == "" { moreResults := 2 - return initialSearchResultKeys, initialSearchResultLabels, moreResults, nil + return prompter.MultiSelectSearchResult{ + Keys: initialSearchResultKeys, + Labels: initialSearchResultLabels, + MoreResults: moreResults, + Err: nil, + } } // Subsequent search with input "more" if input == "more" { - return moreResultKeys, moreResultLabels, 0, nil + return prompter.MultiSelectSearchResult{ + Keys: moreResultKeys, + Labels: moreResultLabels, + MoreResults: 0, + Err: nil, + } } // No other searches expected t.FailNow() - return nil, nil, 0, nil + return prompter.MultiSelectSearchResult{ + Keys: nil, + Labels: nil, + MoreResults: 0, + Err: nil, + } } go func() { diff --git a/internal/prompter/prompter.go b/internal/prompter/prompter.go index dddb49035e3..5fef325d577 100644 --- a/internal/prompter/prompter.go +++ b/internal/prompter/prompter.go @@ -29,7 +29,7 @@ type Prompter interface { // not their indices, since the list of options is dynamic. // The searchFunc args and return values are: func(query) (map[keys]labels, moreResultsCount, searchError) // Where the selected keys are eventually returned by MultiSelectWithSearch and the labels are what is shown to the user in the prompt. - MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) + MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) // Input prompts the user to enter a string value. Input(prompt string, defaultValue string) (string, error) // Password prompts the user to enter a password. @@ -329,7 +329,7 @@ func (p *accessiblePrompter) MarkdownEditor(prompt, defaultValue string, blankAl return text, nil } -func (p *accessiblePrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { +func (p *accessiblePrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc) } @@ -349,11 +349,18 @@ func (p *surveyPrompter) MultiSelect(prompt string, defaultValues, options []str return p.prompter.MultiSelect(prompt, defaultValues, options) } -func (p *surveyPrompter) MultiSelectWithSearch(prompt string, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { +func (p *surveyPrompter) MultiSelectWithSearch(prompt string, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { return multiSelectWithSearch(p, prompt, searchPrompt, defaultValues, persistentValues, searchFunc) } -func multiSelectWithSearch(p Prompter, prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { +type MultiSelectSearchResult struct { + Keys []string + Labels []string + MoreResults int + Err error +} + +func multiSelectWithSearch(p Prompter, prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { selectedOptions := defaultValues // The optionKeyLabels map is used to uniquely identify optionKeyLabels @@ -363,10 +370,13 @@ func multiSelectWithSearch(p Prompter, prompt, searchPrompt string, defaultValue optionKeyLabels[k] = k } - searchResultKeys, searchResultLabels, moreResults, err := searchFunc("") - if err != nil { - return nil, fmt.Errorf("failed to search: %w", err) + searchResult := searchFunc("") + if searchResult.Err != nil { + return nil, fmt.Errorf("failed to search: %w", searchResult.Err) } + searchResultKeys := searchResult.Keys + searchResultLabels := searchResult.Labels + moreResults := searchResult.MoreResults for i, k := range searchResultKeys { optionKeyLabels[k] = searchResultLabels[i] @@ -474,10 +484,13 @@ func multiSelectWithSearch(p Prompter, prompt, searchPrompt string, defaultValue return nil, err } - searchResultKeys, searchResultLabels, moreResults, err = searchFunc(query) - if err != nil { - return nil, err + searchResult := searchFunc(query) + if searchResult.Err != nil { + return nil, searchResult.Err } + searchResultKeys = searchResult.Keys + searchResultLabels = searchResult.Labels + moreResults = searchResult.MoreResults for i, k := range searchResultKeys { optionKeyLabels[k] = searchResultLabels[i] diff --git a/internal/prompter/prompter_mock.go b/internal/prompter/prompter_mock.go index 4543004a07c..fd6492df815 100644 --- a/internal/prompter/prompter_mock.go +++ b/internal/prompter/prompter_mock.go @@ -38,7 +38,7 @@ var _ Prompter = &PrompterMock{} // MultiSelectFunc: func(prompt string, defaults []string, options []string) ([]int, error) { // panic("mock out the MultiSelect method") // }, -// MultiSelectWithSearchFunc: func(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { +// MultiSelectWithSearchFunc: func(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { // panic("mock out the MultiSelectWithSearch method") // }, // PasswordFunc: func(prompt string) (string, error) { @@ -76,7 +76,7 @@ type PrompterMock struct { MultiSelectFunc func(prompt string, defaults []string, options []string) ([]int, error) // MultiSelectWithSearchFunc mocks the MultiSelectWithSearch method. - MultiSelectWithSearchFunc func(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) + MultiSelectWithSearchFunc func(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) // PasswordFunc mocks the Password method. PasswordFunc func(prompt string) (string, error) @@ -140,7 +140,7 @@ type PrompterMock struct { // PersistentOptions is the persistentOptions argument value. PersistentOptions []string // SearchFunc is the searchFunc argument value. - SearchFunc func(string) ([]string, []string, int, error) + SearchFunc func(string) MultiSelectSearchResult } // Password holds details about calls to the Password method. Password []struct { @@ -408,7 +408,7 @@ func (mock *PrompterMock) MultiSelectCalls() []struct { } // MultiSelectWithSearch calls MultiSelectWithSearchFunc. -func (mock *PrompterMock) MultiSelectWithSearch(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { +func (mock *PrompterMock) MultiSelectWithSearch(prompt string, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { if mock.MultiSelectWithSearchFunc == nil { panic("PrompterMock.MultiSelectWithSearchFunc: method is nil but Prompter.MultiSelectWithSearch was just called") } @@ -417,7 +417,7 @@ func (mock *PrompterMock) MultiSelectWithSearch(prompt string, searchPrompt stri SearchPrompt string Defaults []string PersistentOptions []string - SearchFunc func(string) ([]string, []string, int, error) + SearchFunc func(string) MultiSelectSearchResult }{ Prompt: prompt, SearchPrompt: searchPrompt, @@ -440,14 +440,14 @@ func (mock *PrompterMock) MultiSelectWithSearchCalls() []struct { SearchPrompt string Defaults []string PersistentOptions []string - SearchFunc func(string) ([]string, []string, int, error) + SearchFunc func(string) MultiSelectSearchResult } { var calls []struct { Prompt string SearchPrompt string Defaults []string PersistentOptions []string - SearchFunc func(string) ([]string, []string, int, error) + SearchFunc func(string) MultiSelectSearchResult } mock.lockMultiSelectWithSearch.RLock() calls = mock.calls.MultiSelectWithSearch diff --git a/internal/prompter/test.go b/internal/prompter/test.go index adaa0db6dad..8d3f64700b4 100644 --- a/internal/prompter/test.go +++ b/internal/prompter/test.go @@ -99,7 +99,7 @@ func (m *MockPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed return s.fn(prompt, defaultValue, blankAllowed) } -func (m *MockPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) { +func (m *MockPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) { var s multiSelectWithSearchStub if len(m.multiSelectWithSearchStubs) == 0 { return nil, NoSuchPromptErr(prompt) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index b327500fab4..ff683af9824 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -12,6 +12,7 @@ import ( fd "github.com/cli/cli/v2/internal/featuredetection" "github.com/cli/cli/v2/internal/gh" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" shared "github.com/cli/cli/v2/pkg/cmd/pr/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" @@ -335,15 +336,20 @@ func editRun(opts *EditOptions) error { return nil } -func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable *shared.Editable, assignableID string) func(string) ([]string, []string, int, error) { - searchFunc := func(input string) ([]string, []string, int, error) { +func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable *shared.Editable, assignableID string) func(string) prompter.MultiSelectSearchResult { + searchFunc := func(input string) prompter.MultiSelectSearchResult { actors, err := api.SuggestedAssignableActors( apiClient, repo, assignableID, input) if err != nil { - return nil, nil, 0, err + return prompter.MultiSelectSearchResult{ + Keys: nil, + Labels: nil, + MoreResults: 0, + Err: err, + } } var logins []string @@ -366,7 +372,12 @@ func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable * // so that updating the PR later can resolve the actor ID. editable.Metadata.AssignableActors = append(editable.Metadata.AssignableActors, a) } - return logins, displayNames, 0, nil + return prompter.MultiSelectSearchResult{ + Keys: logins, + Labels: displayNames, + MoreResults: 0, + Err: nil, + } } return searchFunc } diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index c69e5a0b5ac..7cfe49d17b5 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -6,6 +6,7 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/gh" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/set" ) @@ -16,7 +17,7 @@ type Editable struct { Reviewers EditableSlice ReviewerSearchFunc func(string) ([]string, []string, error) Assignees EditableAssignees - AssigneeSearchFunc func(string) ([]string, []string, int, error) + AssigneeSearchFunc func(string) prompter.MultiSelectSearchResult Labels EditableSlice Projects EditableProjects Milestone EditableString @@ -279,7 +280,7 @@ type EditPrompter interface { Input(string, string) (string, error) MarkdownEditor(string, string, bool) (string, error) MultiSelect(string, []string, []string) ([]int, error) - MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) + MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) prompter.MultiSelectSearchResult) ([]string, error) Confirm(string, bool) (bool, error) } diff --git a/pkg/cmd/pr/shared/survey.go b/pkg/cmd/pr/shared/survey.go index 5197d6acede..9ba600ae4ba 100644 --- a/pkg/cmd/pr/shared/survey.go +++ b/pkg/cmd/pr/shared/survey.go @@ -9,6 +9,7 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/gh" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/surveyext" @@ -40,7 +41,7 @@ type Prompt interface { MarkdownEditor(prompt string, defaultValue string, blankAllowed bool) (string, error) Confirm(prompt string, defaultValue bool) (bool, error) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) - MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) ([]string, []string, int, error)) ([]string, error) + MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) prompter.MultiSelectSearchResult) ([]string, error) } func ConfirmIssueSubmission(p Prompt, allowPreview bool, allowMetadata bool) (Action, error) { diff --git a/pkg/cmd/preview/prompter/prompter.go b/pkg/cmd/preview/prompter/prompter.go index 35ff901d48c..93dbbe61160 100644 --- a/pkg/cmd/preview/prompter/prompter.go +++ b/pkg/cmd/preview/prompter/prompter.go @@ -157,7 +157,7 @@ func runMultiSelect(p prompter.Prompter, io *iostreams.IOStreams) error { func runMultiSelectWithSearch(p prompter.Prompter, io *iostreams.IOStreams) error { fmt.Fprintln(io.Out, "Demonstrating Multi Select With Search") persistentOptions := []string{"persistent-option-1"} - searchFunc := func(input string) ([]string, []string, int, error) { + searchFunc := func(input string) prompter.MultiSelectSearchResult { var searchResultKeys []string var searchResultLabels []string @@ -165,7 +165,12 @@ func runMultiSelectWithSearch(p prompter.Prompter, io *iostreams.IOStreams) erro moreResults := 2 // Indicate that there are more results available searchResultKeys = []string{"initial-result-1", "initial-result-2"} searchResultLabels = []string{"Initial Result Label 1", "Initial Result Label 2"} - return searchResultKeys, searchResultLabels, moreResults, nil + return prompter.MultiSelectSearchResult{ + Keys: searchResultKeys, + Labels: searchResultLabels, + MoreResults: moreResults, + Err: nil, + } } // In a real implementation, this function would perform a search based on the input. @@ -173,7 +178,12 @@ func runMultiSelectWithSearch(p prompter.Prompter, io *iostreams.IOStreams) erro moreResults := 0 searchResultKeys = []string{"search-result-1", "search-result-2"} searchResultLabels = []string{"Search Result Label 1", "Search Result Label 2"} - return searchResultKeys, searchResultLabels, moreResults, nil + return prompter.MultiSelectSearchResult{ + Keys: searchResultKeys, + Labels: searchResultLabels, + MoreResults: moreResults, + Err: nil, + } } selections, err := p.MultiSelectWithSearch("Select an option", "Search for an option", []string{}, persistentOptions, searchFunc) From 07dfdf97aeb357606c1daed37736ffefd9beb585 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:26:21 -0700 Subject: [PATCH 19/98] Update edit tests Updated test mocks and logic to consistently use lowercase 'monalisa' for login names and display names for user assignees. Improved handling of dynamic assignee fetching in interactive flows by relying on searchFunc and metadata population, and clarified logic in FetchOptions to fetch assignees only when necessary. These changes ensure more accurate simulation of interactive assignment and better test coverage for actor assignee features. --- internal/prompter/accessible_prompter_test.go | 44 +++++++++---------- pkg/cmd/issue/edit/edit_test.go | 2 +- pkg/cmd/pr/edit/edit_test.go | 37 +++++++++------- pkg/cmd/pr/shared/editable.go | 10 ++++- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/internal/prompter/accessible_prompter_test.go b/internal/prompter/accessible_prompter_test.go index 20ce5d844ce..c944ef6cd6d 100644 --- a/internal/prompter/accessible_prompter_test.go +++ b/internal/prompter/accessible_prompter_test.go @@ -228,15 +228,27 @@ func TestAccessiblePrompter(t *testing.T) { console := newTestVirtualTerminal(t) p := newTestAccessiblePrompter(t, console) persistentOptions := []string{"persistent-option-1"} - searchFunc := func(input string) prompter.MultiSelectSearchResult { - var searchResultKeys []string - var searchResultLabels []string - - // Initial search with no input - if input == "" { - moreResults := 2 - searchResultKeys = []string{"initial-result-1", "initial-result-2"} - searchResultLabels = []string{"Initial Result Label 1", "Initial Result Label 2"} + searchFunc := func(input string) prompter.MultiSelectSearchResult { + var searchResultKeys []string + var searchResultLabels []string + + // Initial search with no input + if input == "" { + moreResults := 2 + searchResultKeys = []string{"initial-result-1", "initial-result-2"} + searchResultLabels = []string{"Initial Result Label 1", "Initial Result Label 2"} + return prompter.MultiSelectSearchResult{ + Keys: searchResultKeys, + Labels: searchResultLabels, + MoreResults: moreResults, + Err: nil, + } + } + + // Subsequent search with input + moreResults := 0 + searchResultKeys = []string{"search-result-1", "search-result-2"} + searchResultLabels = []string{"Search Result Label 1", "Search Result Label 2"} return prompter.MultiSelectSearchResult{ Keys: searchResultKeys, Labels: searchResultLabels, @@ -245,19 +257,7 @@ func TestAccessiblePrompter(t *testing.T) { } } - // Subsequent search with input - moreResults := 0 - searchResultKeys = []string{"search-result-1", "search-result-2"} - searchResultLabels = []string{"Search Result Label 1", "Search Result Label 2"} - return prompter.MultiSelectSearchResult{ - Keys: searchResultKeys, - Labels: searchResultLabels, - MoreResults: moreResults, - Err: nil, - } - } - - go func() { + go func() { // Wait for prompt to appear _, err := console.ExpectString("Select an option \r\n") require.NoError(t, err) diff --git a/pkg/cmd/issue/edit/edit_test.go b/pkg/cmd/issue/edit/edit_test.go index 4261efda767..2fcb85c569a 100644 --- a/pkg/cmd/issue/edit/edit_test.go +++ b/pkg/cmd/issue/edit/edit_test.go @@ -845,7 +845,7 @@ func mockRepoMetadata(_ *testing.T, reg *httpmock.Registry) { { "data": { "repository": { "suggestedActors": { "nodes": [ { "login": "hubot", "id": "HUBOTID", "__typename": "Bot" }, - { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name", "__typename": "User" } + { "login": "monalisa", "id": "MONAID", "name": "Mona Display Name", "__typename": "User" } ], "pageInfo": { "hasNextPage": false } } } } } diff --git a/pkg/cmd/pr/edit/edit_test.go b/pkg/cmd/pr/edit/edit_test.go index cd26c1a0a3c..2bd2497f4d4 100644 --- a/pkg/cmd/pr/edit/edit_test.go +++ b/pkg/cmd/pr/edit/edit_test.go @@ -714,7 +714,13 @@ func Test_editRun(t *testing.T) { editFields: func(e *shared.Editable, _ string) error { e.Title.Value = "new title" e.Body.Value = "new body" - e.Assignees.Value = []string{"monalisa", "hubot"} + // When ActorAssignees is enabled, the interactive flow returns display names (or logins for non-users) + e.Assignees.Value = []string{"monalisa (Mona Display Name)", "hubot"} + // Populate metadata to simulate what searchFunc would do during prompting + e.Metadata.AssignableActors = []api.AssignableActor{ + api.NewAssignableBot("HUBOTID", "hubot"), + api.NewAssignableUser("MONAID", "monalisa", "Mona Display Name"), + } e.Labels.Value = []string{"feature", "TODO", "bug"} e.Labels.Add = []string{"feature", "TODO", "bug"} e.Labels.Remove = []string{"docs"} @@ -728,7 +734,8 @@ func Test_editRun(t *testing.T) { }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { // interactive but reviewers not chosen; need everything except reviewers/teams - mockRepoMetadata(reg, mockRepoMetadataOptions{assignees: true, labels: true, projects: true, milestones: true}) + // assignees: false because searchFunc handles dynamic fetching (metadata populated in test mock) + mockRepoMetadata(reg, mockRepoMetadataOptions{assignees: false, labels: true, projects: true, milestones: true}) mockPullRequestUpdate(reg) mockPullRequestUpdateActorAssignees(reg) mockPullRequestUpdateLabels(reg) @@ -822,8 +829,13 @@ func Test_editRun(t *testing.T) { require.Equal(t, []string{"hubot"}, e.Assignees.Default) require.Equal(t, []string{"hubot"}, e.Assignees.DefaultLogins) - // Adding MonaLisa as PR assignee, should preserve hubot. - e.Assignees.Value = []string{"hubot", "MonaLisa (Mona Display Name)"} + // Adding monalisa as PR assignee, should preserve hubot. + e.Assignees.Value = []string{"hubot", "monalisa (Mona Display Name)"} + // Populate metadata to simulate what searchFunc would do during prompting + e.Metadata.AssignableActors = []api.AssignableActor{ + api.NewAssignableBot("HUBOTID", "hubot"), + api.NewAssignableUser("MONAID", "monalisa", "Mona Display Name"), + } return nil }, }, @@ -831,17 +843,8 @@ func Test_editRun(t *testing.T) { EditorRetriever: testEditorRetriever{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(`query RepositoryAssignableActors\b`), - httpmock.StringResponse(` - { "data": { "repository": { "suggestedActors": { - "nodes": [ - { "login": "hubot", "id": "HUBOTID", "__typename": "Bot" }, - { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name", "__typename": "User" } - ], - "pageInfo": { "hasNextPage": false } - } } } } - `)) + // No RepositoryAssignableActors query needed - searchFunc handles dynamic fetching + // (metadata populated in test mock) mockPullRequestUpdate(reg) reg.Register( httpmock.GraphQL(`mutation ReplaceActorsForAssignable\b`), @@ -886,7 +889,7 @@ func Test_editRun(t *testing.T) { { "data": { "repository": { "assignableUsers": { "nodes": [ { "login": "hubot", "id": "HUBOTID" }, - { "login": "MonaLisa", "id": "MONAID" } + { "login": "monalisa", "id": "MONAID" } ], "pageInfo": { "hasNextPage": false } } } } } @@ -1001,7 +1004,7 @@ func mockRepoMetadata(reg *httpmock.Registry, opt mockRepoMetadataOptions) { { "data": { "repository": { "suggestedActors": { "nodes": [ { "login": "hubot", "id": "HUBOTID", "__typename": "Bot" }, - { "login": "MonaLisa", "id": "MONAID", "name": "Mona Display Name", "__typename": "User" } + { "login": "monalisa", "id": "MONAID", "name": "Mona Display Name", "__typename": "User" } ], "pageInfo": { "hasNextPage": false } } } } } diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index 7cfe49d17b5..7e6e772198c 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -430,11 +430,17 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, fetchAssignees := false if editable.Assignees.Edited { // Similar as above, this is likely an interactive flow if no Add/Remove slices are set. - // The addition here is that we also check for an assignee search func because - // if that is set, the prompter will handle dynamic fetching of assignees. + // The addition here is that we also check for an assignee search func. + // If we have a search func, we don't need to fetch assignees since we + // assume that will be done dynamically in the prompting flow. if len(editable.Assignees.Add) == 0 && len(editable.Assignees.Remove) == 0 && editable.AssigneeSearchFunc == nil { fetchAssignees = true } + // However, if we have Add/Remove operations (non-interactive flow), + // we do need to fetch the assignees. + if len(editable.Assignees.Add) > 0 || len(editable.Assignees.Remove) > 0 { + fetchAssignees = true + } } input := api.RepoMetadataInput{ From ad8c770013ffddd355d0a4e80705dd92883c396e Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Sun, 11 Jan 2026 19:47:39 -0700 Subject: [PATCH 20/98] Only support assignee searchfunc on GitHub.com --- pkg/cmd/pr/edit/edit.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index ff683af9824..6710224e36d 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -295,7 +295,9 @@ func editRun(opts *EditOptions) error { // Wire up search functions for assignees and reviewers. // TODO: Wire up reviewer search func. - editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) + if issueFeatures.ActorIsAssignable { + editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) + } opts.IO.StartProgressIndicator() err = opts.Fetcher.EditableOptionsFetch(apiClient, repo, &editable, opts.Detector.ProjectsV1()) From c0df49043fe0d3a4c03743723ffd4f7b12b54ffe Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Sun, 11 Jan 2026 20:02:52 -0700 Subject: [PATCH 21/98] Clarify TODO comment for reviewer search function Updated the TODO comment to specify wiring up the reviewer search function if or when it exists, providing clearer intent for future development. --- pkg/cmd/pr/edit/edit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 6710224e36d..0ced8b58cb5 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -294,7 +294,7 @@ func editRun(opts *EditOptions) error { apiClient := api.NewClientFromHTTP(httpClient) // Wire up search functions for assignees and reviewers. - // TODO: Wire up reviewer search func. + // TODO KW: Wire up reviewer search func if/when it exists. if issueFeatures.ActorIsAssignable { editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) } From f6a09a3e5cf45872835f3c24543bd2f3b658b705 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:23:51 -0700 Subject: [PATCH 22/98] Apply suggestions from code review Co-authored-by: Babak K. Shandiz --- api/queries_pr.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index d994d3aae06..962999f09c7 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -715,16 +715,15 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable Issue struct { SuggestedActors struct { Nodes []struct { + TypeName string `graphql:"__typename"` User struct { ID string Login string Name string - TypeName string `graphql:"__typename"` } `graphql:"... on User"` Bot struct { ID string Login string - TypeName string `graphql:"__typename"` } `graphql:"... on Bot"` } } `graphql:"suggestedActors(first: 10, query: $query)"` @@ -732,16 +731,15 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable PullRequest struct { SuggestedActors struct { Nodes []struct { + TypeName string `graphql:"__typename"` User struct { ID string Login string - Name string - TypeName string `graphql:"__typename"` + Name string } `graphql:"... on User"` Bot struct { ID string Login string - TypeName string `graphql:"__typename"` } `graphql:"... on Bot"` } } `graphql:"suggestedActors(first: 10, query: $query)"` @@ -764,16 +762,15 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable } var nodes []struct { + TypeName string `graphql:"__typename"` User struct { ID string Login string Name string - TypeName string `graphql:"__typename"` } `graphql:"... on User"` Bot struct { ID string Login string - TypeName string `graphql:"__typename"` } `graphql:"... on Bot"` } @@ -789,12 +786,12 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable viewerIncluded := false for _, n := range nodes { - if n.User.TypeName == "User" && n.User.Login != "" { + if n.TypeName == "User" && n.User.Login != "" { actors = append(actors, AssignableUser{id: n.User.ID, login: n.User.Login, name: n.User.Name}) if query == "" && viewerLogin != "" && n.User.Login == viewerLogin { viewerIncluded = true } - } else if n.Bot.TypeName == "Bot" && n.Bot.Login != "" { + } else if n.TypeName == "Bot" && n.Bot.Login != "" { actors = append(actors, AssignableBot{id: n.Bot.ID, login: n.Bot.Login}) if query == "" && viewerLogin != "" && n.Bot.Login == viewerLogin { viewerIncluded = true From 30cfbd9fdd5ef80ed89530e2e68c712674ee4999 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:38:27 -0700 Subject: [PATCH 23/98] Apply suggestions from code review Co-authored-by: Babak K. Shandiz --- pkg/cmd/pr/edit/edit.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 0ced8b58cb5..37ece7cfd17 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -354,8 +354,8 @@ func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable * } } - var logins []string - var displayNames []string + logins := make([]string, 0, len(actors)) + displayNames := make([]string, 0, len(actors)) for _, a := range actors { if a.Login() != "" { From 38f9d7891bbf6705dafc07ecd10ecbd25634e8f9 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:14:32 -0700 Subject: [PATCH 24/98] Fix linter and mock prompter signature --- api/queries_pr.go | 40 +++++++++++++++++++-------------------- internal/prompter/test.go | 6 ++---- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 962999f09c7..6f0b65d672d 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -715,15 +715,15 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable Issue struct { SuggestedActors struct { Nodes []struct { - TypeName string `graphql:"__typename"` - User struct { - ID string - Login string - Name string + TypeName string `graphql:"__typename"` + User struct { + ID string + Login string + Name string } `graphql:"... on User"` Bot struct { - ID string - Login string + ID string + Login string } `graphql:"... on Bot"` } } `graphql:"suggestedActors(first: 10, query: $query)"` @@ -731,15 +731,15 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable PullRequest struct { SuggestedActors struct { Nodes []struct { - TypeName string `graphql:"__typename"` - User struct { - ID string - Login string - Name string + TypeName string `graphql:"__typename"` + User struct { + ID string + Login string + Name string } `graphql:"... on User"` Bot struct { - ID string - Login string + ID string + Login string } `graphql:"... on Bot"` } } `graphql:"suggestedActors(first: 10, query: $query)"` @@ -763,14 +763,14 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable var nodes []struct { TypeName string `graphql:"__typename"` - User struct { - ID string - Login string - Name string + User struct { + ID string + Login string + Name string } `graphql:"... on User"` Bot struct { - ID string - Login string + ID string + Login string } `graphql:"... on Bot"` } diff --git a/internal/prompter/test.go b/internal/prompter/test.go index 8d3f64700b4..599fd389358 100644 --- a/internal/prompter/test.go +++ b/internal/prompter/test.go @@ -51,9 +51,7 @@ type markdownEditorStub struct { } type multiSelectWithSearchStub struct { - prompt string - searchPrompt string - fn func(string, string, []string, []string) ([]string, error) + fn func(string, string, []string, []string, func(string) MultiSelectSearchResult) ([]string, error) } func (m *MockPrompter) AuthToken() (string, error) { @@ -106,7 +104,7 @@ func (m *MockPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaul } s = m.multiSelectWithSearchStubs[0] m.multiSelectWithSearchStubs = m.multiSelectWithSearchStubs[1:len(m.multiSelectWithSearchStubs)] - return s.fn(prompt, searchPrompt, defaults, persistentOptions) + return s.fn(prompt, searchPrompt, defaults, persistentOptions, searchFunc) } func (m *MockPrompter) RegisterAuthToken(stub func() (string, error)) { From 346bd8c0020f95aaa3df051939b9d1e874a76dd7 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:33:11 -0700 Subject: [PATCH 25/98] Simplify suggested assignable actors Simplifies SuggestedAssignableActors by no longer including the viewer in the returned actors list when the query is blank. Removes related logic and variables for viewer handling. --- api/queries_pr.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 6f0b65d672d..11502071d04 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -780,29 +780,16 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable nodes = result.Node.Issue.SuggestedActors.Nodes } - actors := make([]AssignableActor, 0, len(nodes)+1) // +1 in case we add viewer - viewer := result.Viewer - viewerLogin := viewer.Login - viewerIncluded := false + actors := make([]AssignableActor, 0, len(nodes)) for _, n := range nodes { if n.TypeName == "User" && n.User.Login != "" { actors = append(actors, AssignableUser{id: n.User.ID, login: n.User.Login, name: n.User.Name}) - if query == "" && viewerLogin != "" && n.User.Login == viewerLogin { - viewerIncluded = true - } } else if n.TypeName == "Bot" && n.Bot.Login != "" { actors = append(actors, AssignableBot{id: n.Bot.ID, login: n.Bot.Login}) - if query == "" && viewerLogin != "" && n.Bot.Login == viewerLogin { - viewerIncluded = true - } } } - // When query is blank, append viewer if not already present. - if query == "" && viewerLogin != "" && !viewerIncluded { - actors = append(actors, AssignableUser{id: viewer.ID, login: viewer.Login, name: viewer.Name}) - } return actors, nil } From dc105ce7a462eed026bf65533700ac28fea4ead7 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:02:40 -0700 Subject: [PATCH 26/98] Simplify variables map in SuggestedAssignableActors Refactored the construction of the variables map by directly assigning the 'query' key, removing the conditional logic for nil assignment. --- api/queries_pr.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 11502071d04..077d316e990 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -748,12 +748,8 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable } variables := map[string]interface{}{ - "id": githubv4.ID(assignableID), - } - if query != "" { - variables["query"] = githubv4.String(query) - } else { - variables["query"] = (*githubv4.String)(nil) + "id": githubv4.ID(assignableID), + "query": githubv4.String(query), } var result responseData From e3a3a01f2db9be1787aee7b04c241792ba437164 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:20:40 -0700 Subject: [PATCH 27/98] Add comments to assigneeSearchFunc for clarity Added detailed comments to the assigneeSearchFunc explaining its purpose and the importance of updating assignable actors metadata for later ID resolution when mutating assignees with the GraphQL API. --- pkg/cmd/pr/edit/edit.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 37ece7cfd17..2531b7f857a 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -338,6 +338,11 @@ func editRun(opts *EditOptions) error { return nil } +// assigneeSearchFunc is intended to be an arg for MultiSelectWithSearch +// to return potential assignee actors. +// It also contains an important enclosure to update the editable's +// assignable actors metadata for later ID resolution - this is required +// while we continue to use IDs for mutating assignees with the GQL API. func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable *shared.Editable, assignableID string) func(string) prompter.MultiSelectSearchResult { searchFunc := func(input string) prompter.MultiSelectSearchResult { actors, err := api.SuggestedAssignableActors( From 48bea46504fabd0b2152c2cd1ad118748081b369 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:37:39 -0700 Subject: [PATCH 28/98] Remove unused Viewer struct from SuggestedAssignableActors Deleted the Viewer struct from the responseData type in SuggestedAssignableActors as it was not being used. --- api/queries_pr.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 077d316e990..d2915fee856 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -706,11 +706,6 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in // If query is empty, the query variable is passed as null to omit filtering. func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignableID string, query string) ([]AssignableActor, error) { type responseData struct { - Viewer struct { - ID string - Login string - Name string - } `graphql:"viewer"` Node struct { Issue struct { SuggestedActors struct { From fb031b2b43123fcf2f6eb8f123c4eea7f421a1f4 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:48:03 -0700 Subject: [PATCH 29/98] Add test for legacy assignee flow on GHES Introduces a test case to verify that the interactive edit flow on GitHub Enterprise Server uses the legacy assignee selection without search, ensuring correct behavior when editing pull request assignees. --- pkg/cmd/pr/edit/edit_test.go | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pkg/cmd/pr/edit/edit_test.go b/pkg/cmd/pr/edit/edit_test.go index 2bd2497f4d4..97a7d4cb400 100644 --- a/pkg/cmd/pr/edit/edit_test.go +++ b/pkg/cmd/pr/edit/edit_test.go @@ -898,6 +898,55 @@ func Test_editRun(t *testing.T) { }, stdout: "https://github.com/OWNER/REPO/pull/123\n", }, + { + name: "interactive GHES uses legacy assignee flow without search", + input: &EditOptions{ + Detector: &fd.DisabledDetectorMock{}, + SelectorArg: "123", + Finder: shared.NewMockFinder("123", &api.PullRequest{ + URL: "https://github.com/OWNER/REPO/pull/123", + Assignees: api.Assignees{ + Nodes: []api.GitHubUser{{Login: "octocat", ID: "OCTOID"}}, + TotalCount: 1, + }, + }, ghrepo.New("OWNER", "REPO")), + Interactive: true, + Surveyor: testSurveyor{ + fieldsToEdit: func(e *shared.Editable) error { + e.Assignees.Edited = true + return nil + }, + editFields: func(e *shared.Editable, _ string) error { + require.False(t, e.Assignees.ActorAssignees) + require.Nil(t, e.AssigneeSearchFunc) + require.Contains(t, e.Assignees.Options, "monalisa") + require.Contains(t, e.Assignees.Options, "hubot") + + e.Assignees.Value = []string{"monalisa", "hubot"} + return nil + }, + }, + Fetcher: testFetcher{}, + EditorRetriever: testEditorRetriever{}, + }, + httpStubs: func(t *testing.T, reg *httpmock.Registry) { + reg.Exclude(t, httpmock.GraphQL(`query RepositoryAssignableActors\b`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [ + { "login": "hubot", "id": "HUBOTID" }, + { "login": "monalisa", "id": "MONAID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + reg.Exclude(t, httpmock.GraphQL(`mutation ReplaceActorsForAssignable\b`)) + mockPullRequestUpdate(reg) + }, + stdout: "https://github.com/OWNER/REPO/pull/123\n", + }, { name: "non-interactive projects v1 unsupported doesn't fetch v1 metadata", input: &EditOptions{ From af124cd5d29f3efbfc79331f162956f7705279b1 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:56:47 -0700 Subject: [PATCH 30/98] Add test for MultiSelectWithSearch error propagation Introduces a test case to verify that errors returned from the MultiSelectWithSearch search function are properly propagated to the caller. --- internal/prompter/accessible_prompter_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/internal/prompter/accessible_prompter_test.go b/internal/prompter/accessible_prompter_test.go index c944ef6cd6d..2c26a16a0aa 100644 --- a/internal/prompter/accessible_prompter_test.go +++ b/internal/prompter/accessible_prompter_test.go @@ -420,6 +420,21 @@ func TestAccessiblePrompter(t *testing.T) { assert.Equal(t, expectedValues, multiSelectValues) }) + t.Run("MultiSelectWithSearch - search error propagates", func(t *testing.T) { + console := newTestVirtualTerminal(t) + p := newTestAccessiblePrompter(t, console) + + searchFunc := func(input string) prompter.MultiSelectSearchResult { + return prompter.MultiSelectSearchResult{ + Err: fmt.Errorf("search error"), + } + } + + _, err := p.MultiSelectWithSearch("Select", "Search", []string{}, []string{}, searchFunc) + require.Error(t, err) + require.Contains(t, err.Error(), "search error") + }) + t.Run("Input", func(t *testing.T) { console := newTestVirtualTerminal(t) p := newTestAccessiblePrompter(t, console) From 28e07666f8ce69577d77a29ceb57877738a7fc03 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:28:55 -0700 Subject: [PATCH 31/98] Return total assignee count in SuggestedAssignableActors Updated SuggestedAssignableActors to return the total count of available assignees in the repository. Modified assigneeSearchFunc to use this count to calculate and display the number of additional assignees beyond the current results. --- api/queries_pr.go | 16 +++++++++++++--- pkg/cmd/pr/edit/edit.go | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index d2915fee856..6a37ea0b9b0 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -704,8 +704,14 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in // SuggestedAssignableActors fetches up to 10 suggested actors for a specific assignable // (Issue or PullRequest) node ID. `assignableID` is the GraphQL node ID for the Issue/PR. // If query is empty, the query variable is passed as null to omit filtering. -func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignableID string, query string) ([]AssignableActor, error) { +// Returns the actors, the total count of available assignees in the repo, and an error. +func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignableID string, query string) ([]AssignableActor, int, error) { type responseData struct { + Repository struct { + AssignableUsers struct { + TotalCount int + } + } `graphql:"repository(owner: $owner, name: $name)"` Node struct { Issue struct { SuggestedActors struct { @@ -745,13 +751,17 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable variables := map[string]interface{}{ "id": githubv4.ID(assignableID), "query": githubv4.String(query), + "owner": githubv4.String(repo.RepoOwner()), + "name": githubv4.String(repo.RepoName()), } var result responseData if err := client.Query(repo.RepoHost(), "SuggestedAssignableActors", &result, variables); err != nil { - return nil, err + return nil, 0, err } + availableAssigneesCount := result.Repository.AssignableUsers.TotalCount + var nodes []struct { TypeName string `graphql:"__typename"` User struct { @@ -781,7 +791,7 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable } } - return actors, nil + return actors, availableAssigneesCount, nil } func UpdatePullRequestBranch(client *Client, repo ghrepo.Interface, params githubv4.UpdatePullRequestBranchInput) error { diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 2531b7f857a..d9082496aff 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -345,7 +345,7 @@ func editRun(opts *EditOptions) error { // while we continue to use IDs for mutating assignees with the GQL API. func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable *shared.Editable, assignableID string) func(string) prompter.MultiSelectSearchResult { searchFunc := func(input string) prompter.MultiSelectSearchResult { - actors, err := api.SuggestedAssignableActors( + actors, availableAssigneesCount, err := api.SuggestedAssignableActors( apiClient, repo, assignableID, @@ -382,7 +382,7 @@ func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable * return prompter.MultiSelectSearchResult{ Keys: logins, Labels: displayNames, - MoreResults: 0, + MoreResults: availableAssigneesCount, Err: nil, } } From a8053d6e9357421ef37815ba1d96a44c3d3eaac3 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:56:50 -0700 Subject: [PATCH 32/98] Remove redundant comment in editRun test --- pkg/cmd/pr/edit/edit_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/cmd/pr/edit/edit_test.go b/pkg/cmd/pr/edit/edit_test.go index 97a7d4cb400..56b868a36b8 100644 --- a/pkg/cmd/pr/edit/edit_test.go +++ b/pkg/cmd/pr/edit/edit_test.go @@ -843,8 +843,6 @@ func Test_editRun(t *testing.T) { EditorRetriever: testEditorRetriever{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - // No RepositoryAssignableActors query needed - searchFunc handles dynamic fetching - // (metadata populated in test mock) mockPullRequestUpdate(reg) reg.Register( httpmock.GraphQL(`mutation ReplaceActorsForAssignable\b`), From 968a912a07aa6bf32c70aaba84902ca9bc7baf40 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:04:32 -0700 Subject: [PATCH 33/98] Remove outdated TODO comments in survey.go Cleaned up obsolete TODO comments related to assignee and reviewer selection logic in the MetadataSurvey function. --- pkg/cmd/pr/shared/survey.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/cmd/pr/shared/survey.go b/pkg/cmd/pr/shared/survey.go index 9ba600ae4ba..e350671b947 100644 --- a/pkg/cmd/pr/shared/survey.go +++ b/pkg/cmd/pr/shared/survey.go @@ -209,7 +209,6 @@ func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface // Populate the list of selectable assignees and their default selections. // This logic maps the default assignees from `state` to the corresponding actors or users // so that the correct display names are preselected in the prompt. - // TODO: KW21 This will need to go away since we're going to dynamically load assignees via search. var assignees []string var assigneesDefault []string if state.ActorAssignees { @@ -267,9 +266,6 @@ func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface fmt.Fprintln(io.ErrOut, "warning: no available reviewers") } } - // TODO: KW21 This will need to change to use MultiSelectWithSearch once it's implemented. - // MultiSelectWithSearch will return the selected strings directly instead of indices, - // so the logic here will need to be updated accordingly. if isChosen("Assignees") { if len(assignees) > 0 { selected, err := p.MultiSelect("Assignees", assigneesDefault, assignees) From a33d809c88fac69cb4fc43de9a6422de0d49a178 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:09:16 -0700 Subject: [PATCH 34/98] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/prompter/prompter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/prompter/prompter.go b/internal/prompter/prompter.go index 5fef325d577..2bf49eb5877 100644 --- a/internal/prompter/prompter.go +++ b/internal/prompter/prompter.go @@ -27,8 +27,8 @@ type Prompter interface { // Items passed in persistentOptions are always shown in the list, even when not selected. // Unlike MultiSelect, MultiselectWithSearch returns the selected option strings, // not their indices, since the list of options is dynamic. - // The searchFunc args and return values are: func(query) (map[keys]labels, moreResultsCount, searchError) - // Where the selected keys are eventually returned by MultiSelectWithSearch and the labels are what is shown to the user in the prompt. + // The searchFunc has the signature: func(query string) MultiSelectSearchResult. + // In the returned MultiSelectSearchResult, Keys are the values eventually returned by MultiSelectWithSearch and Labels are what is shown to the user in the prompt. MultiSelectWithSearch(prompt, searchPrompt string, defaults []string, persistentOptions []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) // Input prompts the user to enter a string value. Input(prompt string, defaultValue string) (string, error) From 23e80a9d24a197011e5c8a57d512b3150b46e985 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:09:58 -0700 Subject: [PATCH 35/98] Remove outdated comment in SuggestedAssignableActors Deleted a comment about the query variable being passed as null when empty, as it is no longer relevant or necessary. --- api/queries_pr.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 6a37ea0b9b0..c806d6bb75b 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -703,7 +703,6 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in // SuggestedAssignableActors fetches up to 10 suggested actors for a specific assignable // (Issue or PullRequest) node ID. `assignableID` is the GraphQL node ID for the Issue/PR. -// If query is empty, the query variable is passed as null to omit filtering. // Returns the actors, the total count of available assignees in the repo, and an error. func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignableID string, query string) ([]AssignableActor, int, error) { type responseData struct { From fdc72751a71abbeeaf6132bf3ec7fa34de3ec731 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:01:36 +0000 Subject: [PATCH 36/98] chore(deps): bump actions/attest-build-provenance from 3.1.0 to 3.2.0 Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8...96278af6caaf10aea03fd8d33a09a777ca52d62f) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-version: 3.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 8728bc5ec04..0470a6644c1 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -334,7 +334,7 @@ jobs: rpmsign --addsign dist/*.rpm - name: Attest release artifacts if: inputs.environment == 'production' - uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 + uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0 with: subject-path: "dist/gh_*" create-storage-record: false # (default: true) From 738b82ddab008e824a962351a7cf18ac2a44058a Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:21:22 -0700 Subject: [PATCH 37/98] Add CCR and reviewer MultiSelectWithSearch Enables Copilot to be requested as a pull request reviewer, supporting both interactive and non-interactive flows. Introduces new reviewer search functionality, updates reviewer partitioning to distinguish users, bots, and teams, and adds a GraphQL mutation for reviewer management on github.com. Updates help text, tests, and internal APIs to support Copilot reviewer login and display names, while maintaining compatibility with GitHub Enterprise Server. --- api/queries_pr.go | 305 ++++++++++++++++++++++++++++++- api/queries_repo.go | 8 +- api/query_builder.go | 4 + pkg/cmd/pr/edit/edit.go | 141 +++++++++++--- pkg/cmd/pr/edit/edit_test.go | 172 +++++++++++++---- pkg/cmd/pr/shared/editable.go | 58 ++++-- pkg/cmd/pr/shared/params.go | 17 +- pkg/cmd/pr/shared/params_test.go | 31 ++++ 8 files changed, 658 insertions(+), 78 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index c806d6bb75b..f006c529c62 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/cli/cli/v2/internal/ghrepo" @@ -323,6 +324,19 @@ func (r RequestedReviewer) LoginOrSlug() string { return r.Login } +// DisplayName returns a user-friendly name for the reviewer. +// For Copilot bot, returns "Copilot (AI)". For teams, returns "org/slug". +// For users, returns login (could be extended to show name if available). +func (r RequestedReviewer) DisplayName() string { + if r.TypeName == teamTypeName { + return fmt.Sprintf("%s/%s", r.Organization.Login, r.Slug) + } + if r.TypeName == "Bot" && r.Login == CopilotReviewerLogin { + return "Copilot (AI)" + } + return r.Login +} + const teamTypeName = "Team" func (r ReviewRequests) Logins() []string { @@ -333,6 +347,15 @@ func (r ReviewRequests) Logins() []string { return logins } +// DisplayNames returns user-friendly display names for all requested reviewers. +func (r ReviewRequests) DisplayNames() []string { + names := make([]string, len(r.Nodes)) + for i, r := range r.Nodes { + names[i] = r.RequestedReviewer.DisplayName() + } + return names +} + func (pr PullRequest) HeadLabel() string { if pr.IsCrossRepository { return fmt.Sprintf("%s:%s", pr.HeadRepositoryOwner.Login, pr.HeadRefName) @@ -632,6 +655,7 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter } // AddPullRequestReviews adds the given user and team reviewers to a pull request using the REST API. +// Team identifiers can be in "org/slug" format. func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, users, teams []string) error { if len(users) == 0 && len(teams) == 0 { return nil @@ -641,8 +665,15 @@ func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, if users == nil { users = []string{} } - if teams == nil { - teams = []string{} + + // Extract just the slug from org/slug format + teamSlugs := make([]string, 0, len(teams)) + for _, t := range teams { + if idx := strings.Index(t, "/"); idx >= 0 { + teamSlugs = append(teamSlugs, t[idx+1:]) + } else if t != "" { + teamSlugs = append(teamSlugs, t) + } } path := fmt.Sprintf( @@ -656,7 +687,7 @@ func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, TeamReviewers []string `json:"team_reviewers"` }{ Reviewers: users, - TeamReviewers: teams, + TeamReviewers: teamSlugs, } buf := &bytes.Buffer{} if err := json.NewEncoder(buf).Encode(body); err != nil { @@ -667,6 +698,7 @@ func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, } // RemovePullRequestReviews removes requested reviewers from a pull request using the REST API. +// Team identifiers can be in "org/slug" format. func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, users, teams []string) error { if len(users) == 0 && len(teams) == 0 { return nil @@ -676,8 +708,15 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in if users == nil { users = []string{} } - if teams == nil { - teams = []string{} + + // Extract just the slug from org/slug format + teamSlugs := make([]string, 0, len(teams)) + for _, t := range teams { + if idx := strings.Index(t, "/"); idx >= 0 { + teamSlugs = append(teamSlugs, t[idx+1:]) + } else if t != "" { + teamSlugs = append(teamSlugs, t) + } } path := fmt.Sprintf( @@ -691,7 +730,7 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in TeamReviewers []string `json:"team_reviewers"` }{ Reviewers: users, - TeamReviewers: teams, + TeamReviewers: teamSlugs, } buf := &bytes.Buffer{} if err := json.NewEncoder(buf).Encode(body); err != nil { @@ -701,6 +740,70 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in return client.REST(repo.RepoHost(), "DELETE", path, buf, nil) } +// RequestReviewsByLogin sets requested reviewers on a pull request using the GraphQL mutation. +// This mutation replaces existing reviewers with the provided set unless union is true. +// Only available on github.com, not GHES. +// Bot logins should include the [bot] suffix (e.g., "copilot-pull-request-reviewer[bot]"). +// Team slugs should be in the format "org/team-slug". +// When union is false (replace mode), passing empty slices will remove all reviewers. +func RequestReviewsByLogin(client *Client, repo ghrepo.Interface, prID string, userLogins, botLogins, teamSlugs []string, union bool) error { + // In union mode (additive), nothing to do if all lists are empty. + // In replace mode, we may still need to call the mutation to clear reviewers. + if union && len(userLogins) == 0 && len(botLogins) == 0 && len(teamSlugs) == 0 { + return nil + } + + var mutation struct { + RequestReviewsByLogin struct { + ClientMutationID string + } `graphql:"requestReviewsByLogin(input: $input)"` + } + + type RequestReviewsByLoginInput struct { + PullRequestID githubv4.ID `json:"pullRequestId"` + UserLogins *[]githubv4.String `json:"userLogins,omitempty"` + BotLogins *[]githubv4.String `json:"botLogins,omitempty"` + TeamSlugs *[]githubv4.String `json:"teamSlugs,omitempty"` + Union githubv4.Boolean `json:"union"` + } + + input := RequestReviewsByLoginInput{ + PullRequestID: githubv4.ID(prID), + Union: githubv4.Boolean(union), + } + + if len(userLogins) > 0 { + logins := make([]githubv4.String, len(userLogins)) + for i, l := range userLogins { + logins[i] = githubv4.String(l) + } + input.UserLogins = &logins + } + + if len(botLogins) > 0 { + logins := make([]githubv4.String, len(botLogins)) + for i, l := range botLogins { + // Bot logins require the [bot] suffix for the mutation + logins[i] = githubv4.String(l + "[bot]") + } + input.BotLogins = &logins + } + + if len(teamSlugs) > 0 { + slugs := make([]githubv4.String, len(teamSlugs)) + for i, s := range teamSlugs { + slugs[i] = githubv4.String(s) + } + input.TeamSlugs = &slugs + } + + variables := map[string]interface{}{ + "input": input, + } + + return client.Mutate(repo.RepoHost(), "RequestReviewsByLogin", &mutation, variables) +} + // SuggestedAssignableActors fetches up to 10 suggested actors for a specific assignable // (Issue or PullRequest) node ID. `assignableID` is the GraphQL node ID for the Issue/PR. // Returns the actors, the total count of available assignees in the repo, and an error. @@ -793,6 +896,196 @@ func SuggestedAssignableActors(client *Client, repo ghrepo.Interface, assignable return actors, availableAssigneesCount, nil } +// ReviewerCandidate represents a potential reviewer for a pull request. +// This can be a User, Bot, or Team. +type ReviewerCandidate interface { + DisplayName() string + Login() string + + sealedReviewerCandidate() +} + +// ReviewerUser is a user who can review a pull request. +type ReviewerUser struct { + AssignableUser +} + +func NewReviewerUser(login, name string) ReviewerUser { + return ReviewerUser{ + AssignableUser: NewAssignableUser("", login, name), + } +} + +func (r ReviewerUser) sealedReviewerCandidate() {} + +// ReviewerBot is a bot who can review a pull request. +type ReviewerBot struct { + AssignableBot +} + +func NewReviewerBot(login string) ReviewerBot { + return ReviewerBot{ + AssignableBot: NewAssignableBot("", login), + } +} + +func (b ReviewerBot) DisplayName() string { + if b.login == CopilotReviewerLogin { + return fmt.Sprintf("%s (AI)", CopilotActorName) + } + return b.Login() +} + +func (r ReviewerBot) sealedReviewerCandidate() {} + +// ReviewerTeam is a team that can review a pull request. +// The slug is stored as "org/team-slug" format. +type ReviewerTeam struct { + slug string +} + +// NewReviewerTeam creates a new ReviewerTeam with the full "org/slug" format. +func NewReviewerTeam(orgName, teamSlug string) ReviewerTeam { + return ReviewerTeam{slug: fmt.Sprintf("%s/%s", orgName, teamSlug)} +} + +func (r ReviewerTeam) DisplayName() string { + return r.slug +} + +func (r ReviewerTeam) Login() string { + return r.slug +} + +func (r ReviewerTeam) Slug() string { + return r.slug +} + +func (r ReviewerTeam) sealedReviewerCandidate() {} + +// SuggestedReviewerActors fetches suggested reviewers for a pull request. +// It combines results from: +// - suggestedReviewerActors (10 max) - suggested based on activity +// - repository collaborators (10 max) - all collaborators +// - organization teams (10 max for org repos) - all teams (if owner is an org) +// Results are returned in that order with duplicates removed. +// Returns the candidates, a MoreResults count, and an error. +func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, query string) ([]ReviewerCandidate, int, error) { + // Use a single query that includes organization.teams - if the owner is not an org, + // we'll get a "Could not resolve to an Organization" error which we handle gracefully. + type responseData struct { + Node struct { + PullRequest struct { + SuggestedActors struct { + Nodes []struct { + IsAuthor bool + IsCommenter bool + Reviewer struct { + TypeName string `graphql:"__typename"` + User struct { + Login string + Name string + } `graphql:"... on User"` + Bot struct { + Login string + } `graphql:"... on Bot"` + } + } + } `graphql:"suggestedReviewerActors(first: 5, query: $query)"` + } `graphql:"... on PullRequest"` + } `graphql:"node(id: $id)"` + Repository struct { + Collaborators struct { + TotalCount int + Nodes []struct { + Login string + Name string + } + } `graphql:"collaborators(first: 5, query: $query)"` + } `graphql:"repository(owner: $owner, name: $name)"` + Organization struct { + Teams struct { + TotalCount int + Nodes []struct { + Slug string + } + } `graphql:"teams(first: 5, query: $query)"` + } `graphql:"organization(login: $owner)"` + } + + variables := map[string]interface{}{ + "id": githubv4.ID(prID), + "query": githubv4.String(query), + "owner": githubv4.String(repo.RepoOwner()), + "name": githubv4.String(repo.RepoName()), + } + + var result responseData + err := client.Query(repo.RepoHost(), "SuggestedReviewerActors", &result, variables) + // Handle the case where the owner is not an organization - the query still returns + // partial data (repository, node), so we can continue processing. + if err != nil && !strings.Contains(err.Error(), errorResolvingOrganization) { + return nil, 0, err + } + + ownerName := repo.RepoOwner() + seen := make(map[string]bool) + var candidates []ReviewerCandidate + + // Add suggested reviewers first (excluding author) + for _, n := range result.Node.PullRequest.SuggestedActors.Nodes { + if n.IsAuthor { + continue + } + var candidate ReviewerCandidate + if n.Reviewer.TypeName == "User" && n.Reviewer.User.Login != "" { + candidate = NewReviewerUser(n.Reviewer.User.Login, n.Reviewer.User.Name) + } else if n.Reviewer.TypeName == "Bot" && n.Reviewer.Bot.Login != "" { + candidate = NewReviewerBot(n.Reviewer.Bot.Login) + } else { + continue + } + + login := candidate.Login() + if !seen[login] { + seen[login] = true + candidates = append(candidates, candidate) + } + } + + // Add collaborators (deduped against suggested) + for _, c := range result.Repository.Collaborators.Nodes { + if c.Login == "" { + continue + } + candidate := NewReviewerUser(c.Login, c.Name) + login := candidate.Login() + if !seen[login] { + seen[login] = true + candidates = append(candidates, candidate) + } + } + + // Add teams (will be empty if owner is not an org) + for _, t := range result.Organization.Teams.Nodes { + if t.Slug == "" { + continue + } + candidate := NewReviewerTeam(ownerName, t.Slug) + login := candidate.Login() + if !seen[login] { + seen[login] = true + candidates = append(candidates, candidate) + } + } + + // MoreResults is the sum of collaborators and teams total counts + // (teams will be 0 for personal repos) + moreResults := result.Repository.Collaborators.TotalCount + result.Organization.Teams.TotalCount + + return candidates, moreResults, nil +} + func UpdatePullRequestBranch(client *Client, repo ghrepo.Interface, params githubv4.UpdatePullRequestBranchInput) error { var mutation struct { UpdatePullRequestBranch struct { diff --git a/api/queries_repo.go b/api/queries_repo.go index 4dadcbad306..1e244df581f 100644 --- a/api/queries_repo.go +++ b/api/queries_repo.go @@ -1082,8 +1082,10 @@ func RepoProjects(client *Client, repo ghrepo.Interface) ([]RepoProject, error) // Expected login for Copilot when retrieved as an Actor // This is returned from assignable actors and issue/pr assigned actors. -// We use this to check if the actor is Copilot. -const CopilotActorLogin = "copilot-swe-agent" +const CopilotAssigneeLogin = "copilot-swe-agent" + +// Expected login for Copilot when retrieved as a Pull Request Reviewer. +const CopilotReviewerLogin = "copilot-pull-request-reviewer" const CopilotActorName = "Copilot" type AssignableActor interface { @@ -1144,7 +1146,7 @@ func NewAssignableBot(id, login string) AssignableBot { } func (b AssignableBot) DisplayName() string { - if b.login == CopilotActorLogin { + if b.login == CopilotAssigneeLogin { return fmt.Sprintf("%s (AI)", CopilotActorName) } return b.Login() diff --git a/api/query_builder.go b/api/query_builder.go index a2432673b74..c36fee73611 100644 --- a/api/query_builder.go +++ b/api/query_builder.go @@ -94,12 +94,16 @@ var issueClosedByPullRequestsReferences = shortenQuery(` } `) +// prReviewRequests includes ...on Bot to support Copilot as a reviewer on github.com. +// On GHES, Bot is not part of the RequestedReviewer union, but the fragment is +// silently ignored (verified on GHES 3.19). var prReviewRequests = shortenQuery(` reviewRequests(first: 100) { nodes { requestedReviewer { __typename, ...on User{login}, + ...on Bot{login}, ...on Team{ organization{login} name, diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index d9082496aff..3753373ba0d 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -69,13 +69,15 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman - %[1]s@me%[1]s: assign or unassign yourself - %[1]s@copilot%[1]s: assign or unassign Copilot (not supported on GitHub Enterprise Server) - The %[1]s--add-reviewer%[1]s and %[1]s--remove-reviewer%[1]s flags do not support - these special values. + The %[1]s--add-reviewer%[1]s and %[1]s--remove-reviewer%[1]s flags support + the following special value: + - %[1]s@copilot%[1]s: request or remove review from Copilot (not supported on GitHub Enterprise Server) `, "`"), Example: heredoc.Doc(` $ gh pr edit 23 --title "I found a bug" --body "Nothing works" $ gh pr edit 23 --add-label "bug,help wanted" --remove-label "core" $ gh pr edit 23 --add-reviewer monalisa,hubot --remove-reviewer myorg/team-name + $ gh pr edit 23 --add-reviewer "@copilot" $ gh pr edit 23 --add-assignee "@me" --remove-assignee monalisa,hubot $ gh pr edit 23 --add-assignee "@copilot" $ gh pr edit 23 --add-project "Roadmap" --remove-project v1,v2 @@ -188,8 +190,8 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman cmd.Flags().StringVarP(&opts.Editable.Body.Value, "body", "b", "", "Set the new body.") cmd.Flags().StringVarP(&bodyFile, "body-file", "F", "", "Read body text from `file` (use \"-\" to read from standard input)") cmd.Flags().StringVarP(&opts.Editable.Base.Value, "base", "B", "", "Change the base `branch` for this pull request") - cmd.Flags().StringSliceVar(&opts.Editable.Reviewers.Add, "add-reviewer", nil, "Add reviewers by their `login`.") - cmd.Flags().StringSliceVar(&opts.Editable.Reviewers.Remove, "remove-reviewer", nil, "Remove reviewers by their `login`.") + cmd.Flags().StringSliceVar(&opts.Editable.Reviewers.Add, "add-reviewer", nil, "Add reviewers by their `login`. Use \"@copilot\" to request review from Copilot.") + cmd.Flags().StringSliceVar(&opts.Editable.Reviewers.Remove, "remove-reviewer", nil, "Remove reviewers by their `login`. Use \"@copilot\" to remove review request from Copilot.") cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Add, "add-assignee", nil, "Add assigned users by their `login`. Use \"@me\" to assign yourself, or \"@copilot\" to assign Copilot.") cmd.Flags().StringSliceVar(&opts.Editable.Assignees.Remove, "remove-assignee", nil, "Remove assigned users by their `login`. Use \"@me\" to unassign yourself, or \"@copilot\" to unassign Copilot.") cmd.Flags().StringSliceVar(&opts.Editable.Labels.Add, "add-label", nil, "Add labels by `name`") @@ -265,7 +267,8 @@ func editRun(opts *EditOptions) error { editable.Title.Default = pr.Title editable.Body.Default = pr.Body editable.Base.Default = pr.BaseRefName - editable.Reviewers.Default = pr.ReviewRequests.Logins() + editable.Reviewers.Default = pr.ReviewRequests.DisplayNames() + editable.Reviewers.DefaultLogins = pr.ReviewRequests.Logins() if issueFeatures.ActorIsAssignable { editable.Assignees.ActorAssignees = true editable.Assignees.Default = pr.AssignedActors.DisplayNames() @@ -294,9 +297,10 @@ func editRun(opts *EditOptions) error { apiClient := api.NewClientFromHTTP(httpClient) // Wire up search functions for assignees and reviewers. - // TODO KW: Wire up reviewer search func if/when it exists. + // Only enabled on github.com (ActorIsAssignable is false on GHES). if issueFeatures.ActorIsAssignable { editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) + editable.ReviewerSearchFunc = reviewerSearchFunc(apiClient, repo, &editable, pr.ID) } opts.IO.StartProgressIndicator() @@ -389,6 +393,51 @@ func assigneeSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable * return searchFunc } +// reviewerSearchFunc is intended to be an arg for MultiSelectWithSearch +// to return potential reviewer candidates (users, bots, and teams). +// It also updates the editable's metadata for later ID resolution. +func reviewerSearchFunc(apiClient *api.Client, repo ghrepo.Interface, editable *shared.Editable, prID string) func(string) prompter.MultiSelectSearchResult { + searchFunc := func(input string) prompter.MultiSelectSearchResult { + candidates, moreResults, err := api.SuggestedReviewerActors( + apiClient, + repo, + prID, + input) + if err != nil { + return prompter.MultiSelectSearchResult{ + Keys: nil, + Labels: nil, + MoreResults: 0, + Err: err, + } + } + + keys := make([]string, 0, len(candidates)) + labels := make([]string, 0, len(candidates)) + + for _, c := range candidates { + keys = append(keys, c.Login()) + labels = append(labels, c.DisplayName()) + + // Update the teams metadata in the editable struct + // so that updating the PR later can resolve the team ID. + if team, ok := c.(api.ReviewerTeam); ok { + editable.Metadata.Teams = append(editable.Metadata.Teams, api.OrgTeam{ + ID: "", // ID not needed for REST API reviewer mutations + Slug: team.Slug(), + }) + } + } + return prompter.MultiSelectSearchResult{ + Keys: keys, + Labels: labels, + MoreResults: moreResults, + Err: nil, + } + } + return searchFunc +} + func updatePullRequest(httpClient *http.Client, repo ghrepo.Interface, id string, number int, editable shared.Editable) error { var wg errgroup.Group wg.Go(func() error { @@ -396,44 +445,82 @@ func updatePullRequest(httpClient *http.Client, repo ghrepo.Interface, id string }) if editable.Reviewers.Edited { wg.Go(func() error { - return updatePullRequestReviews(httpClient, repo, number, editable) + return updatePullRequestReviews(httpClient, repo, id, number, editable) }) } return wg.Wait() } -func updatePullRequestReviews(httpClient *http.Client, repo ghrepo.Interface, number int, editable shared.Editable) error { +func updatePullRequestReviews(httpClient *http.Client, repo ghrepo.Interface, prID string, number int, editable shared.Editable) error { if !editable.Reviewers.Edited { return nil } + client := api.NewClientFromHTTP(httpClient) + // Rebuild the Value slice from non-interactive flag input. if len(editable.Reviewers.Add) != 0 || len(editable.Reviewers.Remove) != 0 { + add := editable.Reviewers.Add + remove := editable.Reviewers.Remove + + // Replace @copilot with the Copilot reviewer login (only on github.com). + // Also use DefaultLogins (not Default display names) for computing the set. + var defaultLogins []string + if editable.Assignees.ActorAssignees { + copilotReplacer := shared.NewCopilotReviewerReplacer() + add = copilotReplacer.ReplaceSlice(add) + remove = copilotReplacer.ReplaceSlice(remove) + defaultLogins = editable.Reviewers.DefaultLogins + } else { + // On GHES, Default already contains logins (no display name distinction) + defaultLogins = editable.Reviewers.Default + } + s := set.NewStringSet() - s.AddValues(editable.Reviewers.Add) - s.AddValues(editable.Reviewers.Default) - s.RemoveValues(editable.Reviewers.Remove) + s.AddValues(add) + s.AddValues(defaultLogins) + s.RemoveValues(remove) editable.Reviewers.Value = s.ToSlice() } - addUsers, addTeams := partitionUsersAndTeams(editable.Reviewers.Value) + // On github.com, use the new GraphQL mutation which supports bots. + // On GHES, fall back to REST API. + if editable.Assignees.ActorAssignees { + return updatePullRequestReviewsGraphQL(client, repo, prID, editable) + } + return updatePullRequestReviewsREST(client, repo, number, editable) +} + +// updatePullRequestReviewsGraphQL uses the RequestReviewsByLogin mutation. +// This mutation replaces the entire reviewer set (union: false). +func updatePullRequestReviewsGraphQL(client *api.Client, repo ghrepo.Interface, prID string, editable shared.Editable) error { + users, bots, teams := partitionReviewersByType(editable.Reviewers.Value) + return api.RequestReviewsByLogin(client, repo, prID, users, bots, teams, false) +} - // Reviewers in Default but not in the Value have been removed interactively. +// updatePullRequestReviewsREST uses the REST API to add/remove reviewers. +// This is the legacy path for GHES compatibility. +func updatePullRequestReviewsREST(client *api.Client, repo ghrepo.Interface, number int, editable shared.Editable) error { + addUsers, addBots, addTeams := partitionReviewersByType(editable.Reviewers.Value) + // REST API doesn't distinguish bots from users, so we need to combine them. + allAddUsers := append(addUsers, addBots...) + + // Reviewers in Default but not in Value have been removed interactively. var toRemove []string for _, r := range editable.Reviewers.Default { if !slices.Contains(editable.Reviewers.Value, r) { toRemove = append(toRemove, r) } } - removeUsers, removeTeams := partitionUsersAndTeams(toRemove) + removeUsers, removeBots, removeTeams := partitionReviewersByType(toRemove) + allRemoveUsers := append(removeUsers, removeBots...) - client := api.NewClientFromHTTP(httpClient) wg := errgroup.Group{} wg.Go(func() error { - return api.AddPullRequestReviews(client, repo, number, addUsers, addTeams) + return api.AddPullRequestReviews(client, repo, number, allAddUsers, addTeams) }) wg.Go(func() error { - return api.RemovePullRequestReviews(client, repo, number, removeUsers, removeTeams) + return api.RemovePullRequestReviews(client, repo, number, allRemoveUsers, removeTeams) }) return wg.Wait() } @@ -477,16 +564,20 @@ func (e editorRetriever) Retrieve() (string, error) { return cmdutil.DetermineEditor(e.config) } -// partitionUsersAndTeams splits reviewer identifiers into user logins and team slugs. -// Team identifiers are in the form "org/slug"; only the slug portion is returned for teams. -func partitionUsersAndTeams(values []string) (users []string, teams []string) { +// partitionReviewersByType splits reviewer identifiers into users, bots, and teams. +// Team identifiers are in the form "org/slug" and are returned as-is. +// Bot logins (currently only Copilot) are identified and returned separately. +func partitionReviewersByType(values []string) (users []string, bots []string, teams []string) { for _, v := range values { + if v == "" { + continue + } if strings.ContainsRune(v, '/') { - parts := strings.SplitN(v, "/", 2) - if len(parts) == 2 && parts[1] != "" { - teams = append(teams, parts[1]) - } - } else if v != "" { + // Team: org/slug format, pass as-is + teams = append(teams, v) + } else if v == api.CopilotReviewerLogin { + bots = append(bots, v) + } else { users = append(users, v) } } diff --git a/pkg/cmd/pr/edit/edit_test.go b/pkg/cmd/pr/edit/edit_test.go index 56b868a36b8..ddb16631772 100644 --- a/pkg/cmd/pr/edit/edit_test.go +++ b/pkg/cmd/pr/edit/edit_test.go @@ -150,10 +150,10 @@ func TestNewCmdEdit(t *testing.T) { output: EditOptions{ SelectorArg: "23", Editable: shared.Editable{ - Reviewers: shared.EditableSlice{ + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{ Add: []string{"monalisa", "owner/core"}, Edited: true, - }, + }}, }, }, wantsErr: false, @@ -164,10 +164,10 @@ func TestNewCmdEdit(t *testing.T) { output: EditOptions{ SelectorArg: "23", Editable: shared.Editable{ - Reviewers: shared.EditableSlice{ + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{ Remove: []string{"monalisa", "owner/core"}, Edited: true, - }, + }}, }, }, wantsErr: false, @@ -381,11 +381,11 @@ func Test_editRun(t *testing.T) { Value: "base-branch-name", Edited: true, }, - Reviewers: shared.EditableSlice{ + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{ Add: []string{"OWNER/core", "OWNER/external", "monalisa", "hubot"}, Remove: []string{"dependabot"}, Edited: true, - }, + }}, Assignees: shared.EditableAssignees{ EditableSlice: shared.EditableSlice{ Add: []string{"monalisa", "hubot"}, @@ -413,10 +413,12 @@ func Test_editRun(t *testing.T) { Fetcher: testFetcher{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: false, assignees: true, labels: true, projects: true, milestones: true}) + // Non-interactive with Add/Remove doesn't need reviewers/assignees metadata + // REST API accepts logins and team slugs directly + mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: false, teamReviewers: false, assignees: true, labels: true, projects: true, milestones: true}) mockPullRequestUpdate(reg) mockPullRequestUpdateActorAssignees(reg) - mockPullRequestAddReviewers(reg) + mockRequestReviewsByLogin(reg) mockPullRequestUpdateLabels(reg) mockProjectV2ItemUpdate(reg) }, @@ -512,11 +514,11 @@ func Test_editRun(t *testing.T) { Value: "base-branch-name", Edited: true, }, - Reviewers: shared.EditableSlice{ + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{ Default: []string{"OWNER/core", "OWNER/external", "monalisa", "hubot", "dependabot"}, Remove: []string{"OWNER/core", "OWNER/external", "monalisa", "hubot", "dependabot"}, Edited: true, - }, + }}, Assignees: shared.EditableAssignees{ EditableSlice: shared.EditableSlice{ Add: []string{"monalisa", "hubot"}, @@ -544,9 +546,10 @@ func Test_editRun(t *testing.T) { Fetcher: testFetcher{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: false, assignees: true, labels: true, projects: true, milestones: true}) + // Non-interactive with Remove doesn't need reviewers metadata + mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: false, teamReviewers: false, assignees: true, labels: true, projects: true, milestones: true}) mockPullRequestUpdate(reg) - mockPullRequestRemoveReviewers(reg) + mockRequestReviewsByLogin(reg) mockPullRequestUpdateLabels(reg) mockPullRequestUpdateActorAssignees(reg) mockProjectV2ItemUpdate(reg) @@ -562,17 +565,17 @@ func Test_editRun(t *testing.T) { Finder: shared.NewMockFinder("123", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/123"}, ghrepo.New("OWNER", "REPO")), Interactive: false, Editable: shared.Editable{ - Reviewers: shared.EditableSlice{Add: []string{"monalisa", "hubot"}, Edited: true}, + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{Add: []string{"monalisa", "hubot"}, Edited: true}}, }, Fetcher: testFetcher{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - // reviewers only (users), no team reviewers fetched - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true}) + // Non-interactive with Add/Remove doesn't need reviewer metadata + mockRepoMetadata(reg, mockRepoMetadataOptions{}) // explicitly assert that no OrganizationTeamList query occurs reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`)) mockPullRequestUpdate(reg) - mockPullRequestAddReviewers(reg) + mockRequestReviewsByLogin(reg) }, stdout: "https://github.com/OWNER/REPO/pull/123\n", }, @@ -584,17 +587,17 @@ func Test_editRun(t *testing.T) { Finder: shared.NewMockFinder("123", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/123"}, ghrepo.New("OWNER", "REPO")), Interactive: false, Editable: shared.Editable{ - Reviewers: shared.EditableSlice{Add: []string{"monalisa", "OWNER/core"}, Edited: true}, + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{Add: []string{"monalisa", "OWNER/core"}, Edited: true}}, }, Fetcher: testFetcher{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - // reviewer add includes team but non-interactive Add/Remove provided -> no team fetch - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true}) + // Non-interactive with Add/Remove doesn't need reviewer metadata + mockRepoMetadata(reg, mockRepoMetadataOptions{}) // explicitly assert that no OrganizationTeamList query occurs reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`)) mockPullRequestUpdate(reg) - mockPullRequestAddReviewers(reg) + mockRequestReviewsByLogin(reg) }, stdout: "https://github.com/OWNER/REPO/pull/123\n", }, @@ -611,16 +614,17 @@ func Test_editRun(t *testing.T) { }}}, ghrepo.New("OWNER", "REPO")), Interactive: false, Editable: shared.Editable{ - Reviewers: shared.EditableSlice{Remove: []string{"monalisa", "OWNER/core"}, Edited: true}, + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{Remove: []string{"monalisa", "OWNER/core"}, Edited: true}}, }, Fetcher: testFetcher{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true}) + // Non-interactive with Add/Remove doesn't need reviewer metadata + mockRepoMetadata(reg, mockRepoMetadataOptions{}) // explicitly assert that no OrganizationTeamList query occurs reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`)) mockPullRequestUpdate(reg) - mockPullRequestRemoveReviewers(reg) + mockRequestReviewsByLogin(reg) }, stdout: "https://github.com/OWNER/REPO/pull/123\n", }, @@ -632,17 +636,17 @@ func Test_editRun(t *testing.T) { Finder: shared.NewMockFinder("123", &api.PullRequest{URL: "https://github.com/OWNER/REPO/pull/123"}, ghrepo.New("OWNER", "REPO")), Interactive: false, Editable: shared.Editable{ - Reviewers: shared.EditableSlice{Add: []string{"monalisa"}, Remove: []string{"hubot"}, Default: []string{"OWNER/core"}, Edited: true}, + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{Add: []string{"monalisa"}, Remove: []string{"hubot"}, Default: []string{"OWNER/core"}, Edited: true}}, }, Fetcher: testFetcher{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - // reviewers only (users), no team reviewers fetched - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true}) + // Non-interactive with Add/Remove doesn't need reviewer metadata + mockRepoMetadata(reg, mockRepoMetadataOptions{}) // explicitly assert that no OrganizationTeamList query occurs reg.Exclude(t, httpmock.GraphQL(`query OrganizationTeamList\b`)) mockPullRequestUpdate(reg) - mockPullRequestAddReviewers(reg) + mockRequestReviewsByLogin(reg) }, stdout: "https://github.com/OWNER/REPO/pull/123\n", }, @@ -671,6 +675,16 @@ func Test_editRun(t *testing.T) { e.Body.Value = "new body" e.Reviewers.Value = []string{"monalisa", "hubot", "OWNER/core", "OWNER/external"} e.Assignees.Value = []string{"monalisa", "hubot"} + // Populate metadata to simulate what searchFunc would do during prompting + e.Metadata.AssignableActors = []api.AssignableActor{ + api.NewAssignableBot("HUBOTID", "hubot"), + api.NewAssignableUser("MONAID", "monalisa", "Mona Display Name"), + } + // Populate team metadata for reviewer search + e.Metadata.Teams = []api.OrgTeam{ + {ID: "COREID", Slug: "core"}, + {ID: "EXTERNALID", Slug: "external"}, + } e.Labels.Value = []string{"feature", "TODO", "bug"} e.Labels.Add = []string{"feature", "TODO", "bug"} e.Labels.Remove = []string{"docs"} @@ -683,10 +697,12 @@ func Test_editRun(t *testing.T) { EditorRetriever: testEditorRetriever{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: true, assignees: true, labels: true, projects: true, milestones: true}) + // With search functions enabled, we don't fetch reviewers/assignees metadata + // (searchFunc handles dynamic fetching, metadata populated in test mock) + mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: false, teamReviewers: false, assignees: false, labels: true, projects: true, milestones: true}) mockPullRequestUpdate(reg) mockPullRequestUpdateActorAssignees(reg) - mockPullRequestAddReviewers(reg) + mockRequestReviewsByLogin(reg) mockPullRequestUpdateLabels(reg) mockProjectV2ItemUpdate(reg) }, @@ -779,6 +795,16 @@ func Test_editRun(t *testing.T) { e.Body.Value = "new body" e.Reviewers.Remove = []string{"monalisa", "hubot", "OWNER/core", "OWNER/external", "dependabot"} e.Assignees.Value = []string{"monalisa", "hubot"} + // Populate metadata to simulate what searchFunc would do during prompting + e.Metadata.AssignableActors = []api.AssignableActor{ + api.NewAssignableBot("HUBOTID", "hubot"), + api.NewAssignableUser("MONAID", "monalisa", "Mona Display Name"), + } + // Populate team metadata for reviewer search + e.Metadata.Teams = []api.OrgTeam{ + {ID: "COREID", Slug: "core"}, + {ID: "EXTERNALID", Slug: "external"}, + } e.Labels.Value = []string{"feature", "TODO", "bug"} e.Labels.Add = []string{"feature", "TODO", "bug"} e.Labels.Remove = []string{"docs"} @@ -791,9 +817,10 @@ func Test_editRun(t *testing.T) { EditorRetriever: testEditorRetriever{}, }, httpStubs: func(t *testing.T, reg *httpmock.Registry) { - mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: true, teamReviewers: true, assignees: true, labels: true, projects: true, milestones: true}) + // With search functions enabled, we don't fetch reviewers/assignees metadata + mockRepoMetadata(reg, mockRepoMetadataOptions{reviewers: false, teamReviewers: false, assignees: false, labels: true, projects: true, milestones: true}) mockPullRequestUpdate(reg) - mockPullRequestRemoveReviewers(reg) + mockRequestReviewsByLogin(reg) mockPullRequestUpdateActorAssignees(reg) mockPullRequestUpdateLabels(reg) mockProjectV2ItemUpdate(reg) @@ -945,6 +972,78 @@ func Test_editRun(t *testing.T) { }, stdout: "https://github.com/OWNER/REPO/pull/123\n", }, + { + name: "interactive GHES uses legacy reviewer flow without search", + input: &EditOptions{ + Detector: &fd.DisabledDetectorMock{}, + SelectorArg: "123", + Finder: shared.NewMockFinder("123", &api.PullRequest{ + URL: "https://github.com/OWNER/REPO/pull/123", + ReviewRequests: api.ReviewRequests{Nodes: []struct{ RequestedReviewer api.RequestedReviewer }{ + {RequestedReviewer: api.RequestedReviewer{TypeName: "User", Login: "octocat"}}, + }}, + }, ghrepo.New("OWNER", "REPO")), + Interactive: true, + Surveyor: testSurveyor{ + fieldsToEdit: func(e *shared.Editable) error { + e.Reviewers.Edited = true + return nil + }, + editFields: func(e *shared.Editable, _ string) error { + // Verify GHES uses legacy flow: ReviewerSearchFunc should be nil + require.Nil(t, e.ReviewerSearchFunc) + // Verify options are populated from fetched metadata + require.Contains(t, e.Reviewers.Options, "monalisa") + require.Contains(t, e.Reviewers.Options, "hubot") + require.Contains(t, e.Reviewers.Options, "OWNER/core") + + e.Reviewers.Value = []string{"monalisa", "OWNER/core"} + return nil + }, + }, + Fetcher: testFetcher{}, + EditorRetriever: testEditorRetriever{}, + }, + httpStubs: func(t *testing.T, reg *httpmock.Registry) { + // GHES should NOT use the new SuggestedReviewerActors query + reg.Exclude(t, httpmock.GraphQL(`query SuggestedReviewerActors\b`)) + // GHES should use legacy metadata fetch for reviewers (AssignableUsers, not Actors) + reg.Exclude(t, httpmock.GraphQL(`query RepositoryAssignableActors\b`)) + reg.Register( + httpmock.GraphQL(`query RepositoryAssignableUsers\b`), + httpmock.StringResponse(` + { "data": { "repository": { "assignableUsers": { + "nodes": [ + { "login": "hubot", "id": "HUBOTID" }, + { "login": "monalisa", "id": "MONAID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + // GHES should fetch teams for interactive reviewer editing + reg.Register( + httpmock.GraphQL(`query OrganizationTeamList\b`), + httpmock.StringResponse(` + { "data": { "organization": { "teams": { + "nodes": [ + { "slug": "external", "id": "EXTERNALID" }, + { "slug": "core", "id": "COREID" } + ], + "pageInfo": { "hasNextPage": false } + } } } } + `)) + // Current user fetched for reviewers + reg.Register( + httpmock.GraphQL(`query UserCurrent\b`), + httpmock.StringResponse(` + { "data": { "viewer": { "login": "monalisa" } } } + `)) + mockPullRequestUpdate(reg) + mockPullRequestAddReviewers(reg) + mockPullRequestRemoveReviewers(reg) + }, + stdout: "https://github.com/OWNER/REPO/pull/123\n", + }, { name: "non-interactive projects v1 unsupported doesn't fetch v1 metadata", input: &EditOptions{ @@ -1188,6 +1287,17 @@ func mockPullRequestRemoveReviewers(reg *httpmock.Registry) { httpmock.StringResponse(`{}`)) } +// mockRequestReviewsByLogin mocks the RequestReviewsByLogin GraphQL mutation +// used on github.com when ActorAssignees is enabled. +func mockRequestReviewsByLogin(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`mutation RequestReviewsByLogin\b`), + httpmock.GraphQLMutation(` + { "data": { "requestReviewsByLogin": { "clientMutationId": "" } } }`, + func(inputs map[string]interface{}) {}), + ) +} + func mockPullRequestUpdateLabels(reg *httpmock.Registry) { reg.Register( httpmock.GraphQL(`mutation LabelAdd\b`), diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index 7e6e772198c..732f16a4b53 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -14,8 +14,8 @@ type Editable struct { Title EditableString Body EditableString Base EditableString - Reviewers EditableSlice - ReviewerSearchFunc func(string) ([]string, []string, error) + Reviewers EditableReviewers + ReviewerSearchFunc func(string) prompter.MultiSelectSearchResult Assignees EditableAssignees AssigneeSearchFunc func(string) prompter.MultiSelectSearchResult Labels EditableSlice @@ -49,6 +49,13 @@ type EditableAssignees struct { DefaultLogins []string // For disambiguating actors from display names } +// EditableReviewers is a special case of EditableSlice. +// It tracks both display names (for UI) and logins (for API mutations). +type EditableReviewers struct { + EditableSlice + DefaultLogins []string // Logins for computing add/remove sets +} + // ProjectsV2 mutations require a mapping of an item ID to a project ID. // Keep that map along with standard EditableSlice data. type EditableProjects struct { @@ -268,6 +275,13 @@ func (ea *EditableAssignees) clone() EditableAssignees { } } +func (er *EditableReviewers) clone() EditableReviewers { + return EditableReviewers{ + EditableSlice: er.EditableSlice.clone(), + DefaultLogins: er.DefaultLogins, + } +} + func (ep *EditableProjects) clone() EditableProjects { return EditableProjects{ EditableSlice: ep.EditableSlice.clone(), @@ -299,10 +313,24 @@ func EditFieldsSurvey(p EditPrompter, editable *Editable, editorCommand string) } } if editable.Reviewers.Edited { - editable.Reviewers.Value, err = multiSelectSurvey( - p, "Reviewers", editable.Reviewers.Default, editable.Reviewers.Options) - if err != nil { - return err + if editable.ReviewerSearchFunc != nil { + editable.Reviewers.Options = []string{} + editable.Reviewers.Value, err = p.MultiSelectWithSearch( + "Reviewers", + "Search reviewers", + editable.Reviewers.Default, + // No persistent options - teams are included in search results + []string{}, + editable.ReviewerSearchFunc) + if err != nil { + return err + } + } else { + editable.Reviewers.Value, err = multiSelectSurvey( + p, "Reviewers", editable.Reviewers.Default, editable.Reviewers.Options) + if err != nil { + return err + } } } if editable.Assignees.Edited { @@ -415,16 +443,24 @@ func FieldsToEditSurvey(p EditPrompter, editable *Editable) error { } func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, projectV1Support gh.ProjectsV1Support) error { - // Determine whether to fetch organization teams. + // Determine whether to fetch organization teams and reviewers. // Interactive reviewer editing (Edited true, but no Add/Remove slices) still needs - // team data for selection UI. For non-interactive flows, we never need to fetch teams. + // team data for selection UI. For non-interactive flows, we never need to fetch teams + // as the REST API accepts team slugs directly. + // If we have a search func, we don't need to fetch teams/reviewers since we + // assume that will be done dynamically in the prompting flow. teamReviewers := false + fetchReviewers := false if editable.Reviewers.Edited { // This is likely an interactive flow since edited is set but no mutations to - // Add/Remove slices, so we need to load the teams. - if len(editable.Reviewers.Add) == 0 && len(editable.Reviewers.Remove) == 0 { + // Add/Remove slices, so we need to load the teams and reviewers. + // However, if we have a search func, skip fetching as it will be done dynamically. + if len(editable.Reviewers.Add) == 0 && len(editable.Reviewers.Remove) == 0 && editable.ReviewerSearchFunc == nil { teamReviewers = true + fetchReviewers = true } + // Note: Non-interactive flows (with Add/Remove) don't need to fetch reviewers/teams + // because the REST API accepts logins and team slugs directly. } fetchAssignees := false @@ -444,7 +480,7 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, } input := api.RepoMetadataInput{ - Reviewers: editable.Reviewers.Edited, + Reviewers: fetchReviewers, TeamReviewers: teamReviewers, Assignees: fetchAssignees, ActorAssignees: editable.Assignees.ActorAssignees, diff --git a/pkg/cmd/pr/shared/params.go b/pkg/cmd/pr/shared/params.go index e0ea9f105f5..784b68cf9bc 100644 --- a/pkg/cmd/pr/shared/params.go +++ b/pkg/cmd/pr/shared/params.go @@ -295,11 +295,24 @@ func (r *MeReplacer) ReplaceSlice(handles []string) ([]string, error) { // Login is generally needed for API calls; name is used when launching web browser. type CopilotReplacer struct { returnLogin bool + // copilotLogin is the login to use when replacing @copilot. + // Different Copilot features use different bot logins. + copilotLogin string } +// NewCopilotReplacer creates a replacer for assignee @copilot references. func NewCopilotReplacer(returnLogin bool) *CopilotReplacer { return &CopilotReplacer{ - returnLogin: returnLogin, + returnLogin: returnLogin, + copilotLogin: api.CopilotAssigneeLogin, + } +} + +// NewCopilotReviewerReplacer creates a replacer for reviewer @copilot references. +func NewCopilotReviewerReplacer() *CopilotReplacer { + return &CopilotReplacer{ + returnLogin: true, + copilotLogin: api.CopilotReviewerLogin, } } @@ -308,7 +321,7 @@ func (r *CopilotReplacer) replace(handle string) string { return handle } if r.returnLogin { - return api.CopilotActorLogin + return r.copilotLogin } return api.CopilotActorName } diff --git a/pkg/cmd/pr/shared/params_test.go b/pkg/cmd/pr/shared/params_test.go index 024965630fd..ddb7a1b2f6b 100644 --- a/pkg/cmd/pr/shared/params_test.go +++ b/pkg/cmd/pr/shared/params_test.go @@ -334,6 +334,37 @@ func TestCopilotReplacer_ReplaceSlice(t *testing.T) { } } +func TestCopilotReviewerReplacer_ReplaceSlice(t *testing.T) { + tests := []struct { + name string + handles []string + want []string + }{ + { + name: "replaces @copilot with reviewer login", + handles: []string{"monalisa", "@copilot", "hubot"}, + want: []string{"monalisa", "copilot-pull-request-reviewer", "hubot"}, + }, + { + name: "handles @copilot case-insensitively", + handles: []string{"@Copilot", "user", "@CoPiLoT"}, + want: []string{"copilot-pull-request-reviewer", "user", "copilot-pull-request-reviewer"}, + }, + { + name: "handles no @copilot mentions", + handles: []string{"monalisa", "user", "hubot"}, + want: []string{"monalisa", "user", "hubot"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewCopilotReviewerReplacer() + got := r.ReplaceSlice(tt.handles) + require.Equal(t, tt.want, got) + }) + } +} + func Test_QueryHasStateClause(t *testing.T) { tests := []struct { searchQuery string From 05f1a9841631fd702c2cf36c14a2a303d47f7ed9 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 28 Jan 2026 16:09:59 +0000 Subject: [PATCH 38/98] chore: bump `cli/oauth` to `v1.2.2` Signed-off-by: Babak K. Shandiz --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 700ff6f9746..a4c35c76b1e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/cli/go-gh/v2 v2.13.0 github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24 - github.com/cli/oauth v1.2.1 + github.com/cli/oauth v1.2.2 github.com/cli/safeexec v1.0.1 github.com/cpuguy83/go-md2man/v2 v2.0.7 github.com/creack/pty v1.1.24 diff --git a/go.sum b/go.sum index 32a6eb761b5..bb94bfdfa7c 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/cli/go-gh/v2 v2.13.0 h1:jEHZu/VPVoIJkciK3pzZd3rbT8J90swsK5Ui4ewH1ys= github.com/cli/go-gh/v2 v2.13.0/go.mod h1:Us/NbQ8VNM0fdaILgoXSz6PKkV5PWaEzkJdc9vR2geM= github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24 h1:QDrhR4JA2n3ij9YQN0u5ZeuvRIIvsUGmf5yPlTS0w8E= github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24/go.mod h1:rr9GNING0onuVw8MnracQHn7PcchnFlP882Y0II2KZk= -github.com/cli/oauth v1.2.1 h1:9+vketSVuBCbEIpx4XPHHDlTX2R9MbLnM79sfA2Ac+4= -github.com/cli/oauth v1.2.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= +github.com/cli/oauth v1.2.2 h1:/qG/wok8jzu66tx7q+duGOIp4DT5P/ACXrdc33UoNUQ= +github.com/cli/oauth v1.2.2/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= From 8f0cfac2ca56318c9286c64549eec571d6c43f2a Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 28 Jan 2026 16:10:58 +0000 Subject: [PATCH 39/98] chore: update licenses Signed-off-by: Babak K. Shandiz --- third-party-licenses.darwin.md | 2 +- third-party-licenses.linux.md | 2 +- third-party-licenses.windows.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index e7e1a95eca0..a8b605d9c65 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -38,7 +38,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/charmbracelet/x/term](https://pkg.go.dev/github.com/charmbracelet/x/term) ([MIT](https://github.com/charmbracelet/x/blob/term/v0.2.1/term/LICENSE)) - [github.com/cli/browser](https://pkg.go.dev/github.com/cli/browser) ([BSD-2-Clause](https://github.com/cli/browser/blob/v1.3.0/LICENSE)) - [github.com/cli/go-gh/v2](https://pkg.go.dev/github.com/cli/go-gh/v2) ([MIT](https://github.com/cli/go-gh/blob/v2.13.0/LICENSE)) -- [github.com/cli/oauth](https://pkg.go.dev/github.com/cli/oauth) ([MIT](https://github.com/cli/oauth/blob/v1.2.1/LICENSE)) +- [github.com/cli/oauth](https://pkg.go.dev/github.com/cli/oauth) ([MIT](https://github.com/cli/oauth/blob/v1.2.2/LICENSE)) - [github.com/cli/safeexec](https://pkg.go.dev/github.com/cli/safeexec) ([BSD-2-Clause](https://github.com/cli/safeexec/blob/v1.0.1/LICENSE)) - [github.com/cli/shurcooL-graphql](https://pkg.go.dev/github.com/cli/shurcooL-graphql) ([MIT](https://github.com/cli/shurcooL-graphql/blob/v0.0.4/LICENSE)) - [github.com/containerd/stargz-snapshotter/estargz](https://pkg.go.dev/github.com/containerd/stargz-snapshotter/estargz) ([Apache-2.0](https://github.com/containerd/stargz-snapshotter/blob/estargz/v0.18.1/estargz/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 855ba501824..4bbe381ebe1 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -37,7 +37,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/charmbracelet/x/term](https://pkg.go.dev/github.com/charmbracelet/x/term) ([MIT](https://github.com/charmbracelet/x/blob/term/v0.2.1/term/LICENSE)) - [github.com/cli/browser](https://pkg.go.dev/github.com/cli/browser) ([BSD-2-Clause](https://github.com/cli/browser/blob/v1.3.0/LICENSE)) - [github.com/cli/go-gh/v2](https://pkg.go.dev/github.com/cli/go-gh/v2) ([MIT](https://github.com/cli/go-gh/blob/v2.13.0/LICENSE)) -- [github.com/cli/oauth](https://pkg.go.dev/github.com/cli/oauth) ([MIT](https://github.com/cli/oauth/blob/v1.2.1/LICENSE)) +- [github.com/cli/oauth](https://pkg.go.dev/github.com/cli/oauth) ([MIT](https://github.com/cli/oauth/blob/v1.2.2/LICENSE)) - [github.com/cli/safeexec](https://pkg.go.dev/github.com/cli/safeexec) ([BSD-2-Clause](https://github.com/cli/safeexec/blob/v1.0.1/LICENSE)) - [github.com/cli/shurcooL-graphql](https://pkg.go.dev/github.com/cli/shurcooL-graphql) ([MIT](https://github.com/cli/shurcooL-graphql/blob/v0.0.4/LICENSE)) - [github.com/containerd/stargz-snapshotter/estargz](https://pkg.go.dev/github.com/containerd/stargz-snapshotter/estargz) ([Apache-2.0](https://github.com/containerd/stargz-snapshotter/blob/estargz/v0.18.1/estargz/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index cae6a27b48b..d1f20fe8b26 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -37,7 +37,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/charmbracelet/x/term](https://pkg.go.dev/github.com/charmbracelet/x/term) ([MIT](https://github.com/charmbracelet/x/blob/term/v0.2.1/term/LICENSE)) - [github.com/cli/browser](https://pkg.go.dev/github.com/cli/browser) ([BSD-2-Clause](https://github.com/cli/browser/blob/v1.3.0/LICENSE)) - [github.com/cli/go-gh/v2](https://pkg.go.dev/github.com/cli/go-gh/v2) ([MIT](https://github.com/cli/go-gh/blob/v2.13.0/LICENSE)) -- [github.com/cli/oauth](https://pkg.go.dev/github.com/cli/oauth) ([MIT](https://github.com/cli/oauth/blob/v1.2.1/LICENSE)) +- [github.com/cli/oauth](https://pkg.go.dev/github.com/cli/oauth) ([MIT](https://github.com/cli/oauth/blob/v1.2.2/LICENSE)) - [github.com/cli/safeexec](https://pkg.go.dev/github.com/cli/safeexec) ([BSD-2-Clause](https://github.com/cli/safeexec/blob/v1.0.1/LICENSE)) - [github.com/cli/shurcooL-graphql](https://pkg.go.dev/github.com/cli/shurcooL-graphql) ([MIT](https://github.com/cli/shurcooL-graphql/blob/v0.0.4/LICENSE)) - [github.com/containerd/stargz-snapshotter/estargz](https://pkg.go.dev/github.com/containerd/stargz-snapshotter/estargz) ([Apache-2.0](https://github.com/containerd/stargz-snapshotter/blob/estargz/v0.18.1/estargz/LICENSE)) From 846d6619b7913c905eb82d5b63000173fbf37e0e Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:50:54 -0700 Subject: [PATCH 40/98] Remove redundant comment --- pkg/cmd/pr/edit/edit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index 3753373ba0d..b91798daeba 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -297,7 +297,7 @@ func editRun(opts *EditOptions) error { apiClient := api.NewClientFromHTTP(httpClient) // Wire up search functions for assignees and reviewers. - // Only enabled on github.com (ActorIsAssignable is false on GHES). + // Only enabled on github.com. if issueFeatures.ActorIsAssignable { editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) editable.ReviewerSearchFunc = reviewerSearchFunc(apiClient, repo, &editable, pr.ID) From 7f8ca2ca812a3aa678a6e2a74ec1a29e70e99249 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:23:18 -0700 Subject: [PATCH 41/98] Fix return proper slug only for ReviewerTeam --- api/queries_pr.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index f006c529c62..a84582c4269 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -939,26 +939,26 @@ func (b ReviewerBot) DisplayName() string { func (r ReviewerBot) sealedReviewerCandidate() {} // ReviewerTeam is a team that can review a pull request. -// The slug is stored as "org/team-slug" format. type ReviewerTeam struct { - slug string + org string + teamSlug string } -// NewReviewerTeam creates a new ReviewerTeam with the full "org/slug" format. +// NewReviewerTeam creates a new ReviewerTeam. func NewReviewerTeam(orgName, teamSlug string) ReviewerTeam { - return ReviewerTeam{slug: fmt.Sprintf("%s/%s", orgName, teamSlug)} + return ReviewerTeam{org: orgName, teamSlug: teamSlug} } func (r ReviewerTeam) DisplayName() string { - return r.slug + return fmt.Sprintf("%s/%s", r.org, r.teamSlug) } func (r ReviewerTeam) Login() string { - return r.slug + return fmt.Sprintf("%s/%s", r.org, r.teamSlug) } func (r ReviewerTeam) Slug() string { - return r.slug + return r.teamSlug } func (r ReviewerTeam) sealedReviewerCandidate() {} From d643d5386e25c7f7c9d4f716a1c4356262a251f6 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:37:11 -0700 Subject: [PATCH 42/98] fix(pr edit): send empty slices to clear reviewers in replace mode Always send explicit lists for userLogins, botLogins, and teamSlugs in RequestReviewsByLogin mutation, even when empty. Previously, empty slices were omitted due to omitempty JSON behavior and len > 0 checks, which prevented clearing all reviewers when using replace mode. Empty slices are harmless no-ops in union mode, so we now send them unconditionally for simpler logic. Add test to verify the mutation receives empty slices when all reviewers are removed. --- api/queries_pr.go | 32 +++++++++------------ pkg/cmd/pr/edit/edit_test.go | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index a84582c4269..aa584cf5d02 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -772,30 +772,24 @@ func RequestReviewsByLogin(client *Client, repo ghrepo.Interface, prID string, u Union: githubv4.Boolean(union), } - if len(userLogins) > 0 { - logins := make([]githubv4.String, len(userLogins)) - for i, l := range userLogins { - logins[i] = githubv4.String(l) - } - input.UserLogins = &logins + userLoginValues := make([]githubv4.String, len(userLogins)) + for i, l := range userLogins { + userLoginValues[i] = githubv4.String(l) } + input.UserLogins = &userLoginValues - if len(botLogins) > 0 { - logins := make([]githubv4.String, len(botLogins)) - for i, l := range botLogins { - // Bot logins require the [bot] suffix for the mutation - logins[i] = githubv4.String(l + "[bot]") - } - input.BotLogins = &logins + botLoginValues := make([]githubv4.String, len(botLogins)) + for i, l := range botLogins { + // Bot logins require the [bot] suffix for the mutation + botLoginValues[i] = githubv4.String(l + "[bot]") } + input.BotLogins = &botLoginValues - if len(teamSlugs) > 0 { - slugs := make([]githubv4.String, len(teamSlugs)) - for i, s := range teamSlugs { - slugs[i] = githubv4.String(s) - } - input.TeamSlugs = &slugs + teamSlugValues := make([]githubv4.String, len(teamSlugs)) + for i, s := range teamSlugs { + teamSlugValues[i] = githubv4.String(s) } + input.TeamSlugs = &teamSlugValues variables := map[string]interface{}{ "input": input, diff --git a/pkg/cmd/pr/edit/edit_test.go b/pkg/cmd/pr/edit/edit_test.go index ddb16631772..e38fac3caa0 100644 --- a/pkg/cmd/pr/edit/edit_test.go +++ b/pkg/cmd/pr/edit/edit_test.go @@ -556,6 +556,61 @@ func Test_editRun(t *testing.T) { }, stdout: "https://github.com/OWNER/REPO/pull/123\n", }, + { + name: "remove all reviewers sends empty slices to mutation", + input: &EditOptions{ + Detector: &fd.EnabledDetectorMock{}, + SelectorArg: "123", + Finder: shared.NewMockFinder("123", &api.PullRequest{ + URL: "https://github.com/OWNER/REPO/pull/123", + ReviewRequests: api.ReviewRequests{ + Nodes: []struct{ RequestedReviewer api.RequestedReviewer }{ + { + RequestedReviewer: api.RequestedReviewer{ + TypeName: "Team", + Slug: "core", + Organization: struct { + Login string `json:"login"` + }{Login: "OWNER"}, + }, + }, + { + RequestedReviewer: api.RequestedReviewer{ + TypeName: "User", + Login: "monalisa", + }, + }, + }, + }, + }, ghrepo.New("OWNER", "REPO")), + Interactive: false, + Editable: shared.Editable{ + Reviewers: shared.EditableReviewers{EditableSlice: shared.EditableSlice{ + Default: []string{"OWNER/core", "monalisa"}, + Remove: []string{"OWNER/core", "monalisa"}, + Edited: true, + }}, + }, + Fetcher: testFetcher{}, + }, + httpStubs: func(t *testing.T, reg *httpmock.Registry) { + mockRepoMetadata(reg, mockRepoMetadataOptions{}) + mockPullRequestUpdate(reg) + reg.Register( + httpmock.GraphQL(`mutation RequestReviewsByLogin\b`), + httpmock.GraphQLMutation(` + { "data": { "requestReviewsByLogin": { "clientMutationId": "" } } }`, + func(inputs map[string]interface{}) { + // Verify that empty slices are sent to properly clear all reviewer types + require.Equal(t, []interface{}{}, inputs["userLogins"], "userLogins should be an empty slice") + require.Equal(t, []interface{}{}, inputs["botLogins"], "botLogins should be an empty slice") + require.Equal(t, []interface{}{}, inputs["teamSlugs"], "teamSlugs should be an empty slice") + require.Equal(t, false, inputs["union"], "union should be false for replace mode") + }), + ) + }, + stdout: "https://github.com/OWNER/REPO/pull/123\n", + }, // Conditional team fetching cases { name: "non-interactive add only user reviewers skips team fetch", From 2d191e5ba0304934fa22ad376d9553ee45021fa0 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:30:08 -0700 Subject: [PATCH 43/98] Implement cascading quota for reviewer suggestions Each source (suggestions, collaborators, teams) has base quota of 5. Unfilled slots cascade to later sources, allowing up to 15 total. Adds unit tests with HTTP mocks. --- api/queries_pr.go | 74 +++++++++------ api/queries_pr_test.go | 199 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 26 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index aa584cf5d02..fbed0611f91 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -958,13 +958,16 @@ func (r ReviewerTeam) Slug() string { func (r ReviewerTeam) sealedReviewerCandidate() {} // SuggestedReviewerActors fetches suggested reviewers for a pull request. -// It combines results from: -// - suggestedReviewerActors (10 max) - suggested based on activity -// - repository collaborators (10 max) - all collaborators -// - organization teams (10 max for org repos) - all teams (if owner is an org) -// Results are returned in that order with duplicates removed. +// It combines results from three sources using a cascading quota system: +// - suggestedReviewerActors - suggested based on PR activity (base quota: 5) +// - repository collaborators - all collaborators (base quota: 5 + unfilled from suggestions) +// - organization teams - all teams for org repos (base quota: 5 + unfilled from collaborators) +// +// This ensures we show up to 15 total candidates, with each source filling any +// unfilled quota from the previous source. Results are deduplicated. // Returns the candidates, a MoreResults count, and an error. func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, query string) ([]ReviewerCandidate, int, error) { + // Fetch 10 from each source to allow cascading quota to fill from available results. // Use a single query that includes organization.teams - if the owner is not an org, // we'll get a "Could not resolve to an Organization" error which we handle gracefully. type responseData struct { @@ -985,7 +988,7 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, } `graphql:"... on Bot"` } } - } `graphql:"suggestedReviewerActors(first: 5, query: $query)"` + } `graphql:"suggestedReviewerActors(first: 10, query: $query)"` } `graphql:"... on PullRequest"` } `graphql:"node(id: $id)"` Repository struct { @@ -995,7 +998,7 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, Login string Name string } - } `graphql:"collaborators(first: 5, query: $query)"` + } `graphql:"collaborators(first: 10, query: $query)"` } `graphql:"repository(owner: $owner, name: $name)"` Organization struct { Teams struct { @@ -1003,7 +1006,7 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, Nodes []struct { Slug string } - } `graphql:"teams(first: 5, query: $query)"` + } `graphql:"teams(first: 10, query: $query)"` } `graphql:"organization(login: $owner)"` } @@ -1022,54 +1025,73 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, return nil, 0, err } - ownerName := repo.RepoOwner() + // Build candidates using cascading quota logic: + // Each source has a base quota of 5, plus any unfilled quota from previous sources. + // This ensures we show up to 15 total candidates, filling gaps when earlier sources have fewer. seen := make(map[string]bool) var candidates []ReviewerCandidate + const baseQuota = 5 - // Add suggested reviewers first (excluding author) + // Suggested reviewers (excluding author) + suggestionsAdded := 0 for _, n := range result.Node.PullRequest.SuggestedActors.Nodes { + if suggestionsAdded >= baseQuota { + break + } if n.IsAuthor { continue } var candidate ReviewerCandidate + var login string if n.Reviewer.TypeName == "User" && n.Reviewer.User.Login != "" { - candidate = NewReviewerUser(n.Reviewer.User.Login, n.Reviewer.User.Name) + login = n.Reviewer.User.Login + candidate = NewReviewerUser(login, n.Reviewer.User.Name) } else if n.Reviewer.TypeName == "Bot" && n.Reviewer.Bot.Login != "" { - candidate = NewReviewerBot(n.Reviewer.Bot.Login) + login = n.Reviewer.Bot.Login + candidate = NewReviewerBot(login) } else { continue } - - login := candidate.Login() if !seen[login] { seen[login] = true candidates = append(candidates, candidate) + suggestionsAdded++ } } - // Add collaborators (deduped against suggested) + // Collaborators: quota = base + unfilled from suggestions + collaboratorsQuota := baseQuota + (baseQuota - suggestionsAdded) + collaboratorsAdded := 0 for _, c := range result.Repository.Collaborators.Nodes { + if collaboratorsAdded >= collaboratorsQuota { + break + } if c.Login == "" { continue } - candidate := NewReviewerUser(c.Login, c.Name) - login := candidate.Login() - if !seen[login] { - seen[login] = true - candidates = append(candidates, candidate) + if !seen[c.Login] { + seen[c.Login] = true + candidates = append(candidates, NewReviewerUser(c.Login, c.Name)) + collaboratorsAdded++ } } - // Add teams (will be empty if owner is not an org) + // Teams: quota = base + unfilled from collaborators + teamsQuota := baseQuota + (collaboratorsQuota - collaboratorsAdded) + teamsAdded := 0 + ownerName := repo.RepoOwner() for _, t := range result.Organization.Teams.Nodes { + if teamsAdded >= teamsQuota { + break + } if t.Slug == "" { continue } - candidate := NewReviewerTeam(ownerName, t.Slug) - login := candidate.Login() - if !seen[login] { - seen[login] = true - candidates = append(candidates, candidate) + teamLogin := fmt.Sprintf("%s/%s", ownerName, t.Slug) + if !seen[teamLogin] { + seen[teamLogin] = true + candidates = append(candidates, NewReviewerTeam(ownerName, t.Slug)) + teamsAdded++ } } diff --git a/api/queries_pr_test.go b/api/queries_pr_test.go index 0e2646048e1..cea2c201bcf 100644 --- a/api/queries_pr_test.go +++ b/api/queries_pr_test.go @@ -2,6 +2,8 @@ package api import ( "encoding/json" + "fmt" + "strings" "testing" "github.com/cli/cli/v2/internal/ghrepo" @@ -136,3 +138,200 @@ func Test_Logins(t *testing.T) { }) } } + +// mockReviewerResponse generates a GraphQL response for SuggestedReviewerActors tests. +// It creates suggestions (s1, s2...), collaborators (c1, c2...), and teams (team1, team2...). +// totalCollabs and totalTeams set the TotalCount fields (for "more results" calculation). +func mockReviewerResponse(suggestions, collabs, teams, totalCollabs, totalTeams int) string { + var suggestionNodes, collabNodes, teamNodes []string + + for i := 1; i <= suggestions; i++ { + suggestionNodes = append(suggestionNodes, + fmt.Sprintf(`{"isAuthor": false, "reviewer": {"__typename": "User", "login": "s%d", "name": "S%d"}}`, i, i)) + } + for i := 1; i <= collabs; i++ { + collabNodes = append(collabNodes, + fmt.Sprintf(`{"login": "c%d", "name": "C%d"}`, i, i)) + } + for i := 1; i <= teams; i++ { + teamNodes = append(teamNodes, + fmt.Sprintf(`{"slug": "team%d"}`, i)) + } + + return fmt.Sprintf(`{ + "data": { + "node": {"suggestedReviewerActors": {"nodes": [%s]}}, + "repository": {"collaborators": {"totalCount": %d, "nodes": [%s]}}, + "organization": {"teams": {"totalCount": %d, "nodes": [%s]}} + } + }`, strings.Join(suggestionNodes, ","), totalCollabs, strings.Join(collabNodes, ","), + totalTeams, strings.Join(teamNodes, ",")) +} + +func TestSuggestedReviewerActors(t *testing.T) { + tests := []struct { + name string + httpStubs func(*httpmock.Registry) + expectedCount int + expectedLogins []string + expectedMore int + expectError bool + }{ + { + name: "all sources plentiful - 5 each from cascading quota", + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(mockReviewerResponse(6, 6, 6, 20, 10))) + }, + expectedCount: 15, + expectedLogins: []string{"s1", "s2", "s3", "s4", "s5", "c1", "c2", "c3", "c4", "c5", "OWNER/team1", "OWNER/team2", "OWNER/team3", "OWNER/team4", "OWNER/team5"}, + expectedMore: 30, + }, + { + name: "few suggestions - collaborators fill gap", + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(mockReviewerResponse(2, 10, 6, 50, 10))) + }, + expectedCount: 15, + expectedLogins: []string{"s1", "s2", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "OWNER/team1", "OWNER/team2", "OWNER/team3", "OWNER/team4", "OWNER/team5"}, + expectedMore: 60, + }, + { + name: "few suggestions and collaborators - teams fill gap", + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(mockReviewerResponse(2, 3, 10, 3, 10))) + }, + expectedCount: 15, + expectedLogins: []string{"s1", "s2", "c1", "c2", "c3", "OWNER/team1", "OWNER/team2", "OWNER/team3", "OWNER/team4", "OWNER/team5", "OWNER/team6", "OWNER/team7", "OWNER/team8", "OWNER/team9", "OWNER/team10"}, + expectedMore: 13, + }, + { + name: "no suggestions or collaborators - teams only", + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(mockReviewerResponse(0, 0, 10, 0, 10))) + }, + expectedCount: 10, // max 15, but only 10 teams available + expectedLogins: []string{"OWNER/team1", "OWNER/team2", "OWNER/team3", "OWNER/team4", "OWNER/team5", "OWNER/team6", "OWNER/team7", "OWNER/team8", "OWNER/team9", "OWNER/team10"}, + expectedMore: 10, + }, + { + name: "author excluded from suggestions", + httpStubs: func(reg *httpmock.Registry) { + // Custom response with author flag + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(`{ + "data": { + "node": {"suggestedReviewerActors": {"nodes": [ + {"isAuthor": true, "reviewer": {"__typename": "User", "login": "author", "name": "Author"}}, + {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}}, + {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s2", "name": "S2"}} + ]}}, + "repository": {"collaborators": {"totalCount": 5, "nodes": [{"login": "c1", "name": "C1"}]}}, + "organization": {"teams": {"totalCount": 3, "nodes": [{"slug": "team1"}]}} + } + }`)) + }, + expectedCount: 4, + expectedLogins: []string{"s1", "s2", "c1", "OWNER/team1"}, + expectedMore: 8, + }, + { + name: "deduplication across sources", + httpStubs: func(reg *httpmock.Registry) { + // Custom response with duplicate user + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(`{ + "data": { + "node": {"suggestedReviewerActors": {"nodes": [ + {"isAuthor": false, "reviewer": {"__typename": "User", "login": "shareduser", "name": "Shared"}} + ]}}, + "repository": {"collaborators": {"totalCount": 10, "nodes": [ + {"login": "shareduser", "name": "Shared"}, + {"login": "c1", "name": "C1"} + ]}}, + "organization": {"teams": {"totalCount": 5, "nodes": [{"slug": "team1"}]}} + } + }`)) + }, + expectedCount: 3, + expectedLogins: []string{"shareduser", "c1", "OWNER/team1"}, + expectedMore: 15, + }, + { + name: "personal repo - no organization teams", + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(`{ + "data": { + "node": {"suggestedReviewerActors": {"nodes": [ + {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}} + ]}}, + "repository": {"collaborators": {"totalCount": 3, "nodes": [{"login": "c1", "name": "C1"}]}}, + "organization": null + }, + "errors": [{"message": "Could not resolve to an Organization with the login of 'OWNER'."}] + }`)) + }, + expectedCount: 2, + expectedLogins: []string{"s1", "c1"}, + expectedMore: 3, + }, + { + name: "bot reviewer included", + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(`{ + "data": { + "node": {"suggestedReviewerActors": {"nodes": [ + {"isAuthor": false, "reviewer": {"__typename": "Bot", "login": "copilot-pull-request-reviewer"}}, + {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}} + ]}}, + "repository": {"collaborators": {"totalCount": 5, "nodes": []}}, + "organization": {"teams": {"totalCount": 0, "nodes": []}} + } + }`)) + }, + expectedCount: 2, + expectedLogins: []string{"copilot-pull-request-reviewer", "s1"}, + expectedMore: 5, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reg := &httpmock.Registry{} + if tt.httpStubs != nil { + tt.httpStubs(reg) + } + + client := newTestClient(reg) + repo, _ := ghrepo.FromFullName("OWNER/REPO") + + candidates, moreResults, err := SuggestedReviewerActors(client, repo, "PR_123", "") + if tt.expectError { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.expectedCount, len(candidates), "candidate count mismatch") + assert.Equal(t, tt.expectedMore, moreResults, "moreResults mismatch") + + logins := make([]string, len(candidates)) + for i, c := range candidates { + logins[i] = c.Login() + } + assert.Equal(t, tt.expectedLogins, logins) + }) + } +} From 12e59c421b8807080e913388852c176089342c9e Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:38:24 -0700 Subject: [PATCH 44/98] update Go 1.25.6 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a4c35c76b1e..f12da20f021 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cli/cli/v2 -go 1.25.5 +go 1.25.6 require ( github.com/AlecAivazis/survey/v2 v2.3.7 From 484526da77ae13c25dcd7d0048e3edd6ac4364a2 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:45:58 -0700 Subject: [PATCH 45/98] Include name in reviewer display for existing review requests Fetch name field in reviewRequests GraphQL query and show as 'login (Name)'. --- api/queries_pr.go | 5 ++++- api/query_builder.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index fbed0611f91..aaa559e3410 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -326,7 +326,7 @@ func (r RequestedReviewer) LoginOrSlug() string { // DisplayName returns a user-friendly name for the reviewer. // For Copilot bot, returns "Copilot (AI)". For teams, returns "org/slug". -// For users, returns login (could be extended to show name if available). +// For users, returns "login (Name)" if name is available, otherwise just login. func (r RequestedReviewer) DisplayName() string { if r.TypeName == teamTypeName { return fmt.Sprintf("%s/%s", r.Organization.Login, r.Slug) @@ -334,6 +334,9 @@ func (r RequestedReviewer) DisplayName() string { if r.TypeName == "Bot" && r.Login == CopilotReviewerLogin { return "Copilot (AI)" } + if r.Name != "" { + return fmt.Sprintf("%s (%s)", r.Login, r.Name) + } return r.Login } diff --git a/api/query_builder.go b/api/query_builder.go index c36fee73611..766c2b4aa1b 100644 --- a/api/query_builder.go +++ b/api/query_builder.go @@ -102,7 +102,7 @@ var prReviewRequests = shortenQuery(` nodes { requestedReviewer { __typename, - ...on User{login}, + ...on User{login,name}, ...on Bot{login}, ...on Team{ organization{login} From c8b14098033d1cce2581410df5f58e2dd2022d85 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:54:04 -0700 Subject: [PATCH 46/98] Use unfiltered totalCount for reviewer 'more results' display Query aliased fields without search filter to get stable counts. --- api/queries_pr.go | 18 +++++++------ api/queries_pr_test.go | 57 +++++++++++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index aaa559e3410..bb5438fb3c9 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -973,6 +973,7 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, // Fetch 10 from each source to allow cascading quota to fill from available results. // Use a single query that includes organization.teams - if the owner is not an org, // we'll get a "Could not resolve to an Organization" error which we handle gracefully. + // We also fetch unfiltered total counts via aliases for the "X more" display. type responseData struct { Node struct { PullRequest struct { @@ -996,20 +997,24 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, } `graphql:"node(id: $id)"` Repository struct { Collaborators struct { - TotalCount int - Nodes []struct { + Nodes []struct { Login string Name string } } `graphql:"collaborators(first: 10, query: $query)"` + CollaboratorsTotalCount struct { + TotalCount int + } `graphql:"collaboratorsTotalCount: collaborators(first: 0)"` } `graphql:"repository(owner: $owner, name: $name)"` Organization struct { Teams struct { - TotalCount int - Nodes []struct { + Nodes []struct { Slug string } } `graphql:"teams(first: 10, query: $query)"` + TeamsTotalCount struct { + TotalCount int + } `graphql:"teamsTotalCount: teams(first: 0)"` } `graphql:"organization(login: $owner)"` } @@ -1098,9 +1103,8 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, } } - // MoreResults is the sum of collaborators and teams total counts - // (teams will be 0 for personal repos) - moreResults := result.Repository.Collaborators.TotalCount + result.Organization.Teams.TotalCount + // MoreResults uses unfiltered total counts (teams will be 0 for personal repos) + moreResults := result.Repository.CollaboratorsTotalCount.TotalCount + result.Organization.TeamsTotalCount.TotalCount return candidates, moreResults, nil } diff --git a/api/queries_pr_test.go b/api/queries_pr_test.go index cea2c201bcf..69dc505ca70 100644 --- a/api/queries_pr_test.go +++ b/api/queries_pr_test.go @@ -141,7 +141,7 @@ func Test_Logins(t *testing.T) { // mockReviewerResponse generates a GraphQL response for SuggestedReviewerActors tests. // It creates suggestions (s1, s2...), collaborators (c1, c2...), and teams (team1, team2...). -// totalCollabs and totalTeams set the TotalCount fields (for "more results" calculation). +// totalCollabs and totalTeams set the unfiltered TotalCount fields (for "more results" calculation). func mockReviewerResponse(suggestions, collabs, teams, totalCollabs, totalTeams int) string { var suggestionNodes, collabNodes, teamNodes []string @@ -161,11 +161,17 @@ func mockReviewerResponse(suggestions, collabs, teams, totalCollabs, totalTeams return fmt.Sprintf(`{ "data": { "node": {"suggestedReviewerActors": {"nodes": [%s]}}, - "repository": {"collaborators": {"totalCount": %d, "nodes": [%s]}}, - "organization": {"teams": {"totalCount": %d, "nodes": [%s]}} + "repository": { + "collaborators": {"nodes": [%s]}, + "collaboratorsTotalCount": {"totalCount": %d} + }, + "organization": { + "teams": {"nodes": [%s]}, + "teamsTotalCount": {"totalCount": %d} + } } - }`, strings.Join(suggestionNodes, ","), totalCollabs, strings.Join(collabNodes, ","), - totalTeams, strings.Join(teamNodes, ",")) + }`, strings.Join(suggestionNodes, ","), strings.Join(collabNodes, ","), totalCollabs, + strings.Join(teamNodes, ","), totalTeams) } func TestSuggestedReviewerActors(t *testing.T) { @@ -234,8 +240,14 @@ func TestSuggestedReviewerActors(t *testing.T) { {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}}, {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s2", "name": "S2"}} ]}}, - "repository": {"collaborators": {"totalCount": 5, "nodes": [{"login": "c1", "name": "C1"}]}}, - "organization": {"teams": {"totalCount": 3, "nodes": [{"slug": "team1"}]}} + "repository": { + "collaborators": {"nodes": [{"login": "c1", "name": "C1"}]}, + "collaboratorsTotalCount": {"totalCount": 5} + }, + "organization": { + "teams": {"nodes": [{"slug": "team1"}]}, + "teamsTotalCount": {"totalCount": 3} + } } }`)) }, @@ -254,11 +266,17 @@ func TestSuggestedReviewerActors(t *testing.T) { "node": {"suggestedReviewerActors": {"nodes": [ {"isAuthor": false, "reviewer": {"__typename": "User", "login": "shareduser", "name": "Shared"}} ]}}, - "repository": {"collaborators": {"totalCount": 10, "nodes": [ - {"login": "shareduser", "name": "Shared"}, - {"login": "c1", "name": "C1"} - ]}}, - "organization": {"teams": {"totalCount": 5, "nodes": [{"slug": "team1"}]}} + "repository": { + "collaborators": {"nodes": [ + {"login": "shareduser", "name": "Shared"}, + {"login": "c1", "name": "C1"} + ]}, + "collaboratorsTotalCount": {"totalCount": 10} + }, + "organization": { + "teams": {"nodes": [{"slug": "team1"}]}, + "teamsTotalCount": {"totalCount": 5} + } } }`)) }, @@ -276,7 +294,10 @@ func TestSuggestedReviewerActors(t *testing.T) { "node": {"suggestedReviewerActors": {"nodes": [ {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}} ]}}, - "repository": {"collaborators": {"totalCount": 3, "nodes": [{"login": "c1", "name": "C1"}]}}, + "repository": { + "collaborators": {"nodes": [{"login": "c1", "name": "C1"}]}, + "collaboratorsTotalCount": {"totalCount": 3} + }, "organization": null }, "errors": [{"message": "Could not resolve to an Organization with the login of 'OWNER'."}] @@ -297,8 +318,14 @@ func TestSuggestedReviewerActors(t *testing.T) { {"isAuthor": false, "reviewer": {"__typename": "Bot", "login": "copilot-pull-request-reviewer"}}, {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}} ]}}, - "repository": {"collaborators": {"totalCount": 5, "nodes": []}}, - "organization": {"teams": {"totalCount": 0, "nodes": []}} + "repository": { + "collaborators": {"nodes": []}, + "collaboratorsTotalCount": {"totalCount": 5} + }, + "organization": { + "teams": {"nodes": []}, + "teamsTotalCount": {"totalCount": 0} + } } }`)) }, From 7303c4448319df392085be667d44074c2ac2659b Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:17:25 -0700 Subject: [PATCH 47/98] Clarify Copilot assignee comment Reword the comment for CopilotAssigneeLogin to indicate it refers to Copilot when retrieved as an assignee. This updates wording from the previous 'Actor/assignable actors' phrasing for clarity; no code behavior changed. --- api/queries_repo.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/queries_repo.go b/api/queries_repo.go index 1e244df581f..d8ffa191dfc 100644 --- a/api/queries_repo.go +++ b/api/queries_repo.go @@ -1080,8 +1080,7 @@ func RepoProjects(client *Client, repo ghrepo.Interface) ([]RepoProject, error) return projects, nil } -// Expected login for Copilot when retrieved as an Actor -// This is returned from assignable actors and issue/pr assigned actors. +// Expected login for Copilot when retrieved as an assignee const CopilotAssigneeLogin = "copilot-swe-agent" // Expected login for Copilot when retrieved as a Pull Request Reviewer. From 4569aae0e4a3a254509602e1e7d64d793c1cf199 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:20:30 -0700 Subject: [PATCH 48/98] Clarify EditableReviewers comment Remove a redundant struct-level comment and update the DefaultLogins field comment in pkg/cmd/pr/shared/editable.go to more accurately describe its purpose: used to disambiguate actors from display names rather than to compute add/remove sets. --- pkg/cmd/pr/shared/editable.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index 732f16a4b53..3e92444be03 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -50,10 +50,9 @@ type EditableAssignees struct { } // EditableReviewers is a special case of EditableSlice. -// It tracks both display names (for UI) and logins (for API mutations). type EditableReviewers struct { EditableSlice - DefaultLogins []string // Logins for computing add/remove sets + DefaultLogins []string // For disambiguating actors from display names } // ProjectsV2 mutations require a mapping of an item ID to a project ID. From aac223ab71e96f366c8fc8829c39dbbd71554e97 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:29:11 -0700 Subject: [PATCH 49/98] Clarify assignee/reviewer fetching behavior Add clarifying comment in pkg/cmd/pr/edit/edit.go to note that missing assignee/reviewer search functions trigger a downstream fallback to legacy fetching. In pkg/cmd/pr/shared/editable.go remove a redundant line and add a TODO urging migration of non-interactive assignee updates to use the new logins input with ReplaceActorsForAssignable to avoid unnecessary fetching. These are comment and doc changes to clarify intent and future improvements. --- pkg/cmd/pr/edit/edit.go | 3 ++- pkg/cmd/pr/shared/editable.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index b91798daeba..d7ae05236f3 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -297,7 +297,8 @@ func editRun(opts *EditOptions) error { apiClient := api.NewClientFromHTTP(httpClient) // Wire up search functions for assignees and reviewers. - // Only enabled on github.com. + // When these aren't wired up, it triggers a downstream fallback + // to legacy reviewer/assignee fetching. if issueFeatures.ActorIsAssignable { editable.AssigneeSearchFunc = assigneeSearchFunc(apiClient, repo, &editable, pr.ID) editable.ReviewerSearchFunc = reviewerSearchFunc(apiClient, repo, &editable, pr.ID) diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index 3e92444be03..9c6629f5e17 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -465,7 +465,6 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, fetchAssignees := false if editable.Assignees.Edited { // Similar as above, this is likely an interactive flow if no Add/Remove slices are set. - // The addition here is that we also check for an assignee search func. // If we have a search func, we don't need to fetch assignees since we // assume that will be done dynamically in the prompting flow. if len(editable.Assignees.Add) == 0 && len(editable.Assignees.Remove) == 0 && editable.AssigneeSearchFunc == nil { @@ -473,6 +472,8 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, } // However, if we have Add/Remove operations (non-interactive flow), // we do need to fetch the assignees. + // TODO: KW noninteractive assignees need to migrate to directly use + // new logins input with ReplaceActorsForAssignable to prevent fetching. if len(editable.Assignees.Add) > 0 || len(editable.Assignees.Remove) > 0 { fetchAssignees = true } From a9a0486c7082c5898e2bd79a860e6b17d57f4d95 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:33:22 -0700 Subject: [PATCH 50/98] Clarify comment on reviewer API behavior Update comment in FetchOptions to specify that the APIs used for both GHES and GitHub.com accept user logins and team slugs directly, clarifying why non-interactive flows with Add/Remove don't need to fetch reviewers/teams. Comment-only change; no functional modifications. --- pkg/cmd/pr/shared/editable.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/pr/shared/editable.go b/pkg/cmd/pr/shared/editable.go index 9c6629f5e17..31b253d9b6d 100644 --- a/pkg/cmd/pr/shared/editable.go +++ b/pkg/cmd/pr/shared/editable.go @@ -459,7 +459,7 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable, fetchReviewers = true } // Note: Non-interactive flows (with Add/Remove) don't need to fetch reviewers/teams - // because the REST API accepts logins and team slugs directly. + // because the APIs in use for both GHES and GitHub.com accept user logins and team slugs directly. } fetchAssignees := false From b9a63c892310fd526564e79b25d83ed400e99ac1 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:02:25 -0700 Subject: [PATCH 51/98] Update third-party licenses --- third-party-licenses.darwin.md | 18 ++++----- third-party-licenses.linux.md | 18 ++++----- third-party-licenses.windows.md | 18 ++++----- .../github.com/go-openapi/jsonpointer/LICENSE | 1 - .../github.com/go-openapi/jsonpointer/NOTICE | 39 +++++++++++++++++++ .../go-openapi/jsonreference/NOTICE | 5 ++- 6 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 third-party/github.com/go-openapi/jsonpointer/NOTICE diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index c44040186af..e4695312c50 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -60,13 +60,13 @@ Some packages may only be included on certain architectures or operating systems - [github.com/go-logr/logr](https://pkg.go.dev/github.com/go-logr/logr) ([Apache-2.0](https://github.com/go-logr/logr/blob/v1.4.3/LICENSE)) - [github.com/go-logr/stdr](https://pkg.go.dev/github.com/go-logr/stdr) ([Apache-2.0](https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE)) - [github.com/go-openapi/analysis](https://pkg.go.dev/github.com/go-openapi/analysis) ([Apache-2.0](https://github.com/go-openapi/analysis/blob/v0.24.1/LICENSE)) -- [github.com/go-openapi/errors](https://pkg.go.dev/github.com/go-openapi/errors) ([Apache-2.0](https://github.com/go-openapi/errors/blob/v0.22.4/LICENSE)) -- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.22.1/LICENSE)) -- [github.com/go-openapi/jsonreference](https://pkg.go.dev/github.com/go-openapi/jsonreference) ([Apache-2.0](https://github.com/go-openapi/jsonreference/blob/v0.21.3/LICENSE)) +- [github.com/go-openapi/errors](https://pkg.go.dev/github.com/go-openapi/errors) ([Apache-2.0](https://github.com/go-openapi/errors/blob/v0.22.6/LICENSE)) +- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.22.4/LICENSE)) +- [github.com/go-openapi/jsonreference](https://pkg.go.dev/github.com/go-openapi/jsonreference) ([Apache-2.0](https://github.com/go-openapi/jsonreference/blob/v0.21.4/LICENSE)) - [github.com/go-openapi/loads](https://pkg.go.dev/github.com/go-openapi/loads) ([Apache-2.0](https://github.com/go-openapi/loads/blob/v0.23.2/LICENSE)) - [github.com/go-openapi/runtime](https://pkg.go.dev/github.com/go-openapi/runtime) ([Apache-2.0](https://github.com/go-openapi/runtime/blob/v0.29.2/LICENSE)) - [github.com/go-openapi/runtime/middleware/denco](https://pkg.go.dev/github.com/go-openapi/runtime/middleware/denco) ([MIT](https://github.com/go-openapi/runtime/blob/v0.29.2/middleware/denco/LICENSE)) -- [github.com/go-openapi/spec](https://pkg.go.dev/github.com/go-openapi/spec) ([Apache-2.0](https://github.com/go-openapi/spec/blob/v0.22.1/LICENSE)) +- [github.com/go-openapi/spec](https://pkg.go.dev/github.com/go-openapi/spec) ([Apache-2.0](https://github.com/go-openapi/spec/blob/v0.22.3/LICENSE)) - [github.com/go-openapi/strfmt](https://pkg.go.dev/github.com/go-openapi/strfmt) ([Apache-2.0](https://github.com/go-openapi/strfmt/blob/v0.25.0/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.25.4/LICENSE)) - [github.com/go-openapi/swag/cmdutils](https://pkg.go.dev/github.com/go-openapi/swag/cmdutils) ([Apache-2.0](https://github.com/go-openapi/swag/blob/cmdutils/v0.25.4/cmdutils/LICENSE)) @@ -141,7 +141,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) - [github.com/sigstore/protobuf-specs/gen/pb-go](https://pkg.go.dev/github.com/sigstore/protobuf-specs/gen/pb-go) ([Apache-2.0](https://github.com/sigstore/protobuf-specs/blob/v0.5.0/LICENSE)) - [github.com/sigstore/rekor-tiles/v2](https://pkg.go.dev/github.com/sigstore/rekor-tiles/v2) ([Apache-2.0](https://github.com/sigstore/rekor-tiles/blob/v2.0.1/LICENSE)) -- [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.4.3/LICENSE)) +- [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.5.0/LICENSE)) - [github.com/sigstore/sigstore-go/pkg](https://pkg.go.dev/github.com/sigstore/sigstore-go/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore-go/blob/v1.1.4/LICENSE)) - [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.4/LICENSE)) - [github.com/sigstore/timestamp-authority/v2/pkg/verification](https://pkg.go.dev/github.com/sigstore/timestamp-authority/v2/pkg/verification) ([Apache-2.0](https://github.com/sigstore/timestamp-authority/blob/v2.0.3/LICENSE)) @@ -173,14 +173,14 @@ Some packages may only be included on certain architectures or operating systems - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) -- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) +- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) -- [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/3a174f9686a8/googleapis/api/LICENSE)) -- [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/f26f9409b101/googleapis/rpc/LICENSE)) -- [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.77.0/LICENSE)) +- [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) +- [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) +- [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE)) - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index ad464e9f55f..edc8e268ddc 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -59,13 +59,13 @@ Some packages may only be included on certain architectures or operating systems - [github.com/go-logr/logr](https://pkg.go.dev/github.com/go-logr/logr) ([Apache-2.0](https://github.com/go-logr/logr/blob/v1.4.3/LICENSE)) - [github.com/go-logr/stdr](https://pkg.go.dev/github.com/go-logr/stdr) ([Apache-2.0](https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE)) - [github.com/go-openapi/analysis](https://pkg.go.dev/github.com/go-openapi/analysis) ([Apache-2.0](https://github.com/go-openapi/analysis/blob/v0.24.1/LICENSE)) -- [github.com/go-openapi/errors](https://pkg.go.dev/github.com/go-openapi/errors) ([Apache-2.0](https://github.com/go-openapi/errors/blob/v0.22.4/LICENSE)) -- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.22.1/LICENSE)) -- [github.com/go-openapi/jsonreference](https://pkg.go.dev/github.com/go-openapi/jsonreference) ([Apache-2.0](https://github.com/go-openapi/jsonreference/blob/v0.21.3/LICENSE)) +- [github.com/go-openapi/errors](https://pkg.go.dev/github.com/go-openapi/errors) ([Apache-2.0](https://github.com/go-openapi/errors/blob/v0.22.6/LICENSE)) +- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.22.4/LICENSE)) +- [github.com/go-openapi/jsonreference](https://pkg.go.dev/github.com/go-openapi/jsonreference) ([Apache-2.0](https://github.com/go-openapi/jsonreference/blob/v0.21.4/LICENSE)) - [github.com/go-openapi/loads](https://pkg.go.dev/github.com/go-openapi/loads) ([Apache-2.0](https://github.com/go-openapi/loads/blob/v0.23.2/LICENSE)) - [github.com/go-openapi/runtime](https://pkg.go.dev/github.com/go-openapi/runtime) ([Apache-2.0](https://github.com/go-openapi/runtime/blob/v0.29.2/LICENSE)) - [github.com/go-openapi/runtime/middleware/denco](https://pkg.go.dev/github.com/go-openapi/runtime/middleware/denco) ([MIT](https://github.com/go-openapi/runtime/blob/v0.29.2/middleware/denco/LICENSE)) -- [github.com/go-openapi/spec](https://pkg.go.dev/github.com/go-openapi/spec) ([Apache-2.0](https://github.com/go-openapi/spec/blob/v0.22.1/LICENSE)) +- [github.com/go-openapi/spec](https://pkg.go.dev/github.com/go-openapi/spec) ([Apache-2.0](https://github.com/go-openapi/spec/blob/v0.22.3/LICENSE)) - [github.com/go-openapi/strfmt](https://pkg.go.dev/github.com/go-openapi/strfmt) ([Apache-2.0](https://github.com/go-openapi/strfmt/blob/v0.25.0/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.25.4/LICENSE)) - [github.com/go-openapi/swag/cmdutils](https://pkg.go.dev/github.com/go-openapi/swag/cmdutils) ([Apache-2.0](https://github.com/go-openapi/swag/blob/cmdutils/v0.25.4/cmdutils/LICENSE)) @@ -141,7 +141,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) - [github.com/sigstore/protobuf-specs/gen/pb-go](https://pkg.go.dev/github.com/sigstore/protobuf-specs/gen/pb-go) ([Apache-2.0](https://github.com/sigstore/protobuf-specs/blob/v0.5.0/LICENSE)) - [github.com/sigstore/rekor-tiles/v2](https://pkg.go.dev/github.com/sigstore/rekor-tiles/v2) ([Apache-2.0](https://github.com/sigstore/rekor-tiles/blob/v2.0.1/LICENSE)) -- [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.4.3/LICENSE)) +- [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.5.0/LICENSE)) - [github.com/sigstore/sigstore-go/pkg](https://pkg.go.dev/github.com/sigstore/sigstore-go/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore-go/blob/v1.1.4/LICENSE)) - [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.4/LICENSE)) - [github.com/sigstore/timestamp-authority/v2/pkg/verification](https://pkg.go.dev/github.com/sigstore/timestamp-authority/v2/pkg/verification) ([Apache-2.0](https://github.com/sigstore/timestamp-authority/blob/v2.0.3/LICENSE)) @@ -173,14 +173,14 @@ Some packages may only be included on certain architectures or operating systems - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) -- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) +- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) -- [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/3a174f9686a8/googleapis/api/LICENSE)) -- [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/f26f9409b101/googleapis/rpc/LICENSE)) -- [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.77.0/LICENSE)) +- [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) +- [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) +- [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE)) - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 60b3bb675d7..cf7d8c40ed8 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -61,13 +61,13 @@ Some packages may only be included on certain architectures or operating systems - [github.com/go-logr/logr](https://pkg.go.dev/github.com/go-logr/logr) ([Apache-2.0](https://github.com/go-logr/logr/blob/v1.4.3/LICENSE)) - [github.com/go-logr/stdr](https://pkg.go.dev/github.com/go-logr/stdr) ([Apache-2.0](https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE)) - [github.com/go-openapi/analysis](https://pkg.go.dev/github.com/go-openapi/analysis) ([Apache-2.0](https://github.com/go-openapi/analysis/blob/v0.24.1/LICENSE)) -- [github.com/go-openapi/errors](https://pkg.go.dev/github.com/go-openapi/errors) ([Apache-2.0](https://github.com/go-openapi/errors/blob/v0.22.4/LICENSE)) -- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.22.1/LICENSE)) -- [github.com/go-openapi/jsonreference](https://pkg.go.dev/github.com/go-openapi/jsonreference) ([Apache-2.0](https://github.com/go-openapi/jsonreference/blob/v0.21.3/LICENSE)) +- [github.com/go-openapi/errors](https://pkg.go.dev/github.com/go-openapi/errors) ([Apache-2.0](https://github.com/go-openapi/errors/blob/v0.22.6/LICENSE)) +- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.22.4/LICENSE)) +- [github.com/go-openapi/jsonreference](https://pkg.go.dev/github.com/go-openapi/jsonreference) ([Apache-2.0](https://github.com/go-openapi/jsonreference/blob/v0.21.4/LICENSE)) - [github.com/go-openapi/loads](https://pkg.go.dev/github.com/go-openapi/loads) ([Apache-2.0](https://github.com/go-openapi/loads/blob/v0.23.2/LICENSE)) - [github.com/go-openapi/runtime](https://pkg.go.dev/github.com/go-openapi/runtime) ([Apache-2.0](https://github.com/go-openapi/runtime/blob/v0.29.2/LICENSE)) - [github.com/go-openapi/runtime/middleware/denco](https://pkg.go.dev/github.com/go-openapi/runtime/middleware/denco) ([MIT](https://github.com/go-openapi/runtime/blob/v0.29.2/middleware/denco/LICENSE)) -- [github.com/go-openapi/spec](https://pkg.go.dev/github.com/go-openapi/spec) ([Apache-2.0](https://github.com/go-openapi/spec/blob/v0.22.1/LICENSE)) +- [github.com/go-openapi/spec](https://pkg.go.dev/github.com/go-openapi/spec) ([Apache-2.0](https://github.com/go-openapi/spec/blob/v0.22.3/LICENSE)) - [github.com/go-openapi/strfmt](https://pkg.go.dev/github.com/go-openapi/strfmt) ([Apache-2.0](https://github.com/go-openapi/strfmt/blob/v0.25.0/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.25.4/LICENSE)) - [github.com/go-openapi/swag/cmdutils](https://pkg.go.dev/github.com/go-openapi/swag/cmdutils) ([Apache-2.0](https://github.com/go-openapi/swag/blob/cmdutils/v0.25.4/cmdutils/LICENSE)) @@ -144,7 +144,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) - [github.com/sigstore/protobuf-specs/gen/pb-go](https://pkg.go.dev/github.com/sigstore/protobuf-specs/gen/pb-go) ([Apache-2.0](https://github.com/sigstore/protobuf-specs/blob/v0.5.0/LICENSE)) - [github.com/sigstore/rekor-tiles/v2](https://pkg.go.dev/github.com/sigstore/rekor-tiles/v2) ([Apache-2.0](https://github.com/sigstore/rekor-tiles/blob/v2.0.1/LICENSE)) -- [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.4.3/LICENSE)) +- [github.com/sigstore/rekor/pkg](https://pkg.go.dev/github.com/sigstore/rekor/pkg) ([Apache-2.0](https://github.com/sigstore/rekor/blob/v1.5.0/LICENSE)) - [github.com/sigstore/sigstore-go/pkg](https://pkg.go.dev/github.com/sigstore/sigstore-go/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore-go/blob/v1.1.4/LICENSE)) - [github.com/sigstore/sigstore/pkg](https://pkg.go.dev/github.com/sigstore/sigstore/pkg) ([Apache-2.0](https://github.com/sigstore/sigstore/blob/v1.10.4/LICENSE)) - [github.com/sigstore/timestamp-authority/v2/pkg/verification](https://pkg.go.dev/github.com/sigstore/timestamp-authority/v2/pkg/verification) ([Apache-2.0](https://github.com/sigstore/timestamp-authority/blob/v2.0.3/LICENSE)) @@ -176,14 +176,14 @@ Some packages may only be included on certain architectures or operating systems - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) -- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) +- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) -- [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/3a174f9686a8/googleapis/api/LICENSE)) -- [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/f26f9409b101/googleapis/rpc/LICENSE)) -- [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.77.0/LICENSE)) +- [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) +- [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) +- [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE)) - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) diff --git a/third-party/github.com/go-openapi/jsonpointer/LICENSE b/third-party/github.com/go-openapi/jsonpointer/LICENSE index d6456956733..261eeb9e9f8 100644 --- a/third-party/github.com/go-openapi/jsonpointer/LICENSE +++ b/third-party/github.com/go-openapi/jsonpointer/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/third-party/github.com/go-openapi/jsonpointer/NOTICE b/third-party/github.com/go-openapi/jsonpointer/NOTICE new file mode 100644 index 00000000000..f3b51939a95 --- /dev/null +++ b/third-party/github.com/go-openapi/jsonpointer/NOTICE @@ -0,0 +1,39 @@ +Copyright 2015-2025 go-swagger maintainers + +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +This software library, github.com/go-openapi/jsonpointer, includes software developed +by the go-swagger and go-openapi maintainers ("go-swagger maintainers"). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. + +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0. + +This software is copied from, derived from, and inspired by other original software products. +It ships with copies of other software which license terms are recalled below. + +The original software was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com). + +github.com/sigh-399/jsonpointer +=========================== + +// SPDX-FileCopyrightText: Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) +// SPDX-License-Identifier: Apache-2.0 + +Copyright 2013 sigu-399 ( https://github.com/sigu-399 ) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third-party/github.com/go-openapi/jsonreference/NOTICE b/third-party/github.com/go-openapi/jsonreference/NOTICE index f9ad7e0f7a0..f3b51939a95 100644 --- a/third-party/github.com/go-openapi/jsonreference/NOTICE +++ b/third-party/github.com/go-openapi/jsonreference/NOTICE @@ -8,12 +8,15 @@ by the go-swagger and go-openapi maintainers ("go-swagger maintainers"). Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0. + This software is copied from, derived from, and inspired by other original software products. It ships with copies of other software which license terms are recalled below. -The original sofware was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com). +The original software was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com). github.com/sigh-399/jsonpointer =========================== From 291637aabfafbf1e0ade12c164ed20b76a00943b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:01:55 +0000 Subject: [PATCH 52/98] chore(deps): bump github.com/theupdateframework/go-tuf/v2 Bumps [github.com/theupdateframework/go-tuf/v2](https://github.com/theupdateframework/go-tuf) from 2.3.1 to 2.4.1. - [Release notes](https://github.com/theupdateframework/go-tuf/releases) - [Commits](https://github.com/theupdateframework/go-tuf/compare/v2.3.1...v2.4.1) --- updated-dependencies: - dependency-name: github.com/theupdateframework/go-tuf/v2 dependency-version: 2.4.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6652b163f28..c3c71641ea5 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - github.com/theupdateframework/go-tuf/v2 v2.3.1 + github.com/theupdateframework/go-tuf/v2 v2.4.1 github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/yuin/goldmark v1.7.16 github.com/zalando/go-keyring v0.2.6 diff --git a/go.sum b/go.sum index bd3fe92d474..b8120cb1419 100644 --- a/go.sum +++ b/go.sum @@ -507,8 +507,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= -github.com/theupdateframework/go-tuf/v2 v2.3.1 h1:fReZUTLvPdqIL8Rd9xEKPmaxig8GIXe0kS4RSEaRfaM= -github.com/theupdateframework/go-tuf/v2 v2.3.1/go.mod h1:9S0Srkf3c13FelsOyt5OyG3ZZDq9OJDA4IILavrt72Y= +github.com/theupdateframework/go-tuf/v2 v2.4.1 h1:K6ewW064rKZCPkRo1W/CTbTtm/+IB4+coG1iNURAGCw= +github.com/theupdateframework/go-tuf/v2 v2.4.1/go.mod h1:Nex2enPVYDFCklrnbTzl3OVwD7fgIAj0J5++z/rvCj8= github.com/thlib/go-timezone-local v0.0.6 h1:Ii3QJ4FhosL/+eCZl6Hsdr4DDU4tfevNoV83yAEo2tU= github.com/thlib/go-timezone-local v0.0.6/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI= From 188dfde7aacd1c31c3a973ddf6d8d9d962b34693 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:23:45 +0000 Subject: [PATCH 53/98] Update third party licenses --- third-party-licenses.darwin.md | 2 +- third-party-licenses.linux.md | 2 +- third-party-licenses.windows.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index a578dce6e8a..77022f713a8 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -151,7 +151,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/stretchr/objx](https://pkg.go.dev/github.com/stretchr/objx) ([MIT](https://github.com/stretchr/objx/blob/v0.5.2/LICENSE)) - [github.com/stretchr/testify](https://pkg.go.dev/github.com/stretchr/testify) ([MIT](https://github.com/stretchr/testify/blob/v1.11.1/LICENSE)) -- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.1/LICENSE)) +- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.4.1/LICENSE)) - [github.com/thlib/go-timezone-local/tzlocal](https://pkg.go.dev/github.com/thlib/go-timezone-local/tzlocal) ([Unlicense](https://github.com/thlib/go-timezone-local/blob/v0.0.6/LICENSE)) - [github.com/transparency-dev/formats/log](https://pkg.go.dev/github.com/transparency-dev/formats/log) ([Apache-2.0](https://github.com/transparency-dev/formats/blob/404c0d5b696c/LICENSE)) - [github.com/transparency-dev/merkle](https://pkg.go.dev/github.com/transparency-dev/merkle) ([Apache-2.0](https://github.com/transparency-dev/merkle/blob/v0.0.2/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 5e24cd1de8c..dbd16f79f96 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -151,7 +151,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/stretchr/objx](https://pkg.go.dev/github.com/stretchr/objx) ([MIT](https://github.com/stretchr/objx/blob/v0.5.2/LICENSE)) - [github.com/stretchr/testify](https://pkg.go.dev/github.com/stretchr/testify) ([MIT](https://github.com/stretchr/testify/blob/v1.11.1/LICENSE)) -- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.1/LICENSE)) +- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.4.1/LICENSE)) - [github.com/thlib/go-timezone-local/tzlocal](https://pkg.go.dev/github.com/thlib/go-timezone-local/tzlocal) ([Unlicense](https://github.com/thlib/go-timezone-local/blob/v0.0.6/LICENSE)) - [github.com/transparency-dev/formats/log](https://pkg.go.dev/github.com/transparency-dev/formats/log) ([Apache-2.0](https://github.com/transparency-dev/formats/blob/404c0d5b696c/LICENSE)) - [github.com/transparency-dev/merkle](https://pkg.go.dev/github.com/transparency-dev/merkle) ([Apache-2.0](https://github.com/transparency-dev/merkle/blob/v0.0.2/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index a37646ef313..92b24aee1d9 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -154,7 +154,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/stretchr/objx](https://pkg.go.dev/github.com/stretchr/objx) ([MIT](https://github.com/stretchr/objx/blob/v0.5.2/LICENSE)) - [github.com/stretchr/testify](https://pkg.go.dev/github.com/stretchr/testify) ([MIT](https://github.com/stretchr/testify/blob/v1.11.1/LICENSE)) -- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.3.1/LICENSE)) +- [github.com/theupdateframework/go-tuf/v2/metadata](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2/metadata) ([Apache-2.0](https://github.com/theupdateframework/go-tuf/blob/v2.4.1/LICENSE)) - [github.com/thlib/go-timezone-local/tzlocal](https://pkg.go.dev/github.com/thlib/go-timezone-local/tzlocal) ([Unlicense](https://github.com/thlib/go-timezone-local/blob/v0.0.6/LICENSE)) - [github.com/transparency-dev/formats/log](https://pkg.go.dev/github.com/transparency-dev/formats/log) ([Apache-2.0](https://github.com/transparency-dev/formats/blob/404c0d5b696c/LICENSE)) - [github.com/transparency-dev/merkle](https://pkg.go.dev/github.com/transparency-dev/merkle) ([Apache-2.0](https://github.com/transparency-dev/merkle/blob/v0.0.2/LICENSE)) From 915750902ccae56dbd3ec94ebfedd9d7c34433b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:35:17 +0000 Subject: [PATCH 54/98] chore(deps): bump github.com/gdamore/tcell/v2 from 2.13.4 to 2.13.7 Bumps [github.com/gdamore/tcell/v2](https://github.com/gdamore/tcell) from 2.13.4 to 2.13.7. - [Release notes](https://github.com/gdamore/tcell/releases) - [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv3.md) - [Commits](https://github.com/gdamore/tcell/compare/v2.13.4...v2.13.7) --- updated-dependencies: - dependency-name: github.com/gdamore/tcell/v2 dependency-version: 2.13.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c3c71641ea5..3cab99c1618 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea github.com/distribution/reference v0.6.0 github.com/gabriel-vasile/mimetype v1.4.11 - github.com/gdamore/tcell/v2 v2.13.4 + github.com/gdamore/tcell/v2 v2.13.8 github.com/golang/snappy v1.0.0 github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.20.7 diff --git a/go.sum b/go.sum index b8120cb1419..e10e5a7d9e0 100644 --- a/go.sum +++ b/go.sum @@ -197,8 +197,8 @@ github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= -github.com/gdamore/tcell/v2 v2.13.4 h1:k4fdtdHGvLsLr2RttPnWEGTZEkEuTaL+rL6AOVFyRWU= -github.com/gdamore/tcell/v2 v2.13.4/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo= +github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU= +github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= From bc689e358a6815cc8ba489745ec8d6d1f4ed358f Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:39:31 -0700 Subject: [PATCH 55/98] update third party licenses --- third-party-licenses.darwin.md | 2 +- third-party-licenses.linux.md | 2 +- third-party-licenses.windows.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 77022f713a8..22041ae3119 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -56,7 +56,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/fatih/color](https://pkg.go.dev/github.com/fatih/color) ([MIT](https://github.com/fatih/color/blob/v1.18.0/LICENSE.md)) - [github.com/gabriel-vasile/mimetype](https://pkg.go.dev/github.com/gabriel-vasile/mimetype) ([MIT](https://github.com/gabriel-vasile/mimetype/blob/v1.4.11/LICENSE)) - [github.com/gdamore/encoding](https://pkg.go.dev/github.com/gdamore/encoding) ([Apache-2.0](https://github.com/gdamore/encoding/blob/v1.0.1/LICENSE)) -- [github.com/gdamore/tcell/v2](https://pkg.go.dev/github.com/gdamore/tcell/v2) ([Apache-2.0](https://github.com/gdamore/tcell/blob/v2.13.4/LICENSE)) +- [github.com/gdamore/tcell/v2](https://pkg.go.dev/github.com/gdamore/tcell/v2) ([Apache-2.0](https://github.com/gdamore/tcell/blob/v2.13.8/LICENSE)) - [github.com/go-logr/logr](https://pkg.go.dev/github.com/go-logr/logr) ([Apache-2.0](https://github.com/go-logr/logr/blob/v1.4.3/LICENSE)) - [github.com/go-logr/stdr](https://pkg.go.dev/github.com/go-logr/stdr) ([Apache-2.0](https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE)) - [github.com/go-openapi/analysis](https://pkg.go.dev/github.com/go-openapi/analysis) ([Apache-2.0](https://github.com/go-openapi/analysis/blob/v0.24.1/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index dbd16f79f96..5f70fddabc2 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -55,7 +55,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/fatih/color](https://pkg.go.dev/github.com/fatih/color) ([MIT](https://github.com/fatih/color/blob/v1.18.0/LICENSE.md)) - [github.com/gabriel-vasile/mimetype](https://pkg.go.dev/github.com/gabriel-vasile/mimetype) ([MIT](https://github.com/gabriel-vasile/mimetype/blob/v1.4.11/LICENSE)) - [github.com/gdamore/encoding](https://pkg.go.dev/github.com/gdamore/encoding) ([Apache-2.0](https://github.com/gdamore/encoding/blob/v1.0.1/LICENSE)) -- [github.com/gdamore/tcell/v2](https://pkg.go.dev/github.com/gdamore/tcell/v2) ([Apache-2.0](https://github.com/gdamore/tcell/blob/v2.13.4/LICENSE)) +- [github.com/gdamore/tcell/v2](https://pkg.go.dev/github.com/gdamore/tcell/v2) ([Apache-2.0](https://github.com/gdamore/tcell/blob/v2.13.8/LICENSE)) - [github.com/go-logr/logr](https://pkg.go.dev/github.com/go-logr/logr) ([Apache-2.0](https://github.com/go-logr/logr/blob/v1.4.3/LICENSE)) - [github.com/go-logr/stdr](https://pkg.go.dev/github.com/go-logr/stdr) ([Apache-2.0](https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE)) - [github.com/go-openapi/analysis](https://pkg.go.dev/github.com/go-openapi/analysis) ([Apache-2.0](https://github.com/go-openapi/analysis/blob/v0.24.1/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 92b24aee1d9..0b53e20a597 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -57,7 +57,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/fatih/color](https://pkg.go.dev/github.com/fatih/color) ([MIT](https://github.com/fatih/color/blob/v1.18.0/LICENSE.md)) - [github.com/gabriel-vasile/mimetype](https://pkg.go.dev/github.com/gabriel-vasile/mimetype) ([MIT](https://github.com/gabriel-vasile/mimetype/blob/v1.4.11/LICENSE)) - [github.com/gdamore/encoding](https://pkg.go.dev/github.com/gdamore/encoding) ([Apache-2.0](https://github.com/gdamore/encoding/blob/v1.0.1/LICENSE)) -- [github.com/gdamore/tcell/v2](https://pkg.go.dev/github.com/gdamore/tcell/v2) ([Apache-2.0](https://github.com/gdamore/tcell/blob/v2.13.4/LICENSE)) +- [github.com/gdamore/tcell/v2](https://pkg.go.dev/github.com/gdamore/tcell/v2) ([Apache-2.0](https://github.com/gdamore/tcell/blob/v2.13.8/LICENSE)) - [github.com/go-logr/logr](https://pkg.go.dev/github.com/go-logr/logr) ([Apache-2.0](https://github.com/go-logr/logr/blob/v1.4.3/LICENSE)) - [github.com/go-logr/stdr](https://pkg.go.dev/github.com/go-logr/stdr) ([Apache-2.0](https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE)) - [github.com/go-openapi/analysis](https://pkg.go.dev/github.com/go-openapi/analysis) ([Apache-2.0](https://github.com/go-openapi/analysis/blob/v0.24.1/LICENSE)) From ebf932a043d26fd8b6a22ee7c2ed6dfa12b0be17 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:04:20 -0700 Subject: [PATCH 56/98] Address PR review comments Address PR review comments: code consistency and DRY improvements - Add botTypeName const for consistency with teamTypeName - Create extractTeamSlugs helper using strings.SplitN to simplify team slug extraction logic - Replace duplicate code in AddPullRequestReviews and RemovePullRequestReviews with extractTeamSlugs helper - Fix ClientMutationId naming with explicit graphql tag for consistency with other mutations in the codebase --- api/queries_pr.go | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index bb5438fb3c9..90a9567b09d 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -317,6 +317,9 @@ type RequestedReviewer struct { } `json:"organization"` } +const teamTypeName = "Team" +const botTypeName = "Bot" + func (r RequestedReviewer) LoginOrSlug() string { if r.TypeName == teamTypeName { return fmt.Sprintf("%s/%s", r.Organization.Login, r.Slug) @@ -331,7 +334,7 @@ func (r RequestedReviewer) DisplayName() string { if r.TypeName == teamTypeName { return fmt.Sprintf("%s/%s", r.Organization.Login, r.Slug) } - if r.TypeName == "Bot" && r.Login == CopilotReviewerLogin { + if r.TypeName == botTypeName && r.Login == CopilotReviewerLogin { return "Copilot (AI)" } if r.Name != "" { @@ -340,8 +343,6 @@ func (r RequestedReviewer) DisplayName() string { return r.Login } -const teamTypeName = "Team" - func (r ReviewRequests) Logins() []string { logins := make([]string, len(r.Nodes)) for i, r := range r.Nodes { @@ -657,6 +658,20 @@ func CreatePullRequest(client *Client, repo *Repository, params map[string]inter return pr, nil } +// extractTeamSlugs extracts just the slug portion from team identifiers. +// Team identifiers can be in "org/slug" format; this returns just the slug. +func extractTeamSlugs(teams []string) []string { + slugs := make([]string, 0, len(teams)) + for _, t := range teams { + if t == "" { + continue + } + s := strings.SplitN(t, "/", 2) + slugs = append(slugs, s[len(s)-1]) + } + return slugs +} + // AddPullRequestReviews adds the given user and team reviewers to a pull request using the REST API. // Team identifiers can be in "org/slug" format. func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, users, teams []string) error { @@ -669,16 +684,6 @@ func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, users = []string{} } - // Extract just the slug from org/slug format - teamSlugs := make([]string, 0, len(teams)) - for _, t := range teams { - if idx := strings.Index(t, "/"); idx >= 0 { - teamSlugs = append(teamSlugs, t[idx+1:]) - } else if t != "" { - teamSlugs = append(teamSlugs, t) - } - } - path := fmt.Sprintf( "repos/%s/%s/pulls/%d/requested_reviewers", url.PathEscape(repo.RepoOwner()), @@ -690,7 +695,7 @@ func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, TeamReviewers []string `json:"team_reviewers"` }{ Reviewers: users, - TeamReviewers: teamSlugs, + TeamReviewers: extractTeamSlugs(teams), } buf := &bytes.Buffer{} if err := json.NewEncoder(buf).Encode(body); err != nil { @@ -712,16 +717,6 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in users = []string{} } - // Extract just the slug from org/slug format - teamSlugs := make([]string, 0, len(teams)) - for _, t := range teams { - if idx := strings.Index(t, "/"); idx >= 0 { - teamSlugs = append(teamSlugs, t[idx+1:]) - } else if t != "" { - teamSlugs = append(teamSlugs, t) - } - } - path := fmt.Sprintf( "repos/%s/%s/pulls/%d/requested_reviewers", url.PathEscape(repo.RepoOwner()), @@ -733,7 +728,7 @@ func RemovePullRequestReviews(client *Client, repo ghrepo.Interface, prNumber in TeamReviewers []string `json:"team_reviewers"` }{ Reviewers: users, - TeamReviewers: teamSlugs, + TeamReviewers: extractTeamSlugs(teams), } buf := &bytes.Buffer{} if err := json.NewEncoder(buf).Encode(body); err != nil { @@ -758,7 +753,7 @@ func RequestReviewsByLogin(client *Client, repo ghrepo.Interface, prID string, u var mutation struct { RequestReviewsByLogin struct { - ClientMutationID string + ClientMutationId string `graphql:"clientMutationId"` } `graphql:"requestReviewsByLogin(input: $input)"` } From c0febc1ac864a8beca58f19b59ed41ebd9e0b7ac Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:18:49 -0700 Subject: [PATCH 57/98] Add toGitHubV4Strings helper to reduce code duplication --- api/queries_pr.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/api/queries_pr.go b/api/queries_pr.go index 90a9567b09d..1bc5ddb55d7 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -672,6 +672,16 @@ func extractTeamSlugs(teams []string) []string { return slugs } +// toGitHubV4Strings converts a string slice to a githubv4.String slice, +// optionally appending a suffix to each element. +func toGitHubV4Strings(strs []string, suffix string) []githubv4.String { + result := make([]githubv4.String, len(strs)) + for i, s := range strs { + result[i] = githubv4.String(s + suffix) + } + return result +} + // AddPullRequestReviews adds the given user and team reviewers to a pull request using the REST API. // Team identifiers can be in "org/slug" format. func AddPullRequestReviews(client *Client, repo ghrepo.Interface, prNumber int, users, teams []string) error { @@ -770,23 +780,14 @@ func RequestReviewsByLogin(client *Client, repo ghrepo.Interface, prID string, u Union: githubv4.Boolean(union), } - userLoginValues := make([]githubv4.String, len(userLogins)) - for i, l := range userLogins { - userLoginValues[i] = githubv4.String(l) - } + userLoginValues := toGitHubV4Strings(userLogins, "") input.UserLogins = &userLoginValues - botLoginValues := make([]githubv4.String, len(botLogins)) - for i, l := range botLogins { - // Bot logins require the [bot] suffix for the mutation - botLoginValues[i] = githubv4.String(l + "[bot]") - } + // Bot logins require the [bot] suffix for the mutation + botLoginValues := toGitHubV4Strings(botLogins, "[bot]") input.BotLogins = &botLoginValues - teamSlugValues := make([]githubv4.String, len(teamSlugs)) - for i, s := range teamSlugs { - teamSlugValues[i] = githubv4.String(s) - } + teamSlugValues := toGitHubV4Strings(teamSlugs, "") input.TeamSlugs = &teamSlugValues variables := map[string]interface{}{ From 28ed4bdbf059e9471ae5d7682b753a58e87cce26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:17:42 +0000 Subject: [PATCH 58/98] chore(deps): bump golang.org/x/text from 0.32.0 to 0.33.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.32.0 to 0.33.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.32.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.33.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 3cab99c1618..f6e3a044638 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( golang.org/x/crypto v0.46.0 golang.org/x/sync v0.19.0 golang.org/x/term v0.38.0 - golang.org/x/text v0.32.0 + golang.org/x/text v0.33.0 google.golang.org/grpc v1.78.0 google.golang.org/protobuf v1.36.11 gopkg.in/h2non/gock.v1 v1.1.2 @@ -177,10 +177,10 @@ require ( go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.30.0 // indirect + golang.org/x/mod v0.31.0 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.39.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect ) diff --git a/go.sum b/go.sum index e10e5a7d9e0..e7a288ce608 100644 --- a/go.sum +++ b/go.sum @@ -578,8 +578,8 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -617,16 +617,16 @@ 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= From ad305b721753c57fb6f80df7e93441ebc46983c3 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:19:27 -0700 Subject: [PATCH 59/98] update third party licenses --- third-party-licenses.darwin.md | 4 ++-- third-party-licenses.linux.md | 4 ++-- third-party-licenses.windows.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 22041ae3119..d52c1359904 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -172,12 +172,12 @@ Some packages may only be included on certain architectures or operating systems - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([BSD-3-Clause](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) -- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) +- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) -- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) +- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) - [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 5f70fddabc2..a051627f8a5 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -172,12 +172,12 @@ Some packages may only be included on certain architectures or operating systems - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([BSD-3-Clause](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) -- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) +- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) -- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) +- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) - [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 0b53e20a597..ca38b8d84dc 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -175,12 +175,12 @@ Some packages may only be included on certain architectures or operating systems - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([BSD-3-Clause](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) -- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) +- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) -- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) +- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) - [google.golang.org/grpc](https://pkg.go.dev/google.golang.org/grpc) ([Apache-2.0](https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE)) From 4de27314a0a7fe6795375a5e6e999d58f58c8241 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:28:20 +0000 Subject: [PATCH 60/98] chore(deps): bump golang.org/x/term from 0.38.0 to 0.39.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.38.0 to 0.39.0. - [Commits](https://github.com/golang/term/compare/v0.38.0...v0.39.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.39.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index f6e3a044638..0075b3a05f9 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/zalando/go-keyring v0.2.6 golang.org/x/crypto v0.46.0 golang.org/x/sync v0.19.0 - golang.org/x/term v0.38.0 + golang.org/x/term v0.39.0 golang.org/x/text v0.33.0 google.golang.org/grpc v1.78.0 google.golang.org/protobuf v1.36.11 @@ -179,7 +179,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.31.0 // indirect golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect + golang.org/x/sys v0.40.0 // indirect golang.org/x/tools v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect diff --git a/go.sum b/go.sum index e7a288ce608..c31e042c479 100644 --- a/go.sum +++ b/go.sum @@ -604,13 +604,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 0a2841fb9d007b186971bdb1f10c861c8b1f35e2 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:30:46 -0700 Subject: [PATCH 61/98] update third party licenses --- third-party-licenses.darwin.md | 4 ++-- third-party-licenses.linux.md | 4 ++-- third-party-licenses.windows.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index d52c1359904..3f30e42f9c9 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -175,8 +175,8 @@ Some packages may only be included on certain architectures or operating systems - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) -- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) -- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) +- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) +- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index a051627f8a5..3531ff7fae5 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -175,8 +175,8 @@ Some packages may only be included on certain architectures or operating systems - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) -- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) -- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) +- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) +- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index ca38b8d84dc..0862134a427 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -178,8 +178,8 @@ Some packages may only be included on certain architectures or operating systems - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) -- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE)) -- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) +- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) +- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [google.golang.org/genproto/googleapis/api](https://pkg.go.dev/google.golang.org/genproto/googleapis/api) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE)) - [google.golang.org/genproto/googleapis/rpc/status](https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/status) ([Apache-2.0](https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE)) From 18c67c9ccc0f9cd4e2f109e2b36fda1fec5f71f3 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:44:34 -0700 Subject: [PATCH 62/98] bump go to 1.25.7 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0075b3a05f9..63ea141f8d1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cli/cli/v2 -go 1.25.6 +go 1.25.7 require ( github.com/AlecAivazis/survey/v2 v2.3.7 From 35828f44cd3567affac2bb59fbcab1cada17223d Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:52:24 -0700 Subject: [PATCH 63/98] Add manual dispatch to bump-go workflow Enable manual runs of the Bump Go workflow by adding the workflow_dispatch trigger alongside the existing scheduled cron. This allows maintainers to trigger the bump process on-demand while keeping the daily 3 AM UTC schedule intact. --- .github/workflows/bump-go.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/bump-go.yml b/.github/workflows/bump-go.yml index 827bbc6088f..f9647b21064 100644 --- a/.github/workflows/bump-go.yml +++ b/.github/workflows/bump-go.yml @@ -2,6 +2,7 @@ name: Bump Go on: schedule: - cron: "0 3 * * *" # 3 AM UTC + workflow_dispatch: permissions: contents: write pull-requests: write From 7fb3e1ad80f690d26e14b623a2a581e97085418a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:54:51 +0000 Subject: [PATCH 64/98] chore(deps): bump golang.org/x/crypto from 0.46.0 to 0.47.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.46.0 to 0.47.0. - [Commits](https://github.com/golang/crypto/compare/v0.46.0...v0.47.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 63ea141f8d1..e639e60335d 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/yuin/goldmark v1.7.16 github.com/zalando/go-keyring v0.2.6 - golang.org/x/crypto v0.46.0 + golang.org/x/crypto v0.47.0 golang.org/x/sync v0.19.0 golang.org/x/term v0.39.0 golang.org/x/text v0.33.0 diff --git a/go.sum b/go.sum index c31e042c479..1bf25eb4c4d 100644 --- a/go.sum +++ b/go.sum @@ -572,8 +572,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= From cde6edcba2bb705a5478847ea9bfb8ea128a5d95 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:23:06 -0700 Subject: [PATCH 65/98] update third party licenses --- third-party-licenses.darwin.md | 2 +- third-party-licenses.linux.md | 2 +- third-party-licenses.windows.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 3f30e42f9c9..818c86cdabc 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -171,7 +171,7 @@ Some packages may only be included on certain architectures or operating systems - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([Apache-2.0](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([BSD-3-Clause](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) -- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) +- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE)) - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 3531ff7fae5..fa8a3478ebc 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -171,7 +171,7 @@ Some packages may only be included on certain architectures or operating systems - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([Apache-2.0](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([BSD-3-Clause](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) -- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) +- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE)) - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 0862134a427..5eada97f1a6 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -174,7 +174,7 @@ Some packages may only be included on certain architectures or operating systems - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([Apache-2.0](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.opentelemetry.io/otel/trace](https://pkg.go.dev/go.opentelemetry.io/otel/trace) ([BSD-3-Clause](https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE)) - [go.yaml.in/yaml/v3](https://pkg.go.dev/go.yaml.in/yaml/v3) ([MIT](https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE)) -- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) +- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE)) - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.31.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) From 7448aed8ab3427d522e72416989e2e025c58ce8e Mon Sep 17 00:00:00 2001 From: William Martin Date: Thu, 12 Feb 2026 16:56:14 +0100 Subject: [PATCH 66/98] fork default branch only in pr create --- pkg/cmd/pr/create/create.go | 2 +- pkg/cmd/pr/create/create_test.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 5b5d45f9c0a..8411cdada75 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -1128,7 +1128,7 @@ func handlePush(opts CreateOptions, ctx CreateContext) error { forkableRefs, requiresFork := refs.(forkableRefs) if requiresFork { opts.IO.StartProgressIndicator() - forkedRepo, err := api.ForkRepo(ctx.Client, forkableRefs.BaseRepo(), "", "", false) + forkedRepo, err := api.ForkRepo(ctx.Client, forkableRefs.BaseRepo(), "", "", true) opts.IO.StopProgressIndicator() if err != nil { return fmt.Errorf("error forking repo: %w", err) diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index 5ccffd7a8a8..67286902538 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -894,11 +894,13 @@ func Test_createRun(t *testing.T) { httpmock.StringResponse(`{"data": {"viewer": {"login": "monalisa"} } }`)) reg.Register( httpmock.REST("POST", "repos/OWNER/REPO/forks"), - httpmock.StatusStringResponse(201, ` + httpmock.RESTPayload(201, ` { "node_id": "NODEID", "name": "REPO", "owner": {"login": "monalisa"} - }`)) + }`, func(payload map[string]interface{}) { + assert.Equal(t, true, payload["default_branch_only"]) + })) reg.Register( httpmock.GraphQL(`mutation PullRequestCreate\b`), httpmock.GraphQLMutation(` From a5e97b5b6c98e66c862201e980051f479b04d230 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:32:45 -0700 Subject: [PATCH 67/98] Migrate issue triage workflows to shared workflows --- .github/workflows/feature-request-comment.yml | 36 --------- .github/workflows/issueauto.yml | 25 ------- .../scripts/spam-detection/eval-prompts.yml | 2 +- .github/workflows/stale-issues.yml | 2 +- .github/workflows/triage-issues.yml | 61 +++++++++++++++ .github/workflows/triage-scheduled-tasks.yml | 13 ++++ .github/workflows/triage.yml | 74 +++---------------- docs/triage.md | 14 ++-- 8 files changed, 94 insertions(+), 133 deletions(-) delete mode 100644 .github/workflows/feature-request-comment.yml delete mode 100644 .github/workflows/issueauto.yml create mode 100644 .github/workflows/triage-issues.yml create mode 100644 .github/workflows/triage-scheduled-tasks.yml diff --git a/.github/workflows/feature-request-comment.yml b/.github/workflows/feature-request-comment.yml deleted file mode 100644 index 8426d7af2d9..00000000000 --- a/.github/workflows/feature-request-comment.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Add feature-request comment -on: - issues: - types: - - labeled - -permissions: - issues: write - -jobs: - add-comment-to-feature-request-issues: - if: github.event.label.name == 'enhancement' - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - NUMBER: ${{ github.event.issue.number }} - BODY: > - Thank you for your issue! We have categorized it as a feature request, - and it has been added to our backlog. In doing so, **we are not - committing to implementing this feature at this time**, but, we will - consider it for future releases based on community feedback and our own - product roadmap. - - - Unless you see the - https://github.com/cli/cli/labels/help%20wanted label, we are - not currently looking for external contributions for this feature. - - - **If you come across this issue and would like to see it implemented, - please add a thumbs up!** This will help us prioritize the feature. - Please only comment if you have additional information or viewpoints to - contribute. - steps: - - run: gh issue comment "$NUMBER" --body "$BODY" diff --git a/.github/workflows/issueauto.yml b/.github/workflows/issueauto.yml deleted file mode 100644 index cfdcff7644a..00000000000 --- a/.github/workflows/issueauto.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Issue Automation -on: - issues: - types: [opened] - -permissions: - contents: none - issues: write - -jobs: - issue-auto: - runs-on: ubuntu-latest - environment: cli-automation - steps: - - name: label incoming issue - env: - GH_REPO: ${{ github.repository }} - GH_TOKEN: ${{ secrets.AUTOMATION_TOKEN }} - ISSUENUM: ${{ github.event.issue.number }} - ISSUEAUTHOR: ${{ github.event.issue.user.login }} - run: | - if ! gh api orgs/cli/public_members/$ISSUEAUTHOR --silent 2>/dev/null - then - gh issue edit $ISSUENUM --add-label "needs-triage" - fi \ No newline at end of file diff --git a/.github/workflows/scripts/spam-detection/eval-prompts.yml b/.github/workflows/scripts/spam-detection/eval-prompts.yml index 15c61ff76f3..6911013882f 100644 --- a/.github/workflows/scripts/spam-detection/eval-prompts.yml +++ b/.github/workflows/scripts/spam-detection/eval-prompts.yml @@ -918,7 +918,7 @@ testData: We have an automation to nudge on issues waiting for user info (like after one week), and close the issue if there's no further activity (like after one more week). - - Automatically add the stale label to issues labelled needs-user-input after 30 days of inactivity. When the stale label is added, also post a comment to the issue explaining what this means: the issue will close after 30 days of inactivity; contributors can comment on the issue to remove the stale label and keep it open. Maintainers can also add the keep label to make the stale automation ignore that issue. + - Automatically add the stale label to issues labelled more-info-needed after 30 days of inactivity. When the stale label is added, also post a comment to the issue explaining what this means: the issue will close after 30 days of inactivity; contributors can comment on the issue to remove the stale label and keep it open. Maintainers can also add the keep label to make the stale automation ignore that issue. - Automatically close issues labelled stale after they have been stale for 30 days. When the issue is closed, add a comment explaining why this happened. Encourage them to leave a comment if the close was done in error. - The above automation should only act on new issues after the date of the automation's implementation. diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml index 543a909c8a7..d4c5967aa43 100644 --- a/.github/workflows/stale-issues.yml +++ b/.github/workflows/stale-issues.yml @@ -15,7 +15,7 @@ jobs: start-date: "2025-07-10T00:00:00Z" # Skip for issues created before this date days-before-issue-stale: 30 only-issue-labels: - "needs-triage,needs-user-input" # Only issues with all of these labels can be marked as stale + "needs-triage,more-info-needed" # Only issues with all of these labels can be marked as stale exempt-issue-labels: "keep" # Issues marked with this label should not be marked as stale stale-issue-label: "stale" # Mark stale issues with this label stale-issue-message: | diff --git a/.github/workflows/triage-issues.yml b/.github/workflows/triage-issues.yml new file mode 100644 index 00000000000..199952ee2ff --- /dev/null +++ b/.github/workflows/triage-issues.yml @@ -0,0 +1,61 @@ +name: Issue Triaging +on: + issues: + types: [opened, reopened, labeled, unlabeled, closed] + +jobs: + label-incoming: + if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'unlabeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-label-incoming.yml@main + permissions: + issues: write + + close-invalid: + if: github.event.action == 'labeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-close-invalid.yml@main + permissions: + contents: read + issues: write + pull-requests: write + + close-suspected-spam: + if: github.event.action == 'labeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-close-suspected-spam.yml@main + permissions: + issues: write + + close-single-word: + if: github.event.action == 'opened' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-close-single-word-issues.yml@main + permissions: + issues: write + + close-off-topic: + if: github.event.action == 'labeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-close-off-topic.yml@main + permissions: + issues: write + + enhancement-comment: + if: github.event.action == 'labeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-enhancement-comment.yml@main + permissions: + issues: write + + unable-to-reproduce: + if: github.event.action == 'labeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-unable-to-reproduce-comment.yml@main + permissions: + issues: write + + remove-needs-triage: + if: github.event.action == 'labeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-remove-needs-triage.yml@main + permissions: + issues: write + + on-issue-close: + if: github.event.action == 'closed' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-on-issue-close.yml@main + permissions: + issues: write diff --git a/.github/workflows/triage-scheduled-tasks.yml b/.github/workflows/triage-scheduled-tasks.yml new file mode 100644 index 00000000000..721e899f364 --- /dev/null +++ b/.github/workflows/triage-scheduled-tasks.yml @@ -0,0 +1,13 @@ +name: Triage Scheduled Tasks +on: + workflow_dispatch: + issue_comment: + types: [created] + schedule: + - cron: '5 * * * *' # Hourly — no-response close + +jobs: + no-response: + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-no-response-close.yml@main + permissions: + issues: write diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index a2ca1716068..20eca49c8e5 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -5,70 +5,18 @@ on: issues: types: - labeled + # pull_request_target (not pull_request) to access secrets for fork PRs. + # Safe: no PR code is checked out or executed. pull_request_target: types: - labeled -env: - TARGET_REPO: github/cli -jobs: - issue: - environment: cli-discuss-automation - runs-on: ubuntu-latest - if: github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'discuss' - steps: - - name: Create issue based on source issue - env: - BODY: ${{ github.event.issue.body }} - CREATED: ${{ github.event.issue.created_at }} - GH_TOKEN: ${{ secrets.CLI_DISCUSSION_TRIAGE_TOKEN }} - LINK: ${{ github.repository }}#${{ github.event.issue.number }} - TITLE: ${{ github.event.issue.title }} - TRIGGERED_BY: ${{ github.triggering_actor }} - run: | - # Markdown quote source body by replacing newlines for newlines and markdown quoting - BODY="${BODY//$'\n'/$'\n'> }" - - # Create issue using dynamically constructed body within heredoc - cat << EOF | gh issue create --title "Triage issue \"$TITLE\"" --body-file - --repo "$TARGET_REPO" --label triage - **Title:** $TITLE - **Issue:** $LINK - **Created:** $CREATED - **Triggered by:** @$TRIGGERED_BY - - --- - - cc: @github/cli - - > $BODY - EOF - pull_request: - runs-on: ubuntu-latest - environment: cli-discuss-automation - if: github.event_name == 'pull_request_target' && github.event.action == 'labeled' && github.event.label.name == 'discuss' - steps: - - name: Create issue based on source pull request - env: - BODY: ${{ github.event.pull_request.body }} - CREATED: ${{ github.event.pull_request.created_at }} - GH_TOKEN: ${{ secrets.CLI_DISCUSSION_TRIAGE_TOKEN }} - LINK: ${{ github.repository }}#${{ github.event.pull_request.number }} - TITLE: ${{ github.event.pull_request.title }} - TRIGGERED_BY: ${{ github.triggering_actor }} - run: | - # Markdown quote source body by replacing newlines for newlines and markdown quoting - BODY="${BODY//$'\n'/$'\n'> }" - - # Create issue using dynamically constructed body within heredoc - cat << EOF | gh issue create --title "Triage PR \"$TITLE\"" --body-file - --repo "$TARGET_REPO" --label triage - **Title:** $TITLE - **Pull request:** $LINK - **Created:** $CREATED - **Triggered by:** @$TRIGGERED_BY - - --- - - cc: @github/cli - - > $BODY - EOF +jobs: + discuss: + if: github.event.action == 'labeled' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-discuss.yml@main + with: + target_repo: 'github/cli' + cc_team: '@github/cli' + secrets: + discussion_token: ${{ secrets.CLI_DISCUSSION_TRIAGE_TOKEN }} diff --git a/docs/triage.md b/docs/triage.md index ea119994c40..54443345fd3 100644 --- a/docs/triage.md +++ b/docs/triage.md @@ -14,16 +14,16 @@ For bugs, the FR should engage with the issue and community with the goal to rem To be considered triaged, `bug` issues require the following: -- A severity label `p1`, `p2`, and `p3` +- A severity label `priority-1`, `priority-2`, and `priority-3` - Clearly defined Acceptance Criteria, added to the Issue as a standalone comment (see [example](https://github.com/cli/cli/issues/9469#issuecomment-2292315743)) #### Bug severities | Severity | Description | | - | - | -| `p1` | Affects a large population and inhibits work | -| `p2` | Affects more than a few users but doesn't prevent core functions | -| `p3` | Affects a small number of users or is largely cosmetic | +| `priority-1` | Affects a large population and inhibits work | +| `priority-2` | Affects more than a few users but doesn't prevent core functions | +| `priority-3` | Affects a small number of users or is largely cosmetic | ### Enhancements and Docs @@ -36,10 +36,10 @@ When a new issue is opened, the FR **should**: - Ensure there is enough information to understand the enhancement's scope and value - Ask the user for more information about value and use-case, if necessary - Leave the `needs-triage` label on the issue -- Add the `needs-user-input` and `needs-investigation` labels as needed +- Add the `more-info-needed` and `needs-investigation` labels as needed When the FR has enough information to be triaged, they should: -- Remove the `needs-user-input` and `needs-investigation` labels +- Remove the `more-info-needed` and `needs-investigation` labels - Remove their assignment from the issue The FR should **avoid**: @@ -57,7 +57,7 @@ The FR can consider adding any of the following labels below. | - | - | | `discuss` | Some issues require discussion with the internal team. Adding this label will automatically open up an internal discussion with the team to facilitate this discussion. | | `core` | Defines what we would like to do internally. We tend to lean towards `help wanted` by default, and adding `core` should be reserved for trickier issues or implementations we have strong opinions/preferences about. | -| `needs-user-input` | After asking any contributors for more information, add this label so it is clear that the issue has been responded to and we are waiting on the user. | +| `more-info-needed` | After asking any contributors for more information, add this label so it is clear that the issue has been responded to and we are waiting on the user. | | `needs-investigation` | Used when the issue requires further investigation before it can be reviewed and triaged. This is often used for issues that are not clearly bugs or enhancements, or when the FR needs to gather more information before proceeding. | | `invalid` | Added to spam and abusive issues. | From 2b5c3b5ecb8f9c6673c6996aeedc9420706ce92d Mon Sep 17 00:00:00 2001 From: William Martin Date: Fri, 13 Feb 2026 15:36:37 +0100 Subject: [PATCH 68/98] document fork default branch behavior --- pkg/cmd/pr/create/create.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/pr/create/create.go b/pkg/cmd/pr/create/create.go index 8411cdada75..3949ecbb618 100644 --- a/pkg/cmd/pr/create/create.go +++ b/pkg/cmd/pr/create/create.go @@ -214,7 +214,8 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co Upon success, the URL of the created pull request will be printed. When the current branch isn't fully pushed to a git remote, a prompt will ask where - to push the branch and offer an option to fork the base repository. Use %[1]s--head%[1]s to + to push the branch and offer an option to fork the base repository. Any fork created this + way will only have the default branch of the upstream repository. Use %[1]s--head%[1]s to explicitly skip any forking or pushing behavior. %[1]s--head%[1]s supports %[1]s:%[1]s syntax to select a head repo owned by %[1]s%[1]s. From 24964681a8ab84470e623017d066c2815e1692d8 Mon Sep 17 00:00:00 2001 From: William Martin Date: Fri, 13 Feb 2026 15:34:20 +0100 Subject: [PATCH 69/98] Respect --exit-status with --log and --log-failed Fixes #12674 --- pkg/cmd/run/view/view.go | 9 +++++- pkg/cmd/run/view/view_test.go | 58 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/run/view/view.go b/pkg/cmd/run/view/view.go index d50b14fdffc..bed9e3bfa09 100644 --- a/pkg/cmd/run/view/view.go +++ b/pkg/cmd/run/view/view.go @@ -331,7 +331,14 @@ func runView(opts *ViewOptions) error { return err } - return displayLogSegments(opts.IO.Out, segments) + if err := displayLogSegments(opts.IO.Out, segments); err != nil { + return err + } + + if opts.ExitStatus && shared.IsFailureState(run.Conclusion) { + return cmdutil.SilentError + } + return nil } prNumber := "" diff --git a/pkg/cmd/run/view/view_test.go b/pkg/cmd/run/view/view_test.go index 5bcb587d19d..14749fcf66d 100644 --- a/pkg/cmd/run/view/view_test.go +++ b/pkg/cmd/run/view/view_test.go @@ -1048,6 +1048,64 @@ func TestViewRun(t *testing.T) { }, wantOut: quuxTheBarfLogOutput, }, + { + name: "exit status respected with log-failed, failed run", + opts: &ViewOptions{ + RunID: "1234", + LogFailed: true, + ExitStatus: true, + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"), + httpmock.JSONResponse(shared.FailedRun)) + reg.Register( + httpmock.REST("GET", "runs/1234/jobs"), + httpmock.JSONResponse(shared.JobsPayload{ + Jobs: []shared.Job{ + shared.SuccessfulJob, + shared.FailedJob, + }, + })) + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"), + httpmock.BinaryResponse(zipArchive)) + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), + httpmock.JSONResponse(shared.TestWorkflow)) + }, + wantOut: quuxTheBarfLogOutput, + wantErr: true, + }, + { + name: "exit status respected with log, failed run", + opts: &ViewOptions{ + RunID: "1234", + Log: true, + ExitStatus: true, + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"), + httpmock.JSONResponse(shared.FailedRun)) + reg.Register( + httpmock.REST("GET", "runs/1234/jobs"), + httpmock.JSONResponse(shared.JobsPayload{ + Jobs: []shared.Job{ + shared.SuccessfulJob, + shared.FailedJob, + }, + })) + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"), + httpmock.BinaryResponse(zipArchive)) + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), + httpmock.JSONResponse(shared.TestWorkflow)) + }, + wantOut: expectedRunLogOutput, + wantErr: true, + }, { name: "interactive with log, with no step logs available (#10551)", tty: true, From f1ebf6f8d99b2e84188b97062040a080a1cf471b Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:17:33 -0700 Subject: [PATCH 70/98] Migrate stale workflow to shared workflow --- .github/workflows/stale-issues.yml | 36 -------------------- .github/workflows/triage-scheduled-tasks.yml | 13 +++++++ 2 files changed, 13 insertions(+), 36 deletions(-) delete mode 100644 .github/workflows/stale-issues.yml diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml deleted file mode 100644 index d4c5967aa43..00000000000 --- a/.github/workflows/stale-issues.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Marks/closes stale issues -on: - schedule: - - cron: "0 3 * * *" # 3 AM UTC - -permissions: - issues: write - -jobs: - mark-stale-issues: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v10 - with: - start-date: "2025-07-10T00:00:00Z" # Skip for issues created before this date - days-before-issue-stale: 30 - only-issue-labels: - "needs-triage,more-info-needed" # Only issues with all of these labels can be marked as stale - exempt-issue-labels: "keep" # Issues marked with this label should not be marked as stale - stale-issue-label: "stale" # Mark stale issues with this label - stale-issue-message: | - This issue has been automatically marked as stale because it has not had any activity in the last 30 days, - and it will be closed in 30 days if no further activity occurs. - - If you think this is a mistake, please comment on this issue to keep it open. - - days-before-issue-close: 30 - close-issue-reason: "not_planned" - close-issue-message: | - This issue has been automatically closed due to inactivity. - - If you think this is a mistake, please comment on this issue. - - # Exclude PRs from closing or being marked as stale - days-before-pr-stale: -1 - days-before-pr-close: -1 diff --git a/.github/workflows/triage-scheduled-tasks.yml b/.github/workflows/triage-scheduled-tasks.yml index 721e899f364..8dd0793b209 100644 --- a/.github/workflows/triage-scheduled-tasks.yml +++ b/.github/workflows/triage-scheduled-tasks.yml @@ -5,9 +5,22 @@ on: types: [created] schedule: - cron: '5 * * * *' # Hourly — no-response close + - cron: '0 3 * * *' # Daily at 3 AM UTC — stale issues jobs: no-response: uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-no-response-close.yml@main permissions: issues: write + + stale: + if: github.event.schedule == '0 3 * * *' + uses: desktop/gh-cli-and-desktop-shared-workflows/.github/workflows/triage-stale-issues.yml@main + with: + days_before_stale: 30 + days_before_close: -1 + start_date: '2025-07-10T00:00:00Z' + stale_issue_label: 'stale' + exempt_issue_labels: 'keep' + permissions: + issues: write From 166db75d365d6685e8c165d620a1b59fd5652a1b Mon Sep 17 00:00:00 2001 From: William Martin Date: Fri, 13 Feb 2026 19:42:44 +0100 Subject: [PATCH 71/98] pin REST API version to 2022-11-28 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- api/client.go | 3 +++ api/client_test.go | 9 ++++--- api/http_client.go | 3 ++- api/http_client_test.go | 46 +++++++++++++++++++-------------- pkg/cmd/factory/default_test.go | 1 + 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/api/client.go b/api/client.go index e6ff59c592b..207fd86d368 100644 --- a/api/client.go +++ b/api/client.go @@ -16,6 +16,8 @@ import ( const ( accept = "Accept" + apiVersion = "X-GitHub-Api-Version" + apiVersionValue = "2022-11-28" authorization = "Authorization" cacheTTL = "X-GH-CACHE-TTL" graphqlFeatures = "GraphQL-Features" @@ -264,6 +266,7 @@ func clientOptions(hostname string, transport http.RoundTripper) ghAPI.ClientOpt AuthToken: "none", Headers: map[string]string{ authorization: "", + apiVersion: apiVersionValue, }, Host: hostname, SkipDefaultHeaders: true, diff --git a/api/client_test.go b/api/client_test.go index 1701a17a967..f988e090c3a 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -245,10 +245,11 @@ func TestHTTPHeaders(t *testing.T) { assert.NoError(t, err) wantHeader := map[string]string{ - "Accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview", - "Authorization": "token MYTOKEN", - "Content-Type": "application/json; charset=utf-8", - "User-Agent": "GitHub CLI v1.2.3", + "Accept": "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview", + "Authorization": "token MYTOKEN", + "Content-Type": "application/json; charset=utf-8", + "User-Agent": "GitHub CLI v1.2.3", + "X-GitHub-Api-Version": "2022-11-28", } for name, value := range wantHeader { assert.Equal(t, value, gotReq.Header.Get(name), name) diff --git a/api/http_client.go b/api/http_client.go index ab7d490632f..9957f6bc51d 100644 --- a/api/http_client.go +++ b/api/http_client.go @@ -49,7 +49,8 @@ func NewHTTPClient(opts HTTPClientOptions) (*http.Client, error) { } headers := map[string]string{ - userAgent: fmt.Sprintf("GitHub CLI %s", opts.AppVersion), + userAgent: fmt.Sprintf("GitHub CLI %s", opts.AppVersion), + apiVersion: apiVersionValue, } clientOpts.Headers = headers diff --git a/api/http_client_test.go b/api/http_client_test.go index 9a915837fe8..824bc0f1b13 100644 --- a/api/http_client_test.go +++ b/api/http_client_test.go @@ -39,9 +39,10 @@ func TestNewHTTPClient(t *testing.T) { }, host: "github.com", wantHeader: map[string][]string{ - "authorization": {"token MYTOKEN"}, - "user-agent": {"GitHub CLI v1.2.3"}, - "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, + "authorization": {"token MYTOKEN"}, + "user-agent": {"GitHub CLI v1.2.3"}, + "x-github-api-version": {"2022-11-28"}, + "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, }, wantStderr: "", }, @@ -53,9 +54,10 @@ func TestNewHTTPClient(t *testing.T) { }, host: "example.com", wantHeader: map[string][]string{ - "authorization": {"token GHETOKEN"}, - "user-agent": {"GitHub CLI v1.2.3"}, - "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, + "authorization": {"token GHETOKEN"}, + "user-agent": {"GitHub CLI v1.2.3"}, + "x-github-api-version": {"2022-11-28"}, + "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, }, wantStderr: "", }, @@ -68,9 +70,10 @@ func TestNewHTTPClient(t *testing.T) { }, host: "github.com", wantHeader: map[string][]string{ - "authorization": nil, // should not be set - "user-agent": {"GitHub CLI v1.2.3"}, - "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, + "authorization": nil, // should not be set + "user-agent": {"GitHub CLI v1.2.3"}, + "x-github-api-version": {"2022-11-28"}, + "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, }, wantStderr: "", }, @@ -83,9 +86,10 @@ func TestNewHTTPClient(t *testing.T) { }, host: "example.com", wantHeader: map[string][]string{ - "authorization": nil, // should not be set - "user-agent": {"GitHub CLI v1.2.3"}, - "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, + "authorization": nil, // should not be set + "user-agent": {"GitHub CLI v1.2.3"}, + "x-github-api-version": {"2022-11-28"}, + "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, }, wantStderr: "", }, @@ -98,9 +102,10 @@ func TestNewHTTPClient(t *testing.T) { }, host: "github.com", wantHeader: map[string][]string{ - "authorization": {"token MYTOKEN"}, - "user-agent": {"GitHub CLI v1.2.3"}, - "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, + "authorization": {"token MYTOKEN"}, + "user-agent": {"GitHub CLI v1.2.3"}, + "x-github-api-version": {"2022-11-28"}, + "accept": {"application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview"}, }, wantStderr: heredoc.Doc(` * Request at