Skip to content

Commit eca147f

Browse files
feat: add script to find and manage expired repository invitations (#105)
1 parent 68e7be0 commit eca147f

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

gh-cli/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,25 @@ Enable actions on repository - similar to [API example](./../api/enable-actions-
487487

488488
Finds attachments in issues, pull requests, and optionally, issue/pull request comments. This finds both screenshots and file attachments.
489489

490+
### find-expired-repository-invitations.sh
491+
492+
Finds and optionally cancels expired repository invitations across all repositories in an organization.
493+
494+
Usage:
495+
496+
```shell
497+
./find-expired-repository-invitations.sh joshjohanning-org
498+
./find-expired-repository-invitations.sh joshjohanning-org cancel
499+
```
500+
501+
Actions:
502+
503+
- `list` (default) - List all expired invitations across all repositories
504+
- `cancel` - Cancel all expired invitations across all repositories
505+
506+
> [!NOTE]
507+
> This requires admin access to the repositories in the organization.
508+
490509
### generate-release-notes-from-tags.sh
491510

492511
Generates release notes between two tags. See the [release notes docs](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes) on further customizations and the [API docs](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#generate-release-notes-content-for-a-release) for info on the API.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/bin/bash
2+
3+
# Find expired repository invitations across all repositories in an organization
4+
5+
function print_usage {
6+
echo "Usage: $0 <org> [action]"
7+
echo "Example: ./find-expired-repository-invitations.sh joshjohanning-org"
8+
echo "Example: ./find-expired-repository-invitations.sh joshjohanning-org cancel"
9+
echo ""
10+
echo "Actions:"
11+
echo " list (default) - List all expired invitations"
12+
echo " cancel - Cancel all expired invitations"
13+
echo ""
14+
echo "Note: This requires admin access to the repositories in the organization."
15+
exit 1
16+
}
17+
18+
if [ -z "$1" ]; then
19+
print_usage
20+
fi
21+
22+
org="$1"
23+
action="${2:-list}"
24+
25+
case "$action" in
26+
"list" | "cancel")
27+
;;
28+
*)
29+
echo "Error: Invalid action '$action'"
30+
print_usage
31+
;;
32+
esac
33+
34+
echo "Getting repositories for organization: $org"
35+
echo ""
36+
37+
# Get all repositories in the organization
38+
repos=$(gh api --paginate "/orgs/$org/repos" --jq '.[] | select(.archived == false) | .name')
39+
40+
if [ -z "$repos" ]; then
41+
echo "No repositories found or no access to organization: $org"
42+
exit 1
43+
fi
44+
45+
total_repos=$(echo "$repos" | wc -l)
46+
echo "Found $total_repos active repositories"
47+
48+
if [ "$action" = "cancel" ]; then
49+
echo ""
50+
echo "⚠️ WARNING: This will cancel all expired invitations!"
51+
echo "Press Enter to continue or Ctrl+C to abort..."
52+
read -r
53+
fi
54+
55+
echo ""
56+
57+
total_expired=0
58+
total_active=0
59+
repos_with_expired=0
60+
61+
while IFS= read -r repo; do
62+
if [ -n "$repo" ]; then
63+
echo "Checking repository: $org/$repo"
64+
65+
# Get all invitations for this repository
66+
invitations=$(gh api "/repos/$org/$repo/invitations" 2>/dev/null)
67+
68+
if [ $? -ne 0 ]; then
69+
echo " ❌ Error: Could not access invitations for $org/$repo (insufficient permissions)"
70+
echo ""
71+
continue
72+
fi
73+
74+
# Count total invitations
75+
invitation_count=$(echo "$invitations" | jq 'length' 2>/dev/null || echo "0")
76+
77+
if [ "$invitation_count" -eq 0 ]; then
78+
echo " ℹ️ No pending invitations"
79+
echo ""
80+
continue
81+
fi
82+
83+
# Process each invitation
84+
repo_expired_count=0
85+
repo_active_count=0
86+
87+
echo "$invitations" | jq -c '.[]' | while read -r invitation; do
88+
invitation_id=$(echo "$invitation" | jq -r '.id')
89+
invitee_login=$(echo "$invitation" | jq -r '.invitee.login // "unknown"')
90+
is_expired=$(echo "$invitation" | jq -r '.expired')
91+
permissions=$(echo "$invitation" | jq -r '.permissions')
92+
created_at=$(echo "$invitation" | jq -r '.created_at')
93+
94+
if [ "$is_expired" = "true" ]; then
95+
echo " 🔴 EXPIRED - ID: $invitation_id, User: $invitee_login, Permission: $permissions, Created: $created_at"
96+
((repo_expired_count++))
97+
98+
if [ "$action" = "cancel" ]; then
99+
echo " 🗑️ Canceling expired invitation..."
100+
gh api -X DELETE "/repos/$org/$repo/invitations/$invitation_id"
101+
if [ $? -eq 0 ]; then
102+
echo " ✅ Successfully canceled invitation"
103+
else
104+
echo " ❌ Failed to cancel invitation"
105+
fi
106+
fi
107+
else
108+
echo " 🟢 ACTIVE - ID: $invitation_id, User: $invitee_login, Permission: $permissions, Created: $created_at"
109+
((repo_active_count++))
110+
fi
111+
done
112+
113+
# Count expired and active invitations for this repo
114+
expired_in_repo=$(echo "$invitations" | jq '[.[] | select(.expired == true)] | length' 2>/dev/null || echo "0")
115+
active_in_repo=$(echo "$invitations" | jq '[.[] | select(.expired == false)] | length' 2>/dev/null || echo "0")
116+
117+
echo " 📊 Summary: $expired_in_repo expired, $active_in_repo active"
118+
119+
if [ "$expired_in_repo" -gt 0 ]; then
120+
((repos_with_expired++))
121+
total_expired=$((total_expired + expired_in_repo))
122+
fi
123+
124+
total_active=$((total_active + active_in_repo))
125+
126+
echo ""
127+
fi
128+
done <<< "$repos"
129+
130+
# Final summary
131+
echo "=================================================="
132+
echo "SUMMARY"
133+
echo "=================================================="
134+
echo "Total repositories checked: $total_repos"
135+
echo "Repositories with expired invitations: $repos_with_expired"
136+
echo "Total expired invitations: $total_expired"
137+
echo "Total active invitations: $total_active"
138+
139+
if [ "$action" = "cancel" ] && [ "$total_expired" -gt 0 ]; then
140+
echo ""
141+
echo "✅ Expired invitations have been canceled"
142+
elif [ "$total_expired" -eq 0 ]; then
143+
echo ""
144+
echo "🎉 No expired invitations found!"
145+
fi
146+
147+
if [ "$total_expired" -gt 0 ] && [ "$action" = "list" ]; then
148+
echo ""
149+
echo "💡 To cancel all expired invitations, run:"
150+
echo " $0 $org cancel"
151+
fi

0 commit comments

Comments
 (0)