BASH script to clone all git repository in a Group on GitLab, or Organization on GitHub.

Introduction

Starting at a new employer always mean checking out various git repositories.

As the amount of repositories a company has grows, the time needed to clone all of those repositories also grows.

This script automates this task.

In order for this script to work, a personal access token
is needed.

To avoid unexpected behaviour, use Group's ID, rather than its key/name. Informations about Groups can be found in the API at
https://gitlab.example.com/api/v4/groups.

Usage

Call bash gitlab-clone-projects.sh <gitlab-domain> <group-name> <gitlab-token>

For full usage details gitlab-clone-projects --help is available:

Usage: gitlab-clone-projects.sh [-dh] <gitlab-domain> <group-id> <gitlab-token>

Where:

  • <gitlab-domain> is the domain where gitlab lives (for instance: 'gitlab.com')
  • <group-id> is the ID of the group who's repos should be cloned
  • <gitlab-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 a group

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 ./{group-id}/{repo-name}

The git executable can be overridden by setting the GIT environmental variable
before calling this script:
GIT=/usr/local/git-plus gitlab-clone-projects.sh <gitlab-domain> <group-id> <gitlab-token>

github-clone-projects.bash

#!/usr/bin/env bash

# ==============================================================================
# Copyright (C) 2021 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 [-dh] <github-domain> <group-id> <github-token>
##
## Where:
##       - <github-domain> is the domain where github lives (for instance: 'github.com')
##       - <group-id> is the ID of the group who's repos should be cloned
##       - <github-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 a organisation
##
## 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 ./${group-id}/${repo-name}
##
## The git executable can be overridden by setting the GIT environmental variable
## before calling this script:
##
##        GIT=/usr/local/git-plus $0 <github-domain> <group-id> <github-token>
# ==============================================================================

: readonly "${GIT:=git}"

usage() {
    local sScript sUsage

    readonly sScript="$(basename "$0")"
    readonly sUsage="$(grep '^##' <"$0" | cut -c4-)"

    echo -e "${sUsage//\$0/${sScript}}"
}

github-clone-projects() {

    local -a aParameters aRepos
    local g_sGithubDomain g_sGithubToken g_sId
    local bIsUser bDryRun
    local sDirectory sRepo sRepos

    call-api() {
      local -r sSubject="${1?One parameter required: <api-subject>}"
      curl --silent -u "username:${g_sGithubToken}" "https://api.${g_sGithubDomain}/${sSubject}?per_page=100"
    }

    fetch-projects() {
      local iId sSubject

      readonly sSubject="${1?Two parameters required: <subject> <id>}"
      readonly iId="${2?Two parameters required: <subject> <id>}"

      call-api "${sSubject}/${iId}/repos" \
        | grep -E -o '"ssh_url"\s*:\s*"[^"]+"' \
        | cut -d '"' -f4
    }

    fetch-group-projects() {
      local -r iId="${1?One parameters required: <id>}"
      fetch-projects 'orgs' "${iId}"
    }

    fetch-user-projects() {
      local -r iId="${1?One parameters required: <id>}"
      fetch-projects 'users' "${iId}"
    }

    bIsUser=false
    bDryRun=false
    aParameters=()

    for arg in "$@";do
      case $arg in
        -h|--help )
          usage
          exit
        ;;

        -d|--dry-run )
          readonly bDryRun=true
          shift
        ;;

        -u|--user )
          readonly bIsUser=true
          shift
        ;;

        * )
          aParameters+=( "$1" )
          shift
        ;;
      esac
    done
    readonly aParameters

    readonly g_sGithubDomain="${aParameters[0]?Three parameters required: <github-domain> <group-id> <github-token>}"
    readonly g_sId="${aParameters[1]?Three parameters required: <github-domain> <group-id> <github-token>}"
    readonly g_sGithubToken="${aParameters[2]?Three parameters required: <github-domain> <group-id> <github-token>}"

    if [[ "${bIsUser}" = 'true' ]];then
      readonly sRepos=$(fetch-user-projects "${g_sId}")
    else
      readonly sRepos=$(fetch-group-projects "${g_sId}")
    fi

    aRepos=()
    for sRepo in ${sRepos[*]}; do
      aRepos+=("${sRepo}")
    done

    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:]')"
      # Prepend 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

gitlab-clone-projects.bash

#!/usr/bin/env bash

