Skip to content

Commit 2b636d9

Browse files
committed
build: add script to unlink all sub-issues from a tracking issue
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: passed - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent bb1f586 commit 2b636d9

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env bash
2+
#
3+
# @license Apache-2.0
4+
#
5+
# Copyright (c) 2025 The Stdlib Authors.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
19+
# Script to remove sub-issues from a parent issue.
20+
#
21+
# Usage: ./remove_sub_issues <parent-issue-number> [--all]
22+
#
23+
# Arguments:
24+
#
25+
# parent-issue-number Number of the parent issue.
26+
# --all Remove all sub-issues. Without this flag, only
27+
# closed sub-issues are removed.
28+
#
29+
# Environment variables:
30+
#
31+
# GITHUB_TOKEN GitHub authentication token.
32+
#
33+
34+
# shellcheck disable=SC2153,SC2317
35+
36+
# Ensure that the exit status of pipelines is non-zero in the event that at least one of the commands in a pipeline fails:
37+
set -o pipefail
38+
39+
# VARIABLES #
40+
41+
# Assign command line arguments to variables:
42+
parent_issue_number="$1"
43+
remove_all="false"
44+
if [ "$2" = "--all" ]; then
45+
remove_all="true"
46+
fi
47+
48+
# Repository information:
49+
owner="stdlib-js"
50+
repo="stdlib"
51+
52+
# Get the GitHub authentication token:
53+
github_token="${GITHUB_TOKEN}"
54+
if [ -z "$github_token" ]; then
55+
echo -e "ERROR: GITHUB_TOKEN environment variable is not set."
56+
exit 1
57+
fi
58+
59+
# Validate arguments:
60+
if [ -z "$parent_issue_number" ]; then
61+
echo -e "ERROR: Parent issue number is required."
62+
echo -e "Usage: ./remove_sub_issues <parent-issue-number> [--all]"
63+
exit 1
64+
fi
65+
66+
if [ -n "$2" ] && [ "$2" != "--all" ]; then
67+
echo -e "ERROR: Invalid option. Use --all to remove all sub-issues."
68+
exit 1
69+
fi
70+
71+
# FUNCTIONS #
72+
73+
# Error handler.
74+
#
75+
# $1 - error status
76+
on_error() {
77+
echo -e "ERROR: An error was encountered during execution." >&2
78+
exit "$1"
79+
}
80+
81+
# Prints a success message.
82+
print_success() {
83+
echo -e "Success!" >&2
84+
}
85+
86+
# Fetches the parent issue ID and its sub-issues.
87+
fetch_issue_with_sub_issues() {
88+
local response
89+
response=$(curl -s -X POST 'https://api.github.com/graphql' \
90+
-H "Authorization: bearer ${github_token}" \
91+
-H "GraphQL-Features: issue_types" \
92+
-H "GraphQL-Features: sub_issues" \
93+
-H "Content-Type: application/json" \
94+
--data @- << EOF
95+
{
96+
"query": "query(\$owner: String!, \$repo: String!, \$number: Int!) { repository(owner: \$owner, name: \$repo) { issue(number: \$number) { id subIssues(first: 100) { nodes { id number state } } } } }",
97+
"variables": {
98+
"owner": "${owner}",
99+
"repo": "${repo}",
100+
"number": ${parent_issue_number}
101+
}
102+
}
103+
EOF
104+
)
105+
echo "$response"
106+
}
107+
108+
# Removes a sub-issue relationship.
109+
#
110+
# $1 - parent issue ID
111+
# $2 - sub-issue ID
112+
remove_sub_issue() {
113+
local parent_issue_id="$1"
114+
local sub_issue_id="$2"
115+
local response
116+
response=$(curl -s -X POST 'https://api.github.com/graphql' \
117+
-H "Authorization: bearer ${github_token}" \
118+
-H "GraphQL-Features: issue_types" \
119+
-H "GraphQL-Features: sub_issues" \
120+
-H "Content-Type: application/json" \
121+
--data @- << EOF
122+
{
123+
"query": "mutation(\$parentIssueId: ID!, \$subIssueId: ID!) { removeSubIssue(input: { issueId: \$parentIssueId, subIssueId: \$subIssueId }) { issue { number } subIssue { number } } }",
124+
"variables": {
125+
"parentIssueId": "${parent_issue_id}",
126+
"subIssueId": "${sub_issue_id}"
127+
}
128+
}
129+
EOF
130+
)
131+
echo "$response"
132+
}
133+
134+
# Main execution sequence.
135+
main() {
136+
echo "Fetching issue #${parent_issue_number} with sub-issues..."
137+
issue_response=$(fetch_issue_with_sub_issues)
138+
139+
parent_issue_id=$(echo "$issue_response" | jq -r '.data.repository.issue.id')
140+
if [ -z "$parent_issue_id" ] || [ "$parent_issue_id" = "null" ]; then
141+
echo -e "Failed to fetch parent issue. Response: ${issue_response}"
142+
exit 1
143+
fi
144+
145+
# Extract sub-issues based on filter...
146+
if [ "$remove_all" = "true" ]; then
147+
sub_issues=$(echo "$issue_response" | jq -c '.data.repository.issue.subIssues.nodes[]')
148+
echo "Removing all sub-issues..."
149+
else
150+
sub_issues=$(echo "$issue_response" | jq -c '.data.repository.issue.subIssues.nodes[] | select(.state == "CLOSED")')
151+
echo "Removing closed sub-issues..."
152+
fi
153+
154+
# Check if there are any sub-issues to remove...
155+
if [ -z "$sub_issues" ]; then
156+
echo "No sub-issues to remove."
157+
print_success
158+
exit 0
159+
fi
160+
161+
# Count sub-issues...
162+
sub_issue_count=$(echo "$sub_issues" | wc -l | tr -d ' ')
163+
echo "Found ${sub_issue_count} sub-issue(s) to remove."
164+
165+
# Remove each sub-issue...
166+
removed_count=0
167+
while IFS= read -r sub_issue; do
168+
sub_issue_id=$(echo "$sub_issue" | jq -r '.id')
169+
sub_issue_number=$(echo "$sub_issue" | jq -r '.number')
170+
sub_issue_state=$(echo "$sub_issue" | jq -r '.state')
171+
172+
echo "Removing sub-issue #${sub_issue_number} (${sub_issue_state})..."
173+
remove_response=$(remove_sub_issue "$parent_issue_id" "$sub_issue_id")
174+
175+
removed_number=$(echo "$remove_response" | jq -r '.data.removeSubIssue.subIssue.number')
176+
if [ -z "$removed_number" ] || [ "$removed_number" = "null" ]; then
177+
echo -e "Warning: Failed to remove sub-issue #${sub_issue_number}. Response: ${remove_response}"
178+
else
179+
echo -e "Removed sub-issue #${sub_issue_number}"
180+
removed_count=$((removed_count + 1))
181+
fi
182+
done <<< "$sub_issues"
183+
184+
echo -e "Successfully removed ${removed_count} sub-issue(s) from issue #${parent_issue_number}"
185+
186+
print_success
187+
exit 0
188+
}
189+
190+
# Set an error handler to capture errors and perform any clean-up tasks:
191+
trap 'on_error $?' ERR
192+
193+
# Run main:
194+
main

0 commit comments

Comments
 (0)