├── README.md └── gh-project /README.md: -------------------------------------------------------------------------------- 1 | # `gh project` GitHub CLI extension 2 | 3 | `gh project` is a [GitHub CLI](https://github.com/cli/cli) extension for listing projects and linking/unlinking projects to/from issues/PRs interactively. 4 | 5 | ## Installation 6 | 7 | ```console 8 | $ gh extension install micnncim/gh-project 9 | ``` 10 | 11 | This extension depends on [fzf](https://github.com/junegunn/fzf) as a fuzzy finder. To install using Homebrew: 12 | 13 | ```console 14 | $ brew install fzf 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```console 20 | $ # Lists all projects in a current repository. 21 | $ gh project list 22 | $ # Lists all projects in a given organization. 23 | $ gh project list --org=acme-org 24 | # # Links a project in a current repository to the issue or PR #123 interactively. 25 | $ gh project add 123 26 | # # Links a project in a given organization to the issue or PR #123 interactively. 27 | $ gh project add 123 --org=acme-org 28 | # # Unlinks a project a current repository from the issue or PR #123 interactively. 29 | $ gh project remove 123 30 | # # Unlinks a project in a given organization from the issue or PR #123 interactively. 31 | $ gh project remove 123 --org=acme-org 32 | ``` 33 | 34 | ## Environment Variables 35 | 36 | - `GH_PROJECT_ORGANIZATION` (optional): If this environment variable is set, projects in the organization are listed rather than a current repository even without the flag `--org`. 37 | -------------------------------------------------------------------------------- /gh-project: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | readonly GH_PROJECT_ORGANIZATION 6 | 7 | readonly EXECUTABLES=(fzf) 8 | 9 | usage() { 10 | cat <&2 33 | } 34 | 35 | # Determine if executables are in the PATH. 36 | check_executables() { 37 | for e in "${EXECUTABLES[@]}"; do 38 | if ! type -p "${e}" >/dev/null; then 39 | echo "${e} not found on the system" >&2 40 | return 1 41 | fi 42 | done 43 | } 44 | 45 | list() { 46 | local org 47 | while [[ "$#" -gt 0 ]]; do 48 | case "$1" in 49 | --org=*) 50 | org=${1#*=} 51 | ;; 52 | *) ;; 53 | esac 54 | shift 55 | done 56 | 57 | if [[ -n "${GH_PROJECT_ORGANIZATION}" ]]; then 58 | org="${GH_PROJECT_ORGANIZATION}" 59 | fi 60 | 61 | local endpoint="repos/:owner/:repo/projects" 62 | if [[ -n "${org}" ]]; then 63 | endpoint="orgs/${org}/projects" 64 | fi 65 | 66 | PAGER='cat' gh api "${endpoint}" --paginate --template '{{- range .}}{{ .name | printf "%s\n" }}{{- end -}}' 67 | } 68 | 69 | edit() { 70 | local -r cmd="${1}" 71 | local -r number="${2}" 72 | local -r flags="${*:3}" 73 | 74 | local project 75 | project="$(list "${flags[@]}" | fzf)" 76 | 77 | local op preposition 78 | case $cmd in 79 | 'add') 80 | op='link' 81 | preposition='to' 82 | ;; 83 | 'remove') 84 | op='unlink' 85 | preposition='from' 86 | ;; 87 | esac 88 | 89 | if gh issue view "${number}" >/dev/null 2>&1; then 90 | gh issue edit "${number}" "--${cmd}-project" "${project}" >/dev/null 91 | info "Project '${project}' has been ${op}ed ${preposition} issue #$number" 92 | return 93 | fi 94 | 95 | if gh pr view "${number}" >/dev/null 2>&1; then 96 | gh pr edit "${number}" "--${cmd}-project" "${project}" >/dev/null 97 | info "Project '${project}' has been ${op}ed ${preposition} pull request #$number" 98 | return 99 | fi 100 | } 101 | 102 | main() { 103 | if [[ "$#" -lt 1 ]]; then 104 | usage 105 | return 1 106 | fi 107 | 108 | if ! check_executables; then 109 | usage 110 | return 1 111 | fi 112 | 113 | local -r cmd="${1}" 114 | 115 | case $cmd in 116 | 'list') 117 | list "${@:2}" 118 | ;; 119 | 'add' | 'remove') 120 | edit "${cmd}" "${@:2}" 121 | ;; 122 | *) 123 | usage 124 | return 1 125 | ;; 126 | esac 127 | } 128 | 129 | main "$@" 130 | --------------------------------------------------------------------------------