# ==============================================================================
# Copyright (C) 2018, 2020 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 Gitlab Group
# ------------------------------------------------------------------------------
## Usage: $0 [-dh] <gitlab-domain> <group-id> <gitlab-token>
##
## Where:
##       - <gitlab-domain> is the domain where gitlab lives (for instance: 'gitlab.com')
##       - <group-id> is the ID of the group who's repos should be cloned
##       - <gitlab-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 a group
##
## 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 ./${group-id}/${repo-name}
##
## The git executable can be overridden by setting the GIT environmental variable
## before calling this script:
##
##        GIT=/usr/local/git-plus $0 <gitlab-domain> <group-id> <gitlab-token>
# ==============================================================================

: readonly "${GIT:=git}"

usage() {
    local sScript sUsage

    readonly sScript="$(basename "$0")"
    readonly sUsage="$(grep '^##' <"$0" | cut -c4-)"

    echo -e "${sUsage//\$0/${sScript}}"
}

gitlab-clone-projects() {

    local -a aParameters aRepos
    local g_sGitlabDomain g_sGitlabToken g_sId
    local bIsUser bDryRun
    local sDirectory sRepo

    call-api() {
      local -r sSubject="${1?One parameter required: <api-subject>}"
      curl --silent --header "PRIVATE-TOKEN: ${g_sGitlabToken}" "https://${g_sGitlabDomain}/api/v4/${sSubject}?per_page=100"
    }

    fetch-projects() {
      local iId sSubject

      readonly sSubject="${1?Two parameters required: <subject> <id>}"
      readonly iId="${2?Two parameters required: <subject> <id>}"

      call-api "${sSubject}/${iId}/projects" \
        | grep -E -o '"ssh_url_to_repo":"[^"]+"' \
        | cut -d '"' -f4
    }

    fetch-group-projects() {
      local -r iId="${1?One parameters required: <id>}"
      fetch-projects 'groups' "${iId}"
    }

    fetch-user-projects() {
      local -r iId="${1?One parameters required: <id>}"
      fetch-projects 'users' "${iId}"
    }

    bIsUser=false
    bDryRun=false
    aParameters=()

    for arg in "$@";do
      case $arg in
        -h|--help )
          usage
          exit
        ;;

        -d|--dry-run )
          readonly bDryRun=true
          shift
        ;;

        -u|--user )
          readonly bIsUser=true
          shift
        ;;

        * )
          aParameters+=( "$1" )
          shift
        ;;
      esac
    done
    readonly aParameters

    readonly g_sGitlabDomain="${aParameters[0]?Three parameters required: <gitlab-domain> <group-id> <gitlab-token>}"
    readonly g_sId="${aParameters[1]?Three parameters required: <gitlab-domain> <group-id> <gitlab-token>}"
    readonly g_sGitlabToken="${aParameters[2]?Three parameters required: <gitlab-domain> <group-id> <gitlab-token>}"

    if [[ "${bIsUser}" = 'true' ]];then
      readonly sRepos=$(fetch-user-projects "${g_sId}")
    else
      readonly sRepos=$(fetch-group-projects "${g_sId}")
    fi

    aRepos=()
    for sRepo in ${sRepos[*]}; do
      aRepos+=("${sRepo}")
    done

    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:]')"
      # Prepend 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 gitlab-clone-projects
else
  gitlab-clone-projects "${@}"
  exit $?
fi

#EOF

參考

https://bl.ocks.org/Potherca/9dd4306eb27cad26d27874acd4b31376

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子世杀,更是在濱河造成了極大的恐慌礁叔,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)龄糊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來募疮,“玉大人炫惩,你說我怎么就攤上這事“⑴ǎ” “怎么了他嚷?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芭毙。 經(jīng)常有香客問我筋蓖,道長,這世上最難降的妖魔是什么退敦? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任粘咖,我火速辦了婚禮,結(jié)果婚禮上侈百,老公的妹妹穿的比我還像新娘瓮下。我一直安慰自己,他們只是感情好钝域,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布唱捣。 她就那樣靜靜地躺著,像睡著了一般网梢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赂毯,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天战虏,我揣著相機(jī)與錄音,去河邊找鬼党涕。 笑死烦感,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的膛堤。 我是一名探鬼主播手趣,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绿渣?” 一聲冷哼從身側(cè)響起朝群,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎中符,沒想到半個月后姜胖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淀散,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年右莱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片档插。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡慢蜓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郭膛,到底是詐尸還是另有隱情晨抡,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布饲鄙,位于F島的核電站凄诞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏忍级。R本人自食惡果不足惜帆谍,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轴咱。 院中可真熱鬧汛蝙,春花似錦、人聲如沸朴肺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽戈稿。三九已至西土,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鞍盗,已是汗流浹背需了。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留般甲,地道東北人肋乍。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像敷存,于是被迫代替她去往敵國和親墓造。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容