├── LICENSE ├── README.md └── bin ├── hub-comment ├── hub-comment-mentions ├── hub-create-from-template ├── hub-milestones ├── hub-pr-base ├── hub-pr-files-changed ├── hub-pr-merge ├── hub-pr-merge-upstream ├── hub-pr-project ├── hub-pr-ready ├── hub-pr-reviews ├── hub-pr-with-commit ├── hub-repo-rename ├── hub-repos └── hub-search-repos /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `hub api` utils 2 | 3 | This is a collection of small utility scripts to showcase examples of automating 4 | GitHub tasks from the command-line via the [`hub api`](https://hub.github.com/hub-api.1.html) 5 | command. 6 | 7 | Some available commands are: 8 | 9 | * [hub-comment](./bin/hub-comment) - post a comment to an issue/PR 10 | * [hub-pr-merge](./bin/hub-pr-merge) - The Merge Button™ on the command-line 11 | * [hub-pr-with-commit](./bin/hub-pr-with-commit) - find the PR which originated a commit 12 | * [hub-repo-rename](./bin/hub-repo-rename) - rename the current repo 13 | 14 | Prerequisites: 15 | 16 | * [hub 2.12+](https://github.com/github/hub/releases) 17 | * git 1.8+ 18 | * bash (for many scripts) 19 | * [jq](https://stedolan.github.io/jq/) (for some scripts) 20 | * standard POSIX tools such as `grep`, `awk`, `sed` 21 | 22 | Installation with Homebrew: 23 | 24 | ``` 25 | brew install hub jq 26 | ``` 27 | 28 | Then drop individual scripts from this repo into your $PATH and edit them or use 29 | them as you see fit. 30 | 31 | ## How to contribute 32 | 33 | I would love to accept your scripts into this repository! Please send your pull 34 | requests. The prerequisites for submission are simple: 35 | 36 | - Your script should do something useful or interesting with the GitHub API 37 | using the `hub api` command. Either REST or GraphQL APIs might be used. 38 | 39 | - The script should be written in one of the widespread shells such as `bash` or 40 | `zsh`, or in often-available interpreted languages such as `ruby`, `node`, or 41 | `python`. 42 | 43 | - When shelling out to other commands, please try to stick to `jq` and the POSIX 44 | set of command-line utilities to keep scripts portable. 45 | 46 | - There are no other rules. Go wild! But please note that when you do submit 47 | contributions to this repository, you are releasing your code into the public 48 | domain per [LICENSE](./LICENSE). 49 | 50 | ## Tips for `hub api` 51 | 52 | The GitHub REST API can be queried like so: 53 | 54 | ```sh 55 | # The placeholders "{owner}" and "{repo}" get populated with values from the 56 | # current git repository: 57 | hub api 'repos/{owner}/{repo}/issues' 58 | ``` 59 | 60 | The response is always JSON, which can be parsed using `jq` in shell scripts. 61 | Alternatively, you may specify the `--flat` flag to have `hub api` output data 62 | in a line-based format. 63 | 64 | The GraphQL API can be queried like this: 65 | 66 | ```sh 67 | hub api graphql -f query='QUERY' 68 | ``` 69 | 70 | For more information/examples, see https://hub.github.com/hub-api.1.html. 71 | -------------------------------------------------------------------------------- /bin/hub-comment: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-comment [] 3 | # 4 | # Creates a comment in the specified issue or pull request. If isn't 5 | # given as argument, it is read from standard input. 6 | # 7 | # Author: @mislav 8 | set -e 9 | 10 | issue_number="${1?}" 11 | shift 1 12 | 13 | if [ -n "$1" ]; then 14 | body_field="-fbody=$1" 15 | shift 1 16 | else 17 | body_field="-Fbody=@-" 18 | fi 19 | 20 | hub api "repos/{owner}/{repo}/issues/${issue_number}/comments" \ 21 | "$body_field" "$@" -t | awk '/^\.html_url\t/ { print $(NF) }' 22 | -------------------------------------------------------------------------------- /bin/hub-comment-mentions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-comment-mentions 3 | # 4 | # Reads the text of a GitHub comment from standard input and extracts the names 5 | # of every person who would get subscribed to this thread via @-mentions. Needs 6 | # the `org:read` scope for looking up members of mentioned teams. 7 | # 8 | # Author: @mislav 9 | set -e 10 | 11 | find_members() { 12 | local org="${1%/*}" 13 | local team="${1#*/}" 14 | hub api graphql --paginate -t -f org="$org" -f team="$team" -f query=' 15 | query($org: String!, $team: String!, $endCursor: String) { 16 | organization(login: $org) { 17 | team(slug: $team) { 18 | members(first: 100, after: $endCursor) { 19 | nodes { login } 20 | pageInfo { 21 | hasNextPage 22 | endCursor 23 | } 24 | } 25 | } 26 | } 27 | } 28 | ' | awk '/\.login\t/ {print $2}' 29 | } 30 | 31 | extract_mentions() { 32 | grep -o '@[a-zA-Z0-9/_-]\+' | sort -fu | sed 's/^@//' 33 | } 34 | 35 | cat "$@" | extract_mentions | while read mention; do 36 | if [[ $mention == */* ]]; then 37 | find_members "$mention" 38 | else 39 | echo "$mention" 40 | fi 41 | done | sort -fu 42 | -------------------------------------------------------------------------------- /bin/hub-create-from-template: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-create-from-template [/] [--public] 3 | # git clone `hub-create-from-template ` 4 | # 5 | # Creates a new GitHub repository based on a repository template. 6 | set -euo pipefail 7 | 8 | TEMPLATE_REPO= 9 | REPO_OWNER= 10 | REPO_NAME= 11 | PRIVATE=true 12 | MIME_TYPE='application/vnd.github.v3+json;charset=utf-8, application/vnd.github.baptiste-preview' 13 | 14 | for arg; do 15 | case "$arg" in 16 | --public ) PRIVATE=false ;; 17 | * ) 18 | if [ -z "$TEMPLATE_REPO" ]; then 19 | TEMPLATE_REPO="$arg" 20 | else 21 | REPO_OWNER="${arg%/*}" 22 | [ "$REPO_OWNER" != "$arg" ] || REPO_OWNER= 23 | REPO_NAME="${arg#*/}" 24 | fi 25 | ;; 26 | esac 27 | done 28 | 29 | api() { 30 | local response 31 | if response="$(hub api -H "Accept: $MIME_TYPE" "$@")"; then 32 | printf "%s" "$response" 33 | else 34 | printf "%s" "$response" | jq . >&2 35 | return 1 36 | fi 37 | } 38 | 39 | create_from_template() { 40 | local source_repo="${1?}" 41 | shift 1 42 | api "repos/${source_repo}/generate" "$@" 43 | } 44 | 45 | create_args=( 46 | "$TEMPLATE_REPO" 47 | -f name="$REPO_NAME" 48 | -F private="$PRIVATE" 49 | ) 50 | [ -z "$REPO_OWNER" ] || create_args+=(-f owner="$REPO_OWNER") 51 | 52 | create_from_template "${create_args[@]}" | jq -r '.html_url' 53 | -------------------------------------------------------------------------------- /bin/hub-milestones: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-milestones 3 | # 4 | # List the open milestones for the current repository. 5 | # 6 | # Author: @mislav 7 | set -e 8 | 9 | milestones() { 10 | hub api --paginate graphql "$@" -f query=' 11 | query($per_page: Int = 100, $endCursor: String) { 12 | repository(owner: "{owner}", name: "{repo}") { 13 | milestones(first: $per_page, after: $endCursor, states: OPEN, orderBy: {field:CREATED_AT, direction:DESC}) { 14 | nodes { 15 | title 16 | number 17 | } 18 | pageInfo { 19 | hasNextPage 20 | endCursor 21 | } 22 | } 23 | } 24 | } 25 | ' | jq -r '.data.repository.milestones.nodes[] | [.number,.title] | @tsv' 26 | } 27 | 28 | milestones "$@" 29 | -------------------------------------------------------------------------------- /bin/hub-pr-base: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-pr-base 3 | # 4 | # Show the base branch of the pull request associated with the current branch. 5 | # 6 | # Author: @mislav 7 | set -e 8 | 9 | published-branch() { 10 | local branch 11 | if branch="$(git rev-parse --symbolic-full-name @{upstream} 2>/dev/null)"; then 12 | branch="${branch#refs/remotes/}" 13 | echo "${branch#*/}" 14 | else 15 | branch="$(git rev-parse --symbolic-full-name HEAD)" 16 | echo "${branch#refs/heads/}" 17 | fi 18 | } 19 | 20 | base-branch() { 21 | local branch 22 | branch="$(published-branch)" 23 | 24 | hub api graphql -f branch="$branch" "$@" -f query=' 25 | query($branch: String!) { 26 | repository(owner: "{owner}", name: "{repo}") { 27 | pullRequests(headRefName: $branch, states: OPEN, first: 10) { 28 | edges { 29 | node { 30 | baseRefName 31 | isCrossRepository 32 | } 33 | } 34 | } 35 | } 36 | } 37 | ' 38 | } 39 | 40 | base-branch "$@" | jq -r ' 41 | .data.repository.pullRequests.edges[].node | 42 | select(.isCrossRepository == false) | 43 | .baseRefName 44 | ' | head -1 45 | -------------------------------------------------------------------------------- /bin/hub-pr-files-changed: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-pr-files-changed 3 | # 4 | # Lists files changed in a specific pull request. 5 | # 6 | # Author: @mislav 7 | set -e 8 | 9 | list-pr-files() { 10 | local pr_number="${1?}" 11 | shift 1 12 | 13 | hub api -H 'accept: application/vnd.github.ocelot-preview+json' graphql -F "pr=$pr_number" "$@" -f query=' 14 | query($pr: Int!, $per_page: Int = 100, $endCursor: String) { 15 | repository(owner: "{owner}", name: "{repo}") { 16 | pullRequest(number: $pr) { 17 | files(first: $per_page, after: $endCursor) { 18 | edges { 19 | node { 20 | path 21 | } 22 | } 23 | pageInfo { 24 | endCursor 25 | hasNextPage 26 | } 27 | } 28 | } 29 | } 30 | } 31 | ' 32 | } 33 | 34 | list-pr-files "$@" --paginate -t | awk -F '\t' '/\.path\t/ { print $2 }' 35 | -------------------------------------------------------------------------------- /bin/hub-pr-merge: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: hub-pr-merge 4 | # 5 | # Cause a pull request to be merged into its respective base branch. 6 | # 7 | # Author: Oliver Joseph Ash 8 | 9 | # If a script errors, force the script to fail immediately. 10 | set -e 11 | 12 | ID=$1 13 | shift 1 14 | 15 | # https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button 16 | hub api -XPUT "repos/{owner}/{repo}/pulls/$ID/merge" "$@" 17 | -------------------------------------------------------------------------------- /bin/hub-pr-merge-upstream: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Merge the latest master branch into the current pull request while taking into 3 | # account that the base branch of the current PR might be different than master. 4 | # 5 | # When the current branch has PR A opened that is based on PR B, B is based on C, 6 | # and C ultimately has "master" as base branch, do this: 7 | # 8 | # 1. merge "master" into C; 9 | # 2. merge C into B; 10 | # 3. merge B into A. 11 | # 12 | # All merges are automatically pushed. 13 | 14 | set -euo pipefail 15 | 16 | remote=origin 17 | 18 | if ! type -p hub >/dev/null; then 19 | echo "This command requires hub: \`brew install hub'" >&2 20 | exit 1 21 | fi 22 | if ! hub help api --plain-text &>/dev/null; then 23 | echo "You need to upgrade hub: \`brew install hub'" >&2 24 | exit 1 25 | fi 26 | if ! type -p jq >/dev/null; then 27 | echo "This command requires jq: \`brew install jq'" >&2 28 | exit 1 29 | fi 30 | 31 | [ -e ./bin/git ] && alias git=./bin/git 32 | 33 | if default_branch="$(git symbolic-ref -q refs/remotes/$remote/HEAD)"; then 34 | default_branch="${default_branch#refs/remotes/}" 35 | default_branch="${default_branch#*/}" 36 | else 37 | default_branch=master 38 | fi 39 | 40 | if current_branch="$(git rev-parse --symbolic-full-name @{upstream} 2>/dev/null)"; then 41 | current_branch="${current_branch#refs/remotes/}" 42 | current_branch="${current_branch#*/}" 43 | else 44 | current_branch="$(git rev-parse --symbolic-full-name HEAD)" 45 | current_branch="${current_branch#refs/heads/}" 46 | fi 47 | 48 | find-base-branch() { 49 | local branch="${1?}" 50 | local response errors 51 | 52 | response="$(hub api graphql -f branch="$branch" -f query=' 53 | query($branch: String!) { 54 | repository(owner: "{owner}", name: "{repo}") { 55 | pullRequests(headRefName: $branch, states: OPEN, first: 10) { 56 | edges { 57 | node { 58 | baseRefName 59 | isCrossRepository 60 | } 61 | } 62 | } 63 | } 64 | } 65 | ')" 66 | 67 | errors="$(jq -r '(select(.message) | .message) , .errors[]?.message' <<<"$response" 2>/dev/null || true)" 68 | if [ -n "$errors" ]; then 69 | printf "Error fetching '%s' info from GitHub API:\n%s\n" "$branch" "$errors" >&2 70 | if grep -q 'SAML enforcement' <<<"$errors"; then 71 | echo "Visit https://github.com/settings/tokens and choose 'Enable SSO' for hub's token." >&2 72 | fi 73 | return 1 74 | fi 75 | 76 | jq -r '.data.repository.pullRequests.edges[].node | 77 | select(.isCrossRepository == false) | 78 | .baseRefName' <<<"$response" | head -1 79 | } 80 | 81 | stack=("$current_branch") 82 | while true; do 83 | base_branch="$(find-base-branch "${stack[0]}")" 84 | if [ -z "$base_branch" ]; then 85 | echo "No open pull request found for branch '${stack[0]}'" >&2 86 | exit 1 87 | fi 88 | stack=("$base_branch" "${stack[@]}") 89 | if [ "$base_branch" = "$default_branch" ]; then 90 | break 91 | fi 92 | done 93 | 94 | trap "git checkout $current_branch" ERR 95 | 96 | branch_to_merge= 97 | sha_to_merge= 98 | for branch in "${stack[@]}"; do 99 | echo "Fetching branch: $branch" 100 | git fetch -q "$remote" "$branch" 101 | if ! git checkout -q -b "$branch" FETCH_HEAD 2>/dev/null; then 102 | git checkout -q "$branch" 103 | git merge -q --no-stat --ff-only FETCH_HEAD 104 | fi 105 | if [ -n "$branch_to_merge" ]; then 106 | echo "Merging branch: $branch_to_merge" 107 | git merge -q --no-stat --no-ff --no-edit -m "Merge branch '$branch_to_merge' into $branch" "$sha_to_merge" 108 | git push -q "$remote" "HEAD:$branch" 109 | fi 110 | branch_to_merge="$branch" 111 | sha_to_merge="$(git rev-parse HEAD)" 112 | done 113 | -------------------------------------------------------------------------------- /bin/hub-pr-project: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-pr-project 3 | # 4 | # Adds a Pull Request by number to a Project specified by name. 5 | set -euo pipefail 6 | 7 | api() { 8 | hub api -H 'accept: application/vnd.github.inertia-preview+json' "$@" 9 | } 10 | 11 | pr_number="${1?}" 12 | project_name="${2?}" 13 | 14 | # find out content_id of a pull request 15 | pr_id="$(api "repos/{owner}/{repo}/pulls/${pr_number}" | jq -r '.id')" 16 | 17 | # find the project by name 18 | columns_url="$(api -XGET "repos/{owner}/{repo}/projects" -f state=open | \ 19 | jq -r '.[] | select((.name | ascii_downcase) == ($project_name | ascii_downcase)) | .columns_url' \ 20 | --arg project_name "$project_name")" 21 | 22 | # find out cards endpoint for a project's first column 23 | cards_url="$(api "$columns_url" | jq -r '.[0].cards_url')" 24 | 25 | # add a card 26 | api "$cards_url" -F content_id="$pr_id" -f content_type=PullRequest >/dev/null 27 | -------------------------------------------------------------------------------- /bin/hub-pr-ready: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-pr-ready 3 | # 4 | # Marks a pull request as "ready for review", i.e. a non-draft. 5 | # 6 | # Author: @mislav 7 | set -e 8 | 9 | num="${1?}" 10 | 11 | id="$(hub api -t graphql -f query=' 12 | query($pr: Int!) { 13 | repository(owner: "{owner}", name: "{repo}") { 14 | pullRequest(number: $pr) {id} 15 | } 16 | } 17 | ' -F pr=$num | awk -F'\t' '/\.id\t/ {print $2}')" 18 | 19 | hub api graphql -f query=' 20 | mutation($prID: String!) { 21 | markPullRequestReadyForReview( 22 | input: {pullRequestId: $prID} 23 | ){ clientMutationId } 24 | } 25 | ' -f "prID=$id" >/dev/null 26 | -------------------------------------------------------------------------------- /bin/hub-pr-reviews: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-pr-reviews [] 3 | # 4 | # Show the state of reviews for a pull request. When isn't 5 | # specified, find the pull request associated with the current branch. 6 | # 7 | # Author: @mislav 8 | set -e 9 | 10 | published-branch() { 11 | local branch 12 | if branch="$(git rev-parse --symbolic-full-name @{upstream} 2>/dev/null)"; then 13 | branch="${branch#refs/remotes/}" 14 | echo "${branch#*/}" 15 | else 16 | branch="$(git rev-parse --symbolic-full-name HEAD)" 17 | echo "${branch#refs/heads/}" 18 | fi 19 | } 20 | 21 | reviews() { 22 | local branch_field query jq_query 23 | if [ "$1" -gt 0 ] 2>/dev/null; then 24 | branch_field="-Fpr_number=$1" 25 | shift 1 26 | query=' 27 | query($pr_number: Int!, $per_page: Int = 100, $endCursor: String) { 28 | repository(owner: "{owner}", name: "{repo}") { 29 | pullRequest(number: $pr_number) { 30 | ...reviewFields 31 | } 32 | } 33 | } 34 | ' 35 | jq_query='.data.repository.pullRequest' 36 | else 37 | branch_field="-fbranch=$(published-branch)" 38 | query=' 39 | query($branch: String!, $per_page: Int = 100, $endCursor: String) { 40 | repository(owner: "{owner}", name: "{repo}") { 41 | pullRequests(headRefName: $branch, states: OPEN, first: 1) { 42 | edges { 43 | node { 44 | ...reviewFields 45 | } 46 | } 47 | } 48 | } 49 | } 50 | ' 51 | jq_query='.data.repository.pullRequests.edges[].node' 52 | fi 53 | 54 | local fragment=' 55 | fragment reviewFields on PullRequest { 56 | reviewRequests(first: 100) { 57 | edges { 58 | node { 59 | requestedReviewer { 60 | ... on User { 61 | login 62 | } 63 | ... on Team { 64 | combinedSlug 65 | } 66 | } 67 | } 68 | } 69 | } 70 | reviews(first: $per_page, after: $endCursor) { 71 | edges { 72 | node { 73 | author { 74 | login 75 | } 76 | state 77 | } 78 | } 79 | } 80 | } 81 | ' 82 | 83 | hub api graphql "$branch_field" "$@" -F query=@- <<<"$query $fragment" | \ 84 | jq -r "$jq_query"' | 85 | "Requested:", 86 | (.reviewRequests.edges[].node.requestedReviewer | .login // .combinedSlug), 87 | "\nReviewed:", 88 | (.reviews.edges[].node | [.author.login,.state] | @tsv) 89 | ' 90 | } 91 | 92 | reviews "$@" 93 | -------------------------------------------------------------------------------- /bin/hub-pr-with-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Usage: hub-pr-with-commit [PROJECT] SHA 4 | # 5 | # Author: Fabio Rehm 6 | 7 | set -e 8 | 9 | PROJECT= 10 | COMMIT= 11 | 12 | project-from-remote() { 13 | git remote get-url "${1?}" | \ 14 | grep -o 'github\.com[/:].\+' | \ 15 | sed 's/github\.com.//; s/\.git$//' 16 | } 17 | 18 | if [ $# -eq 1 ]; then 19 | PROJECT="$(project-from-remote origin)" 20 | COMMIT="${1}" 21 | shift 1 22 | elif [ $# -ge 2 ]; then 23 | PROJECT="${1}" 24 | COMMIT="${2}" 25 | shift 2 26 | else 27 | echo "Usage: hub-pr-with-commit [PROJECT] SHA" 28 | exit 1 29 | fi 30 | 31 | hub api graphql -f q="${COMMIT} type:pr repo:${PROJECT}" "$@" -f query=' 32 | query($q: String!) { 33 | search(query: $q, type: ISSUE, first: 3) { 34 | nodes { 35 | ... on PullRequest { 36 | url 37 | title 38 | } 39 | } 40 | } 41 | } 42 | ' | jq -r '.data.search.nodes[] | "\(.url) - \(.title)"' 43 | -------------------------------------------------------------------------------- /bin/hub-repo-rename: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-repo-rename 3 | # 4 | # Author: @mislav 5 | set -e 6 | 7 | url_type='html_url' 8 | case "$(git remote get-url origin)" in 9 | 'git@'* | 'git+ssh:'* ) 10 | url_type='ssh_url' 11 | ;; 12 | 'git:'* ) 13 | url_type='git_url' 14 | ;; 15 | esac 16 | 17 | new_url="$(hub api -t -XPATCH 'repos/{owner}/{repo}' -f name="${1?}" | \ 18 | awk "/^\\.${url_type}\\t/ { print \$2 }")" 19 | 20 | [ -n "$new_url" ] || exit 1 21 | 22 | git remote set-url origin "$new_url" 23 | -------------------------------------------------------------------------------- /bin/hub-repos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-repos 3 | # 4 | # List all repositories under the GitHub account. 5 | # 6 | # Author: @mislav 7 | set -e 8 | 9 | repos() { 10 | local owner="${1?}" 11 | shift 1 12 | hub api --paginate graphql -f owner="$owner" "$@" -f query=' 13 | query($owner: String!, $per_page: Int = 100, $endCursor: String) { 14 | repositoryOwner(login: $owner) { 15 | repositories(first: $per_page, after: $endCursor, ownerAffiliations: OWNER) { 16 | nodes { 17 | nameWithOwner 18 | } 19 | pageInfo { 20 | hasNextPage 21 | endCursor 22 | } 23 | } 24 | } 25 | } 26 | ' 27 | } 28 | 29 | repos "$@" -t | awk '/\.nameWithOwner/ { print $(NF) }' 30 | -------------------------------------------------------------------------------- /bin/hub-search-repos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: hub-search-repos [] 3 | # 4 | # List N repositories (default: 10) and their stars count that match . 5 | # 6 | # Author: @mislav 7 | set -e 8 | 9 | search() { 10 | local args=() 11 | if [ "${1#-}" -gt 0 ] 2>/dev/null; then 12 | args+=(-F per_page="${1#-}") 13 | shift 1 14 | fi 15 | args+=(-f q="${1?}") 16 | shift 1 17 | 18 | hub api graphql "${args[@]}" "$@" -f query=' 19 | query($q: String!, $per_page: Int = 10, $endCursor: String) { 20 | search(query: $q, type: REPOSITORY, first: $per_page, after: $endCursor) { 21 | nodes { 22 | ...on Repository { 23 | nameWithOwner 24 | stargazers { 25 | totalCount 26 | } 27 | } 28 | } 29 | pageInfo { 30 | hasNextPage 31 | endCursor 32 | } 33 | } 34 | } 35 | ' 36 | } 37 | 38 | search "$@" | jq -r '.data.search.nodes[] | [.nameWithOwner,.stargazers.totalCount] | @tsv' 39 | --------------------------------------------------------------------------------