Skip to content

Commit 77caf49

Browse files
authored
Merge branch 'cli:trunk' into trunk
2 parents 1444299 + 6224368 commit 77caf49

File tree

19 files changed

+578
-120
lines changed

19 files changed

+578
-120
lines changed

.github/secret_scanning.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
paths-ignore:
2+
- 'third-party/**'
3+
- 'third-party-licenses.*.md'

.github/workflows/codeql.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ permissions:
1818
jobs:
1919
CodeQL-Build:
2020
runs-on: ubuntu-latest
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
language: ['go', 'actions']
2125

2226
steps:
2327
- name: Check out code
@@ -26,13 +30,20 @@ jobs:
2630
- name: Initialize CodeQL
2731
uses: github/codeql-action/init@v3
2832
with:
29-
languages: go
33+
languages: ${{ matrix.language }}
3034
queries: security-and-quality
35+
config: |
36+
paths-ignore:
37+
- 'third-party/**'
38+
- 'third-party-licenses.*.md'
3139
3240
- name: Setup Go
41+
if: matrix.language == 'go'
3342
uses: actions/setup-go@v5
3443
with:
3544
go-version-file: 'go.mod'
3645

3746
- name: Perform CodeQL Analysis
3847
uses: github/codeql-action/analyze@v3
48+
with:
49+
category: "/language:${{ matrix.language }}"

.github/workflows/deployment.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ jobs:
309309
rpmsign --addsign dist/*.rpm
310310
- name: Attest release artifacts
311311
if: inputs.environment == 'production'
312-
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
312+
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
313313
with:
314314
subject-path: "dist/gh_*"
315315
- name: Run createrepo
@@ -384,7 +384,7 @@ jobs:
384384
git diff --name-status @{upstream}..
385385
fi
386386
- name: Bump homebrew-core formula
387-
uses: mislav/bump-homebrew-formula-action@942e550c6344cfdb9e1ab29b9bb9bf0c43efa19b
387+
uses: mislav/bump-homebrew-formula-action@8e2baa47daaa8db10fcdeb04105dfa6850eb0d68
388388
if: inputs.environment == 'production' && !contains(inputs.tag_name, '-')
389389
with:
390390
formula-name: gh

.github/workflows/homebrew-bump.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
runs-on: ubuntu-latest
1818
steps:
1919
- name: Bump homebrew-core formula
20-
uses: mislav/bump-homebrew-formula-action@942e550c6344cfdb9e1ab29b9bb9bf0c43efa19b
20+
uses: mislav/bump-homebrew-formula-action@8e2baa47daaa8db10fcdeb04105dfa6850eb0d68
2121
if: inputs.environment == 'production' && !contains(inputs.tag_name, '-')
2222
with:
2323
formula-name: gh
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: PR Help Wanted Check
2+
on:
3+
pull_request_target:
4+
types: [opened]
5+
6+
permissions:
7+
contents: none
8+
issues: read
9+
pull-requests: write
10+
11+
jobs:
12+
check-help-wanted:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
18+
- name: Check for issues without help-wanted label
19+
env:
20+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
22+
PR_AUTHOR_TYPE: ${{ github.event.pull_request.user.type }}
23+
PR_AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
24+
if: "!github.event.pull_request.draft"
25+
run: |
26+
# Run the script to check for issues without help-wanted label
27+
bash .github/workflows/scripts/check-help-wanted.sh ${{ github.event.pull_request.html_url }}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
PR_URL="$1"
6+
7+
if [ -z "$PR_URL" ]; then
8+
echo "Usage: $0 <PR_URL>"
9+
echo ""
10+
echo "Check if the PR references any non-help-wanted issues and, if so, comment"
11+
echo "on it explaining why the team might close/dismiss it."
12+
exit 1
13+
fi
14+
15+
# Skip if PR is from a bot or org member
16+
if [ "$PR_AUTHOR_TYPE" = "Bot" ] || [ "$PR_AUTHOR_ASSOCIATION" = "MEMBER" ] || [ "$PR_AUTHOR_ASSOCIATION" = "OWNER" ]; then
17+
echo "Skipping check for PR #$PR_URL as it is from a bot ($PR_AUTHOR_TYPE) or an org member ($PR_AUTHOR_ASSOCIATION: MEMBER/OWNER)"
18+
exit 0
19+
fi
20+
21+
# Extract PR number from URL for logging
22+
PR_NUM="$(basename "$PR_URL")"
23+
24+
# Extract cli/cli closing issues references from PR
25+
CLOSING_ISSUES="$(gh pr view "$PR_URL" --json closingIssuesReferences --jq '.closingIssuesReferences[] | select(.repository.name == "cli" and .repository.owner.login == "cli") | .number')"
26+
27+
if [ -z "$CLOSING_ISSUES" ]; then
28+
echo "No closing issues found for PR #$PR_NUM"
29+
exit 0
30+
fi
31+
32+
# Check each closing issue for 'help-wanted' label
33+
ISSUES_WITHOUT_HELP_WANTED=()
34+
35+
for issue_num in $CLOSING_ISSUES; do
36+
echo "Checking issue #$issue_num for 'help wanted' label..."
37+
38+
# Get issue labels
39+
LABELS=$(gh issue view "$issue_num" --json labels --jq '.labels[].name')
40+
41+
# Skip if the issue has the gh-attestion or gh-codespace label
42+
# This is because the codeowners for these commands may not be public
43+
# cli org members, and so unless we authenticate with a PAT, we can't
44+
# know who is an external contributor or not.
45+
# So we skip these issues to avoid falsely writing a comment
46+
# on each PR opened by these codeowners.
47+
if echo "$LABELS" | grep -q -e "gh-attestation" -e "gh-codespace"; then
48+
echo "Issue #$issue_num is skipped due to labels"
49+
continue
50+
fi
51+
52+
# Check if 'help wanted' label exists
53+
if ! echo "$LABELS" | grep -q "help wanted"; then
54+
ISSUES_WITHOUT_HELP_WANTED+=("$issue_num")
55+
echo "Issue #$issue_num does not have 'help wanted' label"
56+
else
57+
echo "Issue #$issue_num has 'help wanted' label"
58+
fi
59+
done
60+
61+
# If we found issues without 'help wanted' label, post a comment
62+
if [ ${#ISSUES_WITHOUT_HELP_WANTED[@]} -gt 0 ]; then
63+
echo "Found ${#ISSUES_WITHOUT_HELP_WANTED[@]} issues without 'help wanted' label"
64+
65+
# Build issue list for comment
66+
ISSUE_LIST=""
67+
for issue_num in "${ISSUES_WITHOUT_HELP_WANTED[@]}"; do
68+
ISSUE_LIST="$ISSUE_LIST- #$issue_num"$'\n'
69+
done
70+
71+
# Create comment message
72+
gh pr comment "$PR_URL" --body-file - <<EOF
73+
Thank you for your pull request! 🎉
74+
75+
This PR appears to fix the following issues that are not labeled with \`help wanted\`:
76+
77+
$ISSUE_LIST
78+
As outlined in our [Contributing Guidelines](https://github.com/cli/cli/blob/trunk/.github/CONTRIBUTING.md), we expect that PRs are only created for issues that have been labeled \`help wanted\`.
79+
80+
While we appreciate your initiative, please note that:
81+
82+
- **PRs for non-\`help wanted\` issues may not be reviewed immediately** as they might not align with our current priorities
83+
- **The issue might already be assigned** to a team member or planned for a specific release
84+
- **We may need to close this PR**. For example, if it conflicts with ongoing work or architectural decisions
85+
86+
**What happens next:**
87+
- Our team will review this PR and the associated issues
88+
- We may add the \`help wanted\` label to the issues, if appropriate, and review this pull request
89+
- In some cases, we may need to close the PR. For example, if it doesn't fit our current roadmap
90+
91+
Thank you for your understanding and contribution to the project! 🙏
92+
93+
*This comment was automatically generated by cliAutomation.*
94+
EOF
95+
96+
echo "Posted comment on PR #$PR_NUM"
97+
else
98+
echo "All closing issues have 'help wanted' label - no action needed"
99+
fi

api/queries_pr.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,25 @@ type PullRequest struct {
8585

8686
Assignees Assignees
8787
AssignedActors AssignedActors
88-
Labels Labels
89-
ProjectCards ProjectCards
90-
ProjectItems ProjectItems
91-
Milestone *Milestone
92-
Comments Comments
93-
ReactionGroups ReactionGroups
94-
Reviews PullRequestReviews
95-
LatestReviews PullRequestReviews
96-
ReviewRequests ReviewRequests
88+
// AssignedActorsUsed is a GIGANTIC hack to carry around whether we expected AssignedActors to be requested
89+
// on this PR. This is required because the Feature Detection of support for AssignedActors occurs inside the
90+
// PR Finder, but knowledge of support is required at the command level. However, we can't easily construct
91+
// the feature detector at the command level because it needs knowledge of the BaseRepo, which is only available
92+
// inside the PR Finder. This is bad and we should feel bad.
93+
//
94+
// The right solution is to extract argument parsing from the PR Finder into each command, so that we have access
95+
// to the BaseRepo and can construct the feature detector there. This is what happens in the issue commands with
96+
// `shared.ParseIssueFromArg`.
97+
AssignedActorsUsed bool
98+
Labels Labels
99+
ProjectCards ProjectCards
100+
ProjectItems ProjectItems
101+
Milestone *Milestone
102+
Comments Comments
103+
ReactionGroups ReactionGroups
104+
Reviews PullRequestReviews
105+
LatestReviews PullRequestReviews
106+
ReviewRequests ReviewRequests
97107

98108
ClosingIssuesReferences ClosingIssuesReferences
99109
}

api/queries_repo.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -709,19 +709,22 @@ func (m *RepoMetadataResult) MembersToIDs(names []string) ([]string, error) {
709709
}
710710

711711
// Look for ID in assignable actors if not found in assignable users
712-
for _, a := range m.AssignableActors {
713-
if strings.EqualFold(assigneeLogin, a.Login()) {
714-
ids = append(ids, a.ID())
715-
found = true
716-
break
717-
}
718-
if strings.EqualFold(assigneeLogin, a.DisplayName()) {
719-
ids = append(ids, a.ID())
720-
found = true
721-
break
712+
if !found {
713+
for _, a := range m.AssignableActors {
714+
if strings.EqualFold(assigneeLogin, a.Login()) {
715+
ids = append(ids, a.ID())
716+
found = true
717+
break
718+
}
719+
if strings.EqualFold(assigneeLogin, a.DisplayName()) {
720+
ids = append(ids, a.ID())
721+
found = true
722+
break
723+
}
722724
}
723725
}
724726

727+
// And if we still didn't find an ID, return an error
725728
if !found {
726729
return nil, fmt.Errorf("'%s' not found", assigneeLogin)
727730
}

api/queries_repo_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,78 @@ t001: team(slug:"robots"){id,slug}
461461
}
462462
}
463463

464+
func TestMembersToIDs(t *testing.T) {
465+
t.Parallel()
466+
467+
t.Run("finds ids in assignable users", func(t *testing.T) {
468+
t.Parallel()
469+
470+
repoMetadataResult := RepoMetadataResult{
471+
AssignableUsers: []AssignableUser{
472+
NewAssignableUser("MONAID", "monalisa", ""),
473+
NewAssignableUser("MONAID2", "monalisa2", ""),
474+
},
475+
AssignableActors: []AssignableActor{
476+
NewAssignableBot("HUBOTID", "hubot"),
477+
},
478+
}
479+
ids, err := repoMetadataResult.MembersToIDs([]string{"monalisa"})
480+
require.NoError(t, err)
481+
require.Equal(t, []string{"MONAID"}, ids)
482+
})
483+
484+
t.Run("finds ids by assignable actor logins", func(t *testing.T) {
485+
t.Parallel()
486+
487+
repoMetadataResult := RepoMetadataResult{
488+
AssignableActors: []AssignableActor{
489+
NewAssignableBot("HUBOTID", "hubot"),
490+
NewAssignableUser("MONAID", "monalisa", ""),
491+
},
492+
}
493+
ids, err := repoMetadataResult.MembersToIDs([]string{"monalisa"})
494+
require.NoError(t, err)
495+
require.Equal(t, []string{"MONAID"}, ids)
496+
})
497+
498+
t.Run("finds ids by assignable actor display names", func(t *testing.T) {
499+
t.Parallel()
500+
501+
repoMetadataResult := RepoMetadataResult{
502+
AssignableActors: []AssignableActor{
503+
NewAssignableUser("MONAID", "monalisa", "mona"),
504+
},
505+
}
506+
ids, err := repoMetadataResult.MembersToIDs([]string{"monalisa (mona)"})
507+
require.NoError(t, err)
508+
require.Equal(t, []string{"MONAID"}, ids)
509+
})
510+
511+
t.Run("when a name appears in both assignable users and actors, the id is only returned once", func(t *testing.T) {
512+
t.Parallel()
513+
514+
repoMetadataResult := RepoMetadataResult{
515+
AssignableUsers: []AssignableUser{
516+
NewAssignableUser("MONAID", "monalisa", ""),
517+
},
518+
AssignableActors: []AssignableActor{
519+
NewAssignableUser("MONAID", "monalisa", ""),
520+
},
521+
}
522+
ids, err := repoMetadataResult.MembersToIDs([]string{"monalisa"})
523+
require.NoError(t, err)
524+
require.Equal(t, []string{"MONAID"}, ids)
525+
})
526+
527+
t.Run("when id is not found, returns an error", func(t *testing.T) {
528+
t.Parallel()
529+
530+
repoMetadataResult := RepoMetadataResult{}
531+
_, err := repoMetadataResult.MembersToIDs([]string{"monalisa"})
532+
require.Error(t, err)
533+
})
534+
}
535+
464536
func sliceEqual(a, b []string) bool {
465537
if len(a) != len(b) {
466538
return false

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ require (
4444
github.com/opentracing/opentracing-go v1.2.0
4545
github.com/rivo/tview v0.0.0-20221029100920-c4a7e501810d
4646
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc
47-
github.com/sigstore/protobuf-specs v0.4.2
47+
github.com/sigstore/protobuf-specs v0.4.3
4848
github.com/sigstore/sigstore-go v1.0.0
4949
github.com/spf13/cobra v1.9.1
5050
github.com/spf13/pflag v1.0.6
@@ -56,7 +56,7 @@ require (
5656
golang.org/x/sync v0.14.0
5757
golang.org/x/term v0.32.0
5858
golang.org/x/text v0.25.0
59-
google.golang.org/grpc v1.72.0
59+
google.golang.org/grpc v1.72.2
6060
google.golang.org/protobuf v1.36.6
6161
gopkg.in/h2non/gock.v1 v1.1.2
6262
gopkg.in/yaml.v3 v3.0.1

0 commit comments

Comments
 (0)