Skip to content

Commit 0b8430c

Browse files
refactor: improve get-repository-users-permission-and-source.sh output and usability (#151)
* refactor: improve get-repository-users-permission-and-source.sh output and usability - Add input parameters for org, repo, and affiliation instead of hardcoded values - Add input validation with usage message - Format output as aligned table with user, effective permission, and sources - Filter out redundant Repository sources inherited from org admin access - Apply heuristic to display MAINTAIN/TRIAGE roles correctly in sources * docs: update comments to clarify GraphQL permissionSources API limitations * refactor: enhance permission mapping and output formatting in get-repository-users-permission-and-source.sh * docs: enhance README and script comments for get-repository-users-permission-and-source.sh * fix: validate affiliation input for get-repository-users-permission-and-source.sh
1 parent ca52cd9 commit 0b8430c

2 files changed

Lines changed: 102 additions & 7 deletions

File tree

gh-cli/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,16 @@ joshjohanning,admin
13451345

13461346
### get-repository-users-permission-and-source.sh
13471347

1348-
Returns the permission for everyone who can access the repo and how they access it (direct, team, org)
1348+
Returns the permission for everyone who can access a repository and how they access it (direct, team, organization). Uses the REST API to get accurate team role names including maintain, triage, and custom roles.
1349+
1350+
Example output:
1351+
1352+
```text
1353+
USER EFFECTIVE SOURCES
1354+
joshjohanning ADMIN org-member(ADMIN), team:admin-team(WRITE), team:approver-team(WRITE)
1355+
FluffyCarlton MAINTAIN direct(MAINTAIN), org-member(READ)
1356+
joshgoldfishturtle ADMIN org-member(READ), team:compliance-team(ADMIN)
1357+
```
13491358

13501359
### get-repository.sh
13511360

gh-cli/get-repository-users-permission-and-source.sh

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,74 @@
11
#!/bin/bash
22

3-
# gh cli's token needs to be able to admin org - run this first if it can't
4-
# gh auth refresh -h github.com -s admin:org
3+
# Returns the permission for everyone who can access a repository and how they
4+
# access it (direct, team, organization)
5+
#
6+
# Uses the REST API to get accurate team role names (maintain, triage) since the
7+
# GraphQL permissionSources API only returns READ, WRITE, and ADMIN. A heuristic
8+
# is also applied to direct sources to correct MAINTAIN/TRIAGE labels.
9+
#
10+
# gh cli's token needs to be able to admin the organization - run this first if needed:
11+
# gh auth refresh -h github.com -s admin:org
12+
#
13+
# Usage:
14+
# ./get-repository-users-permission-and-source.sh <org> <repo> [affiliation] [hostname]
15+
#
16+
# affiliation can be: OUTSIDE, DIRECT, ALL (default: ALL)
17+
# hostname: GitHub hostname (default: github.com), e.g. github.example.com
518

6-
# affiliation can be: OUTSIDE, DIRECT, ALL
19+
# Example output:
20+
#
21+
# USER EFFECTIVE SOURCES
22+
# joshjohanning ADMIN org-member(ADMIN), team:admin-team(WRITE), team:approver-team(WRITE)
23+
# FluffyCarlton MAINTAIN direct(MAINTAIN), org-member(READ)
24+
# joshgoldfishturtle ADMIN org-member(READ), team:compliance-team(ADMIN)
725

8-
# returns the permission for everyone who can access the repo and how they access it (direct, team, org)
926

10-
gh api graphql --paginate -f owner='joshjohanning-org' -f repo='ghas-demo' -f affiliation='ALL' -f query='
27+
if [ -z "$2" ]; then
28+
echo "Usage: $0 <org> <repo> [affiliation] [hostname]"
29+
echo " affiliation: OUTSIDE, DIRECT, ALL (default: ALL)"
30+
echo " hostname: GitHub hostname (default: github.com)"
31+
exit 1
32+
fi
33+
34+
org="$1"
35+
repo="$2"
36+
affiliation_input="${3:-ALL}"
37+
affiliation="$(echo "$affiliation_input" | tr '[:lower:]' '[:upper:]')"
38+
hostname="${4:-github.com}"
39+
40+
case "$affiliation" in
41+
OUTSIDE|DIRECT|ALL)
42+
;;
43+
*)
44+
echo "Error: invalid affiliation '$affiliation_input'. Must be OUTSIDE, DIRECT, or ALL."
45+
exit 1
46+
;;
47+
esac
48+
49+
# Map REST permission names (pull/push) to GraphQL-style names (READ/WRITE)
50+
map_permission() {
51+
case "$1" in
52+
pull) echo "READ" ;;
53+
triage) echo "TRIAGE" ;;
54+
push) echo "WRITE" ;;
55+
maintain) echo "MAINTAIN" ;;
56+
admin) echo "ADMIN" ;;
57+
*) echo "$1" | tr '[:lower:]' '[:upper:]' ;;
58+
esac
59+
}
60+
61+
# Get true team permissions via REST API and build a sed command to fix labels
62+
sed_cmd=""
63+
while IFS=$'\t' read -r slug perm; do
64+
mapped=$(map_permission "$perm")
65+
sed_cmd="${sed_cmd}s/team:${slug}\([^)]*\)/team:${slug}(${mapped})/g;"
66+
done <<EOF
67+
$(gh api --hostname "$hostname" --paginate "/repos/$org/$repo/teams?per_page=100" --jq '.[] | [.slug, .permission] | @tsv')
68+
EOF
69+
70+
# Get source details via GraphQL
71+
raw_output=$(gh api graphql --hostname "$hostname" --paginate -f owner="$org" -f repo="$repo" -f affiliation="$affiliation" -f query='
1172
query ($owner: String!, $repo: String!, $affiliation: CollaboratorAffiliation!, $endCursor: String) {
1273
repository(owner:$owner, name:$repo) {
1374
name
@@ -44,4 +105,29 @@ query ($owner: String!, $repo: String!, $affiliation: CollaboratorAffiliation!,
44105
}
45106
}
46107
}
47-
}'
108+
}' --jq '
109+
.data.repository.collaborators.edges[] |
110+
.node.login as $user |
111+
.permission as $effective |
112+
(.permissionSources | map(select(.source.type == "Organization") | .permission)) as $org_perms |
113+
[.permissionSources[] |
114+
if .source.type == "Organization" then "org-member(\(.permission))"
115+
elif .source.type == "Team" then "team:\(.source.name)(\(.permission))"
116+
elif (.permission as $p | $org_perms | any(. == $p)) | not then
117+
# permissionSources only returns READ/WRITE/ADMIN - use effective for MAINTAIN/TRIAGE
118+
if .permission == "WRITE" and $effective == "MAINTAIN" then "direct(MAINTAIN)"
119+
elif .permission == "READ" and $effective == "TRIAGE" then "direct(TRIAGE)"
120+
else "direct(\(.permission))"
121+
end
122+
else empty
123+
end
124+
] | unique | join(", ") |
125+
"\($user) | \($effective) | \(.)"
126+
')
127+
128+
# Fix team permission labels using REST data
129+
if [ -n "$sed_cmd" ]; then
130+
raw_output=$(echo "$raw_output" | sed -E "$sed_cmd")
131+
fi
132+
133+
(echo "USER | EFFECTIVE | SOURCES" && echo "$raw_output") | column -t -s '|'

0 commit comments

Comments
 (0)