|
#!/usr/bin/env bash |
|
|
|
# ============================================================================== |
|
# Copyright (C) 2021-2023 Potherca |
|
# |
|
# This Source Code Form is subject to the terms of the Mozilla Public |
|
# License, v. 2.0. If a copy of the MPL was not distributed with this |
|
# file, You can obtain one at https://mozilla.org/MPL/2.0/. |
|
# ============================================================================== |
|
# There are a few standards this code tries to adhere to, these are listed below. |
|
# |
|
# - Code follows the BASH style-guide described at: |
|
# http://guides.dealerdirect.io/code-styling/bash/ |
|
# |
|
# - Variables are named using an adaption of Systems Hungarian explained at: |
|
# http://blog.pother.ca/VariableNamingConvention |
|
# |
|
# ============================================================================== |
|
|
|
set -o errexit # Exit script when a command exits with non-zero status. |
|
set -o errtrace # Exit on error inside any functions or sub-shells. |
|
set -o nounset # Exit script on use of an undefined variable. |
|
set -o pipefail # Return exit status of the last command in the pipe that exited with a non-zero exit code |
|
|
|
# ============================================================================== |
|
## Git Clone All Projects in GitHub Organisation |
|
# ------------------------------------------------------------------------------ |
|
## Usage: $0 [-dhu] <domain> <organisation-id> <api-token> |
|
## |
|
## Where: |
|
## - <domain> is the domain where GitHub lives (for instance: 'github.com') |
|
## - <organization-id> is the ID of the organization whose repos should be cloned |
|
## - <api-token> is the API access token to make REST API calls with |
|
## |
|
## Options: |
|
## -d|--dry-run Only list the repositories, without actually cloning them |
|
## -h|--help Print this help dialogue and exit |
|
## -u|--user The given ID is a user, not an organization |
|
## |
|
## The repositories will be cloned into a sub-directory under the path from Where |
|
## this script has been called. The repository will be cloned into ./${organisation-id}/${repo-name} |
|
## |
|
## The git and cUrl executable can be overridden by setting their respective environmental variable |
|
## before calling this script: |
|
## |
|
## CURL=/usr/local/curl GIT=/usr/local/git-plus $0 <domain> <organisation-id> <api-token> |
|
# ============================================================================== |
|
|
|
: readonly "${CURL:=curl}" |
|
: readonly "${GIT:=git}" |
|
|
|
usage() { |
|
local sScript sUsage |
|
|
|
sScript="$(basename "$0")" |
|
readonly sScript |
|
|
|
sUsage="$(grep '^##' < "$0" | cut -c4-)" |
|
readonly sUsage |
|
|
|
echo -e "${sUsage//\$0/${sScript}}" |
|
} |
|
|
|
github-clone-projects() { |
|
call-url() { |
|
local sToken sHeaderResult sPaginationUrl sPrevious sResult sUrl |
|
|
|
readonly sToken="${1?Two parameters required: <api-token> <url> [previous-content]}" |
|
readonly sUrl="${2?Two parameters required: <api-token> <url> [previous-content]}" |
|
readonly sPrevious="${3:-''}" |
|
|
|
sHeaderResult=$("${CURL}" \ |
|
--head \ |
|
--header "Accept: application/vnd.github.v3+json" \ |
|
--header "Authorization: token ${sToken}" \ |
|
--silent \ |
|
"${sUrl}") |
|
|
|
sResult=$("${CURL}" \ |
|
--header "Accept: application/vnd.github.v3+json" \ |
|
--header "Authorization: token ${sToken}" \ |
|
--silent \ |
|
"${sUrl}") |
|
|
|
sPaginationUrl=$( |
|
echo "${sHeaderResult}" \ |
|
| grep -oE '<[^>]+>; rel="next"' \ |
|
| grep -oE 'https?://[^>]+' |
|
) || true |
|
|
|
if [[ "${sPaginationUrl}" == '' ]]; then |
|
printf '%s\n%s' "${sPrevious}" "${sResult}" |
|
else |
|
call-url "${sToken}" "${sPaginationUrl}" "${sResult}" |
|
fi |
|
} |
|
|
|
call-api() { |
|
local sToken sUrl |
|
|
|
readonly sToken="${1?Two parameters required: <api-token> <url>}" |
|
readonly sUrl="${2?Two parameters required: <api-token> <url>}" |
|
|
|
call-url "${sToken}" "${sUrl}" |
|
} |
|
|
|
fetch-projects() { |
|
local iId sDomain sToken sSubject |
|
|
|
readonly sToken="${1?Four parameters required: <api-token> <domain> <subject> <id>}" |
|
readonly sDomain="${2?Four parameters required: <api-token> <domain> <subject> <id>}" |
|
readonly sSubject="${3?Four parameters required: <api-token> <domain> <subject> <id>}" |
|
readonly iId="${4?Four parameters required: <api-token> <domain> <subject> <id>}" |
|
|
|
call-api "${sToken}" "https://api.${sDomain}/${sSubject}/${iId}/repos?per_page=100" \ |
|
| grep -E -o '"ssh_url"\s*:\s*"[^"]+"' \ |
|
| cut -d '"' -f4 |
|
} |
|
|
|
fetch-organisation-projects() { |
|
local iId sDomain sToken |
|
|
|
readonly sToken="${1?Three parameters required: <api-token> <domain> <id>}" |
|
readonly sDomain="${2?Three parameters required: <api-token> <domain> <id>}" |
|
readonly iId="${3?Three parameters required: <api-token> <domain> <id>}" |
|
|
|
fetch-projects "${sToken}" "${sDomain}" 'orgs' "${iId}" |
|
} |
|
|
|
fetch-user-projects() { |
|
local iId sDomain sToken |
|
|
|
readonly sToken="${1?Three parameters required: <api-token> <domain> <id>}" |
|
readonly sDomain="${2?Three parameters required: <api-token> <domain> <id>}" |
|
readonly iId="${3?Three parameters required: <api-token> <domain> <id>}" |
|
|
|
fetch-projects "${sToken}" "${sDomain}" 'users' "${iId}" |
|
} |
|
|
|
local -a aParameters=() aRepos=() |
|
local bDryRun=false bIsUser=false |
|
local sDirectory sDomain sToken sId sRepo sRepos |
|
|
|
for sArg in "$@"; do |
|
case "${sArg}" in |
|
-h | --help) |
|
usage |
|
exit |
|
;; |
|
|
|
-d | --dry-run) |
|
readonly bDryRun=true |
|
shift |
|
;; |
|
|
|
-u | --user) |
|
# @TODO: Is there a way we can detect if this is a user or organisation? |
|
readonly bIsUser=true |
|
shift |
|
;; |
|
|
|
*) |
|
aParameters+=("$1") |
|
shift |
|
;; |
|
esac |
|
done |
|
readonly aParameters |
|
|
|
readonly sDomain="${aParameters[0]?Three parameters required: <domain> <organisation-id> <api-token>}" |
|
readonly sId="${aParameters[1]?Three parameters required: <domain> <organisation-id> <api-token>}" |
|
readonly sToken="${aParameters[2]?Three parameters required: <domain> <organisation-id> <api-token>}" |
|
|
|
if [[ ${bIsUser} == 'true' ]]; then |
|
sRepos="$(fetch-user-projects "${sToken}" "${sDomain}" "${sId}")" |
|
else |
|
sRepos="$(fetch-organisation-projects "${sToken}" "${sDomain}" "${sId}")" |
|
fi |
|
readonly sRepos |
|
|
|
while read -r sRepo; do |
|
aRepos+=("${sRepo}") |
|
done <<< "${sRepos}" |
|
readonly aRepos |
|
|
|
echo ' =====> Found ' ${#aRepos[@]} ' repositories' |
|
|
|
for sRepo in "${aRepos[@]}"; do |
|
# Grab repo name |
|
sDirectory="$(echo "${sRepo}" | grep -o -E ':(.*)\.')" |
|
# Lowercase the name |
|
sDirectory="$(echo "${sDirectory}" | tr '[:upper:]' '[:lower:]')" |
|
# Append the current location |
|
sDirectory="$(realpath --canonicalize-missing --relative-to=./ "${sDirectory:1:-1}")" |
|
|
|
if [[ -d ${sDirectory} ]]; then |
|
echo " -----> Skipping '${sRepo}', directory '${sDirectory}' already exists" |
|
else |
|
echo " -----> Cloning '${sRepo}' into directory '${sDirectory}'" |
|
|
|
if [[ ${bDryRun} != 'true' ]]; then |
|
mkdir -p "${sDirectory}" |
|
"${GIT}" clone --recursive "${sRepo}" "${sDirectory}" \ |
|
|| { |
|
rm -rf "${sDirectory}" |
|
echo -e "\n ! ERROR !\n Could not clone ${sRepo}" |
|
} |
|
echo "" |
|
fi |
|
fi |
|
done |
|
} |
|
|
|
if [[ ${BASH_SOURCE[0]} != "$0" ]]; then |
|
export -f github-clone-projects |
|
else |
|
github-clone-projects "${@}" |
|
exit $? |
|
fi |
|
|
|
#EOF |
Note to self: Use
parallelto speed things up (also available on alpine).