diff --git a/pkg/cmd/cache/delete/delete.go b/pkg/cmd/cache/delete/delete.go index b8367cb5cf7..9cb188008ea 100644 --- a/pkg/cmd/cache/delete/delete.go +++ b/pkg/cmd/cache/delete/delete.go @@ -61,6 +61,9 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co # Delete all caches (exit code 1 on no caches) $ gh cache delete --all + # Delete all caches for a specific ref + $ gh cache delete --all --ref refs/pull//merge + # Delete all caches (exit code 0 on no caches) $ gh cache delete --all --succeed-on-no-caches `), @@ -76,18 +79,11 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co return err } - if err := cmdutil.MutuallyExclusive( - "--ref cannot be used with --all", - opts.DeleteAll, opts.Ref != "", - ); err != nil { - return err - } - if !opts.DeleteAll && opts.SucceedOnNoCaches { return cmdutil.FlagErrorf("--succeed-on-no-caches must be used in conjunction with --all") } - if opts.Ref != "" && len(args) == 0 { + if opts.Ref != "" && len(args) == 0 && !opts.DeleteAll { return cmdutil.FlagErrorf("must provide a cache key") } @@ -113,7 +109,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co }, } - cmd.Flags().BoolVarP(&opts.DeleteAll, "all", "a", false, "Delete all caches") + cmd.Flags().BoolVarP(&opts.DeleteAll, "all", "a", false, "Delete all caches, can be used with --ref to delete all caches for a specific ref") cmd.Flags().StringVarP(&opts.Ref, "ref", "r", "", "Delete by cache key and ref, formatted as refs/heads/ or refs/pull//merge") cmd.Flags().BoolVar(&opts.SucceedOnNoCaches, "succeed-on-no-caches", false, "Return exit code 0 if no caches found. Must be used in conjunction with `--all`") @@ -135,7 +131,7 @@ func deleteRun(opts *DeleteOptions) error { var toDelete []string if opts.DeleteAll { opts.IO.StartProgressIndicator() - caches, err := shared.GetCaches(client, repo, shared.GetCachesOptions{Limit: -1}) + caches, err := shared.GetCaches(client, repo, shared.GetCachesOptions{Limit: -1, Ref: opts.Ref}) opts.IO.StopProgressIndicator() if err != nil { return err diff --git a/pkg/cmd/cache/delete/delete_test.go b/pkg/cmd/cache/delete/delete_test.go index a05c57c7c2d..51ad18e1eee 100644 --- a/pkg/cmd/cache/delete/delete_test.go +++ b/pkg/cmd/cache/delete/delete_test.go @@ -84,9 +84,9 @@ func TestNewCmdDelete(t *testing.T) { wantsErr: "--ref cannot be used with cache ID", }, { - name: "ref flag with all flag", - cli: "--all --ref refs/heads/main", - wantsErr: "--ref cannot be used with --all", + name: "ref flag with all flag", + cli: "--all --ref refs/heads/main", + wants: DeleteOptions{DeleteAll: true, Ref: "refs/heads/main"}, }, } @@ -374,6 +374,82 @@ func TestDeleteRun(t *testing.T) { wantErr: true, wantErrMsg: "X Could not find a cache matching existing-cache-key (with ref invalid-ref) in OWNER/REPO", }, + { + name: "deletes all caches with ref", + opts: DeleteOptions{DeleteAll: true, Ref: "refs/heads/main"}, + stubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.QueryMatcher("GET", "repos/OWNER/REPO/actions/caches", url.Values{ + "ref": []string{"refs/heads/main"}, + }), + httpmock.JSONResponse(shared.CachePayload{ + ActionsCaches: []shared.Cache{ + { + Id: 123, + Key: "foo", + Ref: "refs/heads/main", + CreatedAt: time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC), + LastAccessedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), + }, + { + Id: 456, + Key: "bar", + Ref: "refs/heads/main", + CreatedAt: time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC), + LastAccessedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC), + }, + }, + TotalCount: 2, + }), + ) + reg.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/actions/caches/123"), + httpmock.StatusStringResponse(204, ""), + ) + reg.Register( + httpmock.REST("DELETE", "repos/OWNER/REPO/actions/caches/456"), + httpmock.StatusStringResponse(204, ""), + ) + }, + tty: true, + wantStdout: "✓ Deleted 2 caches from OWNER/REPO\n", + }, + { + name: "no caches to delete when deleting all with ref", + opts: DeleteOptions{DeleteAll: true, Ref: "refs/heads/main"}, + stubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.QueryMatcher("GET", "repos/OWNER/REPO/actions/caches", url.Values{ + "ref": []string{"refs/heads/main"}, + }), + httpmock.JSONResponse(shared.CachePayload{ + ActionsCaches: []shared.Cache{}, + TotalCount: 0, + }), + ) + }, + tty: false, + wantErr: true, + wantErrMsg: "X No caches to delete", + }, + { + name: "no caches to delete when deleting all for ref but succeed on no cache tty", + opts: DeleteOptions{DeleteAll: true, SucceedOnNoCaches: true, Ref: "refs/heads/main"}, + stubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.QueryMatcher("GET", "repos/OWNER/REPO/actions/caches", url.Values{ + "ref": []string{"refs/heads/main"}, + }), + httpmock.JSONResponse(shared.CachePayload{ + ActionsCaches: []shared.Cache{}, + TotalCount: 0, + }), + ) + }, + tty: true, + wantErr: false, + wantStdout: "✓ No caches to delete\n", + }, } for _, tt := range tests { diff --git a/pkg/cmd/copilot/copilot_test.go b/pkg/cmd/copilot/copilot_test.go index f377f171dc0..e7c8fb02755 100644 --- a/pkg/cmd/copilot/copilot_test.go +++ b/pkg/cmd/copilot/copilot_test.go @@ -352,9 +352,9 @@ func TestFetchExpectedChecksum(t *testing.T) { ) client := &http.Client{Transport: reg} - _, err := fetchExpectedChecksum(client, "https://example.com/checksums", "copilot-windows-x64.zip") + _, err := fetchExpectedChecksum(client, "https://example.com/checksums", "copilot-win32-x64.zip") require.Error(t, err, "expected error for missing archive") - require.Equal(t, "checksum not found for copilot-windows-x64.zip", err.Error(), "unexpected error") + require.Equal(t, "checksum not found for copilot-win32-x64.zip", err.Error(), "unexpected error") }) t.Run("handles single space separator", func(t *testing.T) {