Skip to content

Instantly share code, notes, and snippets.

@rbf
Last active May 17, 2016 13:05
Show Gist options
  • Save rbf/5983134 to your computer and use it in GitHub Desktop.
Save rbf/5983134 to your computer and use it in GitHub Desktop.
Bash script to (re)create a set of issue labels in a GitHub repo at once.

Description

Bash script to (re)create a set of issue labels in a GitHub repo at once. This is very handy when we want to replicate a set of labels every time we create a new repo. The repo can be public or private, and belonging to your user or to an organisation your user belongs to.

Note: The script aims also to be one example of bash scripting, so that I can use it as reference for several code snippets

Execute script

You can download the script if you want to modify it, and make it executable. You can do it with following shell command:

curl -LO https://gist.github.com/rbf/5983134/raw/create_github_labels.sh && chmod +x ./create_github_labels.sh

If you just want to execute it directly, without modifying it or saving a local copy, you can execute following command:

bash <(curl -sSL https://gist.github.com/rbf/5983134/raw/create_github_labels.sh)

In both cases, locally or remotely, you can directly pass the required parameters (i.e. repo_owner repo_name and access_token) when launching the script, to avoid the interactive mode:

./create_github_labels.sh octocat Spoon-Knife 1234abcd
bash <(curl -sSL https://gist.github.com/rbf/5983134/raw/create_github_labels.sh) octocat Spoon-Knife 1234abcd

Logging

The script creates a local file named github_labels_creation.log with all curl REST calls and its responses. Besides allowing to debug an unexpected behaviour, the log file makes it easy to gather all well formatted curl REST calls, including a valid GitHub token, to make your own customised script.

Labels

After several projects, we are converging to the following labels for our repos. Sticking to this same label structure (including colors) accros repos when possible, makes it easier to catch the state of a project at glance when switching contexts.

This suggested set of labels deployed by default by the script has 5 groups of labels:

Labels for priorities

  • on light yellow (#FFF3C3), i.e. low priority
  • ★★ on yellow (#FFE26B), i.e. medium priority
  • ★★★ on dark yellow (#FBCA04), i.e. high priority

Labels for estimates

We tend to use estimates as half-day units (i.e. 2 means one day of work). This labels are all on black background (precisely #444444).

  • 0
  • 0.5
  • 1
  • 2
  • 4
  • 10

Labels for task Status

These statuses help us to manage our task dashboard in an agile manner. Then, playing with tabs in your browser, you can replicate e.g. a scrum dashboard. Or even better, using them with waffle.io.

  • S00 Backlog, on color #EEEEEE
  • S02 Top of Backlog, on color #FAD8C7
  • S05 Ready to work, on color #EC9265
  • S10 Working, on color #EB6420
  • S20 Testing, on color #F39712
  • S30 Ready to deploy, on color #FBCA04
  • S40 Ready to validate, on color #D7E102
  • S50 Validated, on color #009800
  • S110 Rejected, on color #444444
  • S120 Blocked, on color #E10C02

Labels for task Type

  • T0 Epic, on color #003300
  • T1 User story, on color #061B71
  • T2 Task, on color #0B02E1
  • T3 Enhancement, on color #480CB3
  • T4 Discussion, on color #841685
  • T5 Bug, on color #FC2929

Labels for other tags

Following two labels are left from the default set included by GitHub when creating a new repository:

  • duplicate, on color #CCCCCC
  • question, on color #CB317C

Access tokens

The script handles the creation of a new GitHub access token, if none available, and proposes to revoke it at the end of the script if the user wants. This has been done also with the aim of being an example of how to create, use and revoke (delete) access tokens.

Note: The access tokens created with this script have the scope repo, which is necessary to create labels. The "Personal API Access Tokens" you create from your GitHub settings page, have all scopes (i.e. they are like passwords), so they can also be used with this script. If you do so, you don't need to type your GitHub password when using this script.

Change log

  • [fix] README typos.
  • [new] Add documentation in the script itself

v2.0 (12may2016)

  • [new] Add milestones management.

v1.1-SNAPSHOT (12feb2015)

  • [change] Make states numbers x 10 to allow easily custom states in-between.
  • [new] Add state "S20 Testing".
  • [new] Add state "S05 Ready to work".
  • [fix] Add epic label and fix token retrieval.

v1.0 (05aug2013)

  • Initial version.
#!/bin/bash
# The MIT License (MIT)
#
# Copyright (c) 2013 https://gist.github.com/rbf
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
### File info:
CGL_VERSION="v2.0"
CGL_SOURCE="https://raw.github.com/gist/5983134/create_github_labels.sh"
CGL_DOC="https://gist.github.com/rbf/5983134"
###
# # Requirements
#
# Create a "GitHub access token" with "repo" scope at https://github.com/settings/tokens.
# You can use this token either when 1) asked in the interactive mode, 2) as the last
# argument in the direct mode or 3) stored as an ENV variable named 'GITHUB_TOKEN'. In the
# latter case you might want to add this variable in your ~/.bashrc or similar, e.g.:
#
# echo 'export GITHUB_TOKEN=your_token_here' >> ~/.bashrc
#
#
# # Labels and Milestones
#
# The first part of this script defines the necessary functions to communicate with
# the GitHub API (https://developer.github.com/v3/) to manage labels and milestones.
#
# At the bottom of this script, there are the some examples of milestones and labels.
# Comment or uncomment the ones that you'd like to use, and/or create new ones before
# running the script.
#
#
# # Usage
#
# Once the token is available and the wanted labels and milestones are uncommented (see
# above), you can use this script as follows:
#
# ./create_github_labels.sh [repo_owner [repo_name [access_token]]]
#
# The script will ask for the missing parameters interactively. Note that 'repo_owner'
# is the GitHub username of the _owner_ of the repo (which might be a plain user or an
# organization), not your GitHub username (although they will be the same for a repo of
# yours).
#
#
# # Batch usage
#
# To apply this script to several repos at the same time, take a look at the companion
# script 'batch_create_github_labels_and_milestones.sh'.
#
#
# # Logs
#
# This script logs it's activity to a file named 'github_labels_creation.log' in the same
# directory as the script file itself. It contains the list of all 'curl' requests done with
# the corresponding responses. Please note that they include your 'GITHUB_TOKEN' in plain
# text, should that be an issue for you.
#
#
# API_DOC: https://developer.github.com/v3/
# Log functions
LOG_PREFIX_DEBUG="[DEBUG]"
LOG_PREFIX_INFO="[INFO] "
LOG_PREFIX_WARN="[WARN] "
LOG_PREFIX_ERROR="[ERROR]"
LOG_PREFIX_INPUT=" > "
GITHUB_LABEL_LOG_FILE="github_labels_creation.log"
logblankline(){
echo >> ${GITHUB_LABEL_LOG_FILE}
}
log(){
echo $(date +"[%Y-%m-%d %H:%M:%S]") "$@" >> ${GITHUB_LABEL_LOG_FILE}
}
log-echo(){
echo "$@"
log "$@"
}
# Global functions
github-request(){
GITHUB_REQUEST_LOG_MSG="${1}"
GITHUB_REQUEST="${2}"
GITHUB_REQUEST_NEEDS_LOGIN="${3}"
GITHUB_REQUEST_CALLBACK_ON_OK="${4}"
GITHUB_REQUEST_CALLBACK_ON_FAIL="${5}"
GITHUB_REQUEST_RESPONSE_FILE="github_request_response.log"
log
if [ "${GITHUB_REQUEST_NEEDS_LOGIN}" == "needs_login" ]
then
log-echo "${LOG_PREFIX_INFO} ${GITHUB_REQUEST_LOG_MSG}"
echo -n "${LOG_PREFIX_INPUT} "
GITHUB_REQUEST_SUCCESS_STATUS_OK="${LOG_PREFIX_INFO} OK"
GITHUB_REQUEST_SUCCESS_STATUS_FAILED="${LOG_PREFIX_WARN} FAILED"
else
log "${LOG_PREFIX_INFO} ${GITHUB_REQUEST_LOG_MSG}"
echo -n "${LOG_PREFIX_INFO} ${GITHUB_REQUEST_LOG_MSG}"
GITHUB_REQUEST_SUCCESS_STATUS_OK="OK"
GITHUB_REQUEST_SUCCESS_STATUS_FAILED="\r${LOG_PREFIX_WARN} ${GITHUB_REQUEST_LOG_MSG}FAILED"
fi
log ${LOG_PREFIX_DEBUG} ${GITHUB_REQUEST}
GITHUB_REQUEST_RESPONSE=$(eval ${GITHUB_REQUEST})
log ${LOG_PREFIX_DEBUG} ${GITHUB_REQUEST_RESPONSE}
echo ${GITHUB_REQUEST_RESPONSE} > ${GITHUB_REQUEST_RESPONSE_FILE}
if [ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Bad credentials")" != "" ] || \
[ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Max number of login attempt exceeded")" != "" ]
then
echo -e "${GITHUB_REQUEST_SUCCESS_STATUS_FAILED}"
log-echo "${LOG_PREFIX_ERROR} Bad credentials"
log "${LOG_PREFIX_ERROR} ${GITHUB_REQUEST_LOG_MSG} FAILED"
eval "${GITHUB_REQUEST_CALLBACK_ON_FAIL}"
elif [ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Validation Failed")" != "" ] || \
[ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Body should be a JSON Hash")" != "" ] || \
[ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Not Found")" != "" ]
then
log "${LOG_PREFIX_WARN} ${GITHUB_REQUEST_LOG_MSG} FAILED"
echo -e "${GITHUB_REQUEST_SUCCESS_STATUS_FAILED}"
eval "${GITHUB_REQUEST_CALLBACK_ON_FAIL}"
else
log "${LOG_PREFIX_INFO} ${GITHUB_REQUEST_LOG_MSG} OK"
echo "${GITHUB_REQUEST_SUCCESS_STATUS_OK}"
eval "${GITHUB_REQUEST_CALLBACK_ON_OK}"
fi
}
deletecreatedtoken(){
if [ "${GITHUB_TOKEN_ID}" != "" ]
then
echo "${LOG_PREFIX_INFO} "
echo "${LOG_PREFIX_INFO} You will find the created access token ${GITHUB_TOKEN} in the GitHub web"
echo "${LOG_PREFIX_INFO} interface (https://github.com/settings/applications), where you can revoke it at any point. Alternatively, "
echo "${LOG_PREFIX_INFO} you can also do in now (You will have to enter your GitHub password again for that)."
read -p "${LOG_PREFIX_INPUT} Delete created token now? [y or n]: " answer
if [ "${answer}" == "y" ]
then
github-request \
"Deleting token \"${GITHUB_TOKEN}\"... " \
"curl -sS -X DELETE -u ${GITHUB_USER} https://api.github.com/authorizations/${GITHUB_TOKEN_ID}" \
"needs_login"
fi
fi
}
terminate(){
deletecreatedtoken
echo "${LOG_PREFIX_INFO} For detailed info look at file \"${GITHUB_LABEL_LOG_FILE}\""
logblankline
exit ${1}
}
createlabel() {
# The color is the first arg to make a bunch of calls to this function more readable,
# since color arg will have always the same width, but the name of the label won't.
github-request \
"Creating label \"${2}\"... " \
"curl -sS -d '{\"name\":\"${2}\",\"color\":\"${1}\"}' https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/labels?access_token=${GITHUB_TOKEN}"
}
deletelabel() {
github-request \
"Deleting label \"${1}\"... " \
"curl -sS -X DELETE https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/labels/${1}?access_token=${GITHUB_TOKEN}"
}
createmilestone() {
# Param 1: Milestone title (required)
# Param 2: Description
# Param 3: Date in format '2012-10-09T23:39:01Z'
github-request \
"Creating milestone \"${1}\"... " \
"curl -sS -d '{\"title\":\"${1}\",\"state\":\"open\",\"description\":\"${2}\",\"due_on\":\"${3}\"}' https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/milestones?access_token=${GITHUB_TOKEN}"
}
# DOC: https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository
# NOTE: Does not seem to work for private repos (???)
__set_milestone_url_env_var() {
# Param 1: Milestone title (required)
unset GITHUB_MILESTONE_URL
github-request \
"Getting url for milestone \"${1}\"... " \
"curl -sS https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/milestones?state=all&access_token=${GITHUB_TOKEN}" \
"no_needs_login" \
":" \
"terminate 1"
GITHUB_MILESTONE_URL="$(python -c "import json; milestones = json.loads(open('"${GITHUB_REQUEST_RESPONSE_FILE}"').read()); urls = [ milestone['url'] for milestone in milestones if milestone['title'] == '"${1}"' ]; url = urls[0] if urls else ''; print url; ")"
[ -z "${GITHUB_MILESTONE_URL}" ] && log-echo "Milestone '${1}' cannot be found in repo '${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}'"
}
deletemilestone() {
# Param 1: Milestone title (required)
__set_milestone_url_env_var "${1}"
[ -z "${GITHUB_MILESTONE_URL}" ] && log-echo "Milestone '${1}' was not deleted from repo '${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}'" && return
github-request \
"Deleting milestone \"${1}\"... " \
"curl -sS -X DELETE "${GITHUB_MILESTONE_URL}"?access_token=${GITHUB_TOKEN}"
}
updatemilestone() {
# Param 1: Milestone current title (required)
# Param 2: New milestone title (required)
# Param 3: New description (required)
# Param 4: New date in format '2012-10-09T23:39:01Z' (required)
__set_milestone_url_env_var "${1}"
[ -z "${GITHUB_MILESTONE_URL}" ] && log-echo "Milestone '${1}' was not updated in repo '${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}'" && return
github-request \
"Updating milestone \"${1}\"... " \
"curl -sS -X PATCH "${GITHUB_MILESTONE_URL}"?access_token=${GITHUB_TOKEN} -d '{\"title\":\"${2}\",\"description\":\"${3}\",\"due_on\":\"${4}\"}'"
}
# Gather the three needed parameters: repo_owner, repo_name and access_token
PERFORM_TOKEN_VALIDATION=false
if [ "${1}" == "--perform-token-validation" ]
then
PERFORM_TOKEN_VALIDATION=true
shift
fi
log-echo "${LOG_PREFIX_INFO} Begining creation of labels in GitHub..."
if [ "${1}" != "" ]
then
GITHUB_REPO_OWNER=${1}
log-echo "${LOG_PREFIX_INFO} GitHub repo owner: ${GITHUB_REPO_OWNER}"
else
read -e -p "${LOG_PREFIX_INPUT} Repo owner (GitHub user or organisation): " GITHUB_REPO_OWNER
log "${LOG_PREFIX_INFO} GitHub repo owner: ${GITHUB_REPO_OWNER}"
fi
if [ "${GITHUB_REPO_OWNER}" == "" ]
then
log-echo "${LOG_PREFIX_ERROR} Blank repo owner. Try again!"
terminate 1
fi
GITHUB_USER=${GITHUB_REPO_OWNER}
if [ "${2}" != "" ]
then
GITHUB_REPO_NAME=${2}
log-echo "${LOG_PREFIX_INFO} GitHub repo name: ${GITHUB_REPO_NAME}"
else
read -e -p "${LOG_PREFIX_INPUT} Repo: " GITHUB_REPO_NAME
log "${LOG_PREFIX_INFO} GitHub repo name: ${GITHUB_REPO_NAME}"
fi
if [ "${GITHUB_REPO_NAME}" == "" ]
then
log-echo "${LOG_PREFIX_ERROR} Blank repo name. Try again!"
terminate 1
fi
if [ "${3}" != "" ]
then
GITHUB_TOKEN=${3}
log-echo "${LOG_PREFIX_INFO} GitHub access token: ${GITHUB_TOKEN}"
fi
if [ "${GITHUB_TOKEN}" == "" ]
then
read -e -p "${LOG_PREFIX_INPUT} Access token (leave blank to create a new one): " GITHUB_TOKEN
fi
token_creation_callback_ok(){
GITHUB_TOKEN=$(echo ${GITHUB_REQUEST_RESPONSE} | sed -e "s/.*\"token\": \"\([a-zA-Z0-9]*\)\".*/\1/")
GITHUB_TOKEN_ID=$(echo ${GITHUB_REQUEST_RESPONSE} | sed -e "s/.*\"id\": \([0-9]*\).*/\1/")
log-echo "${LOG_PREFIX_INFO} New token created successfully: ${GITHUB_TOKEN} - Token id: ${GITHUB_TOKEN_ID}"
}
if [ "${GITHUB_TOKEN}" == "" ]
then
read -e -p "${LOG_PREFIX_INPUT} GitHub user (leave blank to use '${GITHUB_USER}'): " input
GITHUB_USER="${input:-$GITHUB_USER}"
GITHUB_TOKEN_REQUEST_BODY="{\"scopes\":[\"repo\"], \"note\":\"Generated on $(date +"%Y-%m-%d %H:%M:%S %z") by the script at https://gist.github.com/rbf/5983134\"}"
github-request \
"Creating a new access token..." \
"curl -sS -u ${GITHUB_USER} -d '${GITHUB_TOKEN_REQUEST_BODY}' https://api.github.com/authorizations" \
"needs_login" \
token_creation_callback_ok \
"terminate 1"
fi
if "${PERFORM_TOKEN_VALIDATION}"
then
github-request \
"Validating token ${GITHUB_TOKEN}... " \
"curl -sS https://api.github.com/repos/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/labels?access_token=${GITHUB_TOKEN}"
if [ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Bad credentials")" != "" ] || \
[ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Max number of login attempt exceeded")" != "" ]
then
log-echo "${LOG_PREFIX_ERROR} Token ${GITHUB_TOKEN} is not valid."
log-echo "${LOG_PREFIX_WARN} Generate a new token at https://github.com/settings/applications or relaunching this script leaving the token parameter blank."
terminate 1
fi
if [ "$(echo ${GITHUB_REQUEST_RESPONSE} | grep "Not Found")" != "" ]
then
log-echo "${LOG_PREFIX_ERROR} Token ${GITHUB_TOKEN} doesn't seem valid for repository ${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}."
log-echo "${LOG_PREFIX_WARN} Verify that the repository exists at https://github.com/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}."
log-echo "${LOG_PREFIX_WARN} Generate a new token at https://github.com/settings/applications or relaunching this script leaving the token parameter blank."
terminate 1
fi
fi
# Milestones (usage examples)
# createmilestone "v2.25" "Milestone for sprint Mar 04 - Mar 18 with target version '2.25'." "2016-03-18T18:00:00Z"
# updatemilestone "v2.25" "v3.1" "Milestone for sprint Mar 04 - Mar 18 with target version '3.1'." "2016-03-18T18:00:00Z"
# deletemilestone "v3.1"
# Labels for priorities
createlabel "fff3c3" "\u2605" # "★" on light yellow, i.e. low priority
createlabel "ffe26b" "\u2605\u2605" # "★★" on yellow, i.e. medium priority
createlabel "fbca04" "\u2605\u2605\u2605" # "★★★" on dark yellow, i.e. high priority
# Labels for estimates
createlabel "444444" "0"
createlabel "444444" "0.5"
createlabel "444444" "1"
createlabel "444444" "2"
createlabel "444444" "4"
createlabel "444444" "10"
# Labels for task Status
createlabel "eeeeee" "S00 Backlog"
createlabel "fad8c7" "S02 Top of Backlog"
createlabel "ec9265" "S05 Ready to work"
createlabel "eb6420" "S10 Working"
createlabel "f39712" "S20 Testing"
createlabel "fbca04" "S30 Ready to deploy"
createlabel "d7e102" "S40 Ready to validate"
createlabel "009800" "S50 Validated"
createlabel "444444" "S110 Rejected"
createlabel "e10c02" "S120 Blocked"
# Labels for task Type
createlabel "003300" "T0 Epic"
createlabel "061b71" "T1 User story"
createlabel "0b02e1" "T2 Task"
createlabel "480cb3" "T3 Enhancement"
createlabel "841685" "T4 Discussion"
createlabel "fc2929" "T5 Bug"
# Remove unused labels added by default by GitHub on repo creation
deletelabel "bug"
# deletelabel "duplicate"
deletelabel "enhancement"
deletelabel "invalid"
# deletelabel "question"
deletelabel "wontfix"
# deletelabel "help%20wanted"
echo "${LOG_PREFIX_INFO} Done!"
terminate 0
  • Recognize if a label has not been created because it was already present, and don't report FAILED but something like ALREADY EXISTENT

  • Recognize if a label has not been deleted because it was not found, and don't report FAILED but something like NOT FOUND

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment