From 6b56a239704caf59a14e82a9d8124509c2d77cbe Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:24:05 -0700 Subject: [PATCH] Remove unnecessary StateReasonDuplicate feature detection The DUPLICATE enum variant for IssueClosedStateReason was added in GHES 3.16, which is older than the earliest supported GHES version. The feature detection check is therefore unnecessary. Addresses: https://github.com/cli/cli/pull/12811#issuecomment-3997044372 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../featuredetection/feature_detection.go | 29 ++------ .../feature_detection_test.go | 45 ++--------- pkg/cmd/issue/close/close.go | 6 -- pkg/cmd/issue/close/close_test.go | 74 ------------------- 4 files changed, 14 insertions(+), 140 deletions(-) diff --git a/internal/featuredetection/feature_detection.go b/internal/featuredetection/feature_detection.go index e40c134bc..7a200e20c 100644 --- a/internal/featuredetection/feature_detection.go +++ b/internal/featuredetection/feature_detection.go @@ -23,15 +23,13 @@ type Detector interface { } type IssueFeatures struct { - StateReason bool - StateReasonDuplicate bool - ActorIsAssignable bool + StateReason bool + ActorIsAssignable bool } var allIssueFeatures = IssueFeatures{ - StateReason: true, - StateReasonDuplicate: true, - ActorIsAssignable: true, + StateReason: true, + ActorIsAssignable: true, } type PullRequestFeatures struct { @@ -140,9 +138,8 @@ func (d *detector) IssueFeatures() (IssueFeatures, error) { } features := IssueFeatures{ - StateReason: false, - StateReasonDuplicate: false, - ActorIsAssignable: false, // replaceActorsForAssignable GraphQL mutation unavailable on GHES + StateReason: false, + ActorIsAssignable: false, // replaceActorsForAssignable GraphQL mutation unavailable on GHES } var featureDetection struct { @@ -151,11 +148,6 @@ func (d *detector) IssueFeatures() (IssueFeatures, error) { Name string } `graphql:"fields(includeDeprecated: true)"` } `graphql:"Issue: __type(name: \"Issue\")"` - IssueClosedStateReason struct { - EnumValues []struct { - Name string - } `graphql:"enumValues(includeDeprecated: true)"` - } `graphql:"IssueClosedStateReason: __type(name: \"IssueClosedStateReason\")"` } gql := api.NewClientFromHTTP(d.httpClient) @@ -170,15 +162,6 @@ func (d *detector) IssueFeatures() (IssueFeatures, error) { } } - if features.StateReason { - for _, enumValue := range featureDetection.IssueClosedStateReason.EnumValues { - if enumValue.Name == "DUPLICATE" { - features.StateReasonDuplicate = true - break - } - } - } - return features, nil } diff --git a/internal/featuredetection/feature_detection_test.go b/internal/featuredetection/feature_detection_test.go index 7417cc7d8..032f5cda0 100644 --- a/internal/featuredetection/feature_detection_test.go +++ b/internal/featuredetection/feature_detection_test.go @@ -23,9 +23,8 @@ func TestIssueFeatures(t *testing.T) { name: "github.com", hostname: "github.com", wantFeatures: IssueFeatures{ - StateReason: true, - StateReasonDuplicate: true, - ActorIsAssignable: true, + StateReason: true, + ActorIsAssignable: true, }, wantErr: false, }, @@ -33,9 +32,8 @@ func TestIssueFeatures(t *testing.T) { name: "ghec data residency (ghe.com)", hostname: "stampname.ghe.com", wantFeatures: IssueFeatures{ - StateReason: true, - StateReasonDuplicate: true, - ActorIsAssignable: true, + StateReason: true, + ActorIsAssignable: true, }, wantErr: false, }, @@ -46,50 +44,23 @@ func TestIssueFeatures(t *testing.T) { `query Issue_fields\b`: `{"data": {}}`, }, wantFeatures: IssueFeatures{ - StateReason: false, - StateReasonDuplicate: false, - ActorIsAssignable: false, + StateReason: false, + ActorIsAssignable: false, }, wantErr: false, }, { - name: "GHE has state reason field without duplicate enum", + name: "GHE has state reason field", hostname: "git.my.org", queryResponse: map[string]string{ `query Issue_fields\b`: heredoc.Doc(` { "data": { "Issue": { "fields": [ {"name": "stateReason"} - ] }, "IssueClosedStateReason": { "enumValues": [ - {"name": "COMPLETED"}, - {"name": "NOT_PLANNED"} ] } } } `), }, wantFeatures: IssueFeatures{ - StateReason: true, - StateReasonDuplicate: false, - ActorIsAssignable: false, - }, - wantErr: false, - }, - { - name: "GHE has duplicate state reason enum value", - hostname: "git.my.org", - queryResponse: map[string]string{ - `query Issue_fields\b`: heredoc.Doc(` - { "data": { "Issue": { "fields": [ - {"name": "stateReason"} - ] }, "IssueClosedStateReason": { "enumValues": [ - {"name": "COMPLETED"}, - {"name": "NOT_PLANNED"}, - {"name": "DUPLICATE"} - ] } } } - `), - }, - wantFeatures: IssueFeatures{ - StateReason: true, - StateReasonDuplicate: true, - ActorIsAssignable: false, + StateReason: true, }, wantErr: false, }, diff --git a/pkg/cmd/issue/close/close.go b/pkg/cmd/issue/close/close.go index 4c1c7d382..ab7e30688 100644 --- a/pkg/cmd/issue/close/close.go +++ b/pkg/cmd/issue/close/close.go @@ -196,12 +196,6 @@ func apiClose(httpClient *http.Client, repo ghrepo.Interface, issue *api.Issue, return fmt.Errorf("closing as duplicate is not supported on %s", repo.RepoHost()) } reason = "" - } else if reason == "duplicate" && !features.StateReasonDuplicate { - if duplicateIssueID != "" { - return fmt.Errorf("closing as duplicate is not supported on %s", repo.RepoHost()) - } - // If DUPLICATE is not supported silently close issue without setting StateReason. - reason = "" } } diff --git a/pkg/cmd/issue/close/close_test.go b/pkg/cmd/issue/close/close_test.go index ddab71210..300fe5fc5 100644 --- a/pkg/cmd/issue/close/close_test.go +++ b/pkg/cmd/issue/close/close_test.go @@ -16,15 +16,6 @@ import ( "github.com/stretchr/testify/require" ) -type issueFeaturesDetectorMock struct { - fd.EnabledDetectorMock - issueFeatures fd.IssueFeatures -} - -func (md *issueFeaturesDetectorMock) IssueFeatures() (fd.IssueFeatures, error) { - return md.issueFeatures, nil -} - func TestNewCmdClose(t *testing.T) { // Test shared parsing of issue number / URL. argparsetest.TestArgParsing(t, NewCmdClose) @@ -282,71 +273,6 @@ func TestCloseRun(t *testing.T) { }, wantStderr: "✓ Closed issue OWNER/REPO#13 (The title of the issue)\n", }, - { - name: "close issue with duplicate reason when duplicate is not supported", - opts: &CloseOptions{ - IssueNumber: 13, - Reason: "duplicate", - Detector: &issueFeaturesDetectorMock{ - issueFeatures: fd.IssueFeatures{ - StateReason: true, - StateReasonDuplicate: false, - }, - }, - }, - httpStubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(`query IssueByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "hasIssuesEnabled": true, - "issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"} - } } }`), - ) - reg.Register( - httpmock.GraphQL(`mutation IssueClose\b`), - httpmock.GraphQLMutation(`{"id": "THE-ID"}`, - func(inputs map[string]interface{}) { - assert.Equal(t, 1, len(inputs)) - assert.Equal(t, "THE-ID", inputs["issueId"]) - }), - ) - }, - wantStderr: "✓ Closed issue OWNER/REPO#13 (The title of the issue)\n", - }, - { - name: "close issue as duplicate when duplicate is not supported", - opts: &CloseOptions{ - IssueNumber: 13, - DuplicateOf: "99", - Detector: &issueFeaturesDetectorMock{ - issueFeatures: fd.IssueFeatures{ - StateReason: true, - StateReasonDuplicate: false, - }, - }, - }, - httpStubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(`query IssueByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "hasIssuesEnabled": true, - "issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"} - } } }`), - ) - reg.Register( - httpmock.GraphQL(`query IssueByNumber\b`), - httpmock.StringResponse(` - { "data": { "repository": { - "hasIssuesEnabled": true, - "issue": { "id": "DUPLICATE-ID", "number": 99} - } } }`), - ) - }, - wantErr: true, - errMsg: "closing as duplicate is not supported on github.com", - }, { name: "duplicate of cannot point to same issue", opts: &CloseOptions{