Skip to content

Instantly share code, notes, and snippets.

@jfgordon2
Last active July 27, 2023 07:02
Show Gist options
  • Save jfgordon2/d83f4f097d7aa62829542a632e1f77a3 to your computer and use it in GitHub Desktop.
Save jfgordon2/d83f4f097d7aa62829542a632e1f77a3 to your computer and use it in GitHub Desktop.
Clone all GitHub repos for an organization
#!/bin/bash
# Clone all GitHub repos for an organization
# Usage: gh-clone.sh [--limit <number>] [--path <path>] [--update] <organization>
# Requires GitHub CLI: https://cli.github.com/manual/installation
# Requires jq: https://stedolan.github.io/jq/download/
function check_requirements() {
# ensure GitHub CLI installed
if ! command -v gh &>/dev/null; then
printf "\e[1m%s\e[0m\n" "GitHub CLI not installed"
echo "See https://cli.github.com/manual/installation"
fi
# ensure jq installed
if ! command -v jq &>/dev/null; then
printf "\e[1m%s\e[0m\n" "jq not installed"
echo "See https://stedolan.github.io/jq/download/"
fi
if ! command -v gh &>/dev/null || ! command -v jq &>/dev/null; then
exit 1
fi
}
function available_organizations() {
check_requirements
organizations=$(gh api user/orgs | jq -r '.[].login')
self=$(gh api user | jq -r '.login')
printf "\e[1m%s\e[0m\n" "Available organizations are:"
echo "$self"
echo "$organizations"
}
function update_git_at_directory() {
printf "\n\e[1m%s\e[0m" "Updating $1 "
# Get main branch name
main_branch=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
# Get current branch name
current_branch=$(git rev-parse --abbrev-ref HEAD)
# Update local checkout
git checkout -q "$main_branch" 2>/dev/null || true
# Pull latest changes and print full error output on new line if any
OUTPUT=$(git pull -q 2>&1)
if [ $? -ne 0 ]; then
printf "\e[31m%s\e[0m\n" "$OUTPUT"
else
printf "\e[34m%s\e[0m" " $main_branch updated"
fi
# restore previously checked out branch
git checkout -q "$current_branch" 2>/dev/null || true
}
function check_if_git_repo_and_update() {
cd "$1" || exit
if [ -d ".git" ]; then
if [ -z "$(git rev-parse --verify HEAD 2>/dev/null)" ]; then
# If empty, delete local checkout
printf "\e[31m%s\e[0m" " empty repo"
rm -rf "$1"
printf " - deleted"
else
update_git_at_directory "$1"
fi
fi
}
function show_help() {
printf "\e[1m%s\e[0m%s\n" "Usage: " "$0 <arguments> <organization>"
echo ""
printf "\e[1m%s\e[0m\n" "Arguments:"
echo " --limit <number> Limit number of repos to clone (default: 1000)"
echo " --help Show this help"
echo " --path Path to clone repos to (default: ~/GitHub)"
echo " --update Update local checkout"
echo ""
echo "Clones all repos for an organization"
echo ""
available_organizations
exit 0
}
# ensure organization name provided in first argument
if [ -z "$1" ]; then
printf "\e[1m%s\e[0m\n" "Organization name required"
echo ""
show_help
fi
# defaults
limit=1000
dir_path="$HOME/GitHub"
update=false
# Parse arguments
while true; do
case "$1" in
--limit)
limit="$2"
shift 2
;;
--path)
dir_path="$2"
shift 2
;;
--update)
update=true
shift
;;
--help)
show_help
;;
*)
break
;;
esac
done
printf "\e[1m%s\e[0m\n" "Cloning repos for $1"
gh repo list "$1" --limit "$limit" | while read -r repo _; do
printf "\nResolving %s..." "$repo"
# Check if repo is archived
archived=$(gh api "repos/$repo" | jq -r '.archived')
repo_path="$dir_path/$repo"
if [ "$archived" = "true" ]; then
IFS='/' read -r -a split_name <<<"$repo"
repo_path="$dir_path/${split_name[0]}/~archived/${split_name[1]}"
fi
# Clone repo if not already cloned
if [ ! -d "$repo_path" ]; then
if [ "$archived" = "true" ] && [ -d "$dir_path/$repo" ]; then
# If repo is archived, but not in archived folder, move it
mv "$dir_path/$repo" "$repo_path"
printf "\e[33m%s\e[0m" " archived"
printf " - moved"
else
gh repo clone "$repo" "$repo_path" -- -q 2>/dev/null || true
printf "\e[32m%s\e[0m" " cloned"
fi
else
printf "\e[32m%s\e[0m" " already exists"
fi
# Update local checkout
if [ "$update" = true ]; then
check_if_git_repo_and_update "$repo_path"
# Advise if repo is archived
if [ "$archived" == "true" ]; then
printf "\e[33m%s\e[0m" " - archived"
fi
fi
done
printf "\n\n\e[1m%s\e[0m\n" "Done!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment