最后活跃于 1 month ago

Получить все исходники, доступные в Gitlab

get_all_src.sh 原始文件
1#!/bin/bash
2set -euo pipefail
3
4# ── Colors ───────────────────────────────────────────────────────────
5RED='\033[0;31m'
6GREEN='\033[0;32m'
7YELLOW='\033[1;33m'
8CYAN='\033[0;36m'
9NC='\033[0m'
10
11# ── Logging ──────────────────────────────────────────────────────────
12log_info() { printf "${CYAN}[INFO]${NC} %s\n" "$*"; }
13log_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
14log_ok() { printf "${GREEN}[ OK ]${NC} %s\n" "$*"; }
15log_error() { printf "${RED}[ERR]${NC} %s\n" "$*" >&2; }
16
17# ── Usage ────────────────────────────────────────────────────────────
18usage() {
19 cat <<EOF
20Usage: $(basename "$0") [OPTIONS]
21
22Clone every project from every group (and subgroups, recursively) on a
23GitLab instance, preserving the path_with_namespace directory structure.
24
25Options:
26 -h, --help Show this help
27 -t, --token-file=FILE File containing the GitLab personal access token
28 -g, --gitlab-url=URL GitLab instance URL (default: https://git.mws-team.ru)
29
30Examples:
31 $(basename "$0") -t ~/.gitlab_token
32 $(basename "$0") --token-file=token.txt --gitlab-url=https://gitlab.com
33EOF
34 exit 1
35}
36
37# ── Dependency check ─────────────────────────────────────────────────
38for cmd in curl jq git; do
39 if ! command -v "$cmd" &>/dev/null; then
40 log_error "Required command not found: ${cmd}"
41 exit 1
42 fi
43done
44
45# ── Parse arguments ──────────────────────────────────────────────────
46GITLAB_URL="https://git.mws-team.ru"
47TOKEN_FILE=""
48
49translated=()
50while (( $# )); do
51 case "$1" in
52 --help) translated+=("-h"); shift ;;
53 --token-file=*) translated+=("-t" "${1#*=}"); shift ;;
54 --gitlab-url=*) translated+=("-g" "${1#*=}"); shift ;;
55 --) translated+=("--"); shift; translated+=("$@"); break ;;
56 *) translated+=("$1"); shift ;;
57 esac
58done
59set -- "${translated[@]}"
60
61while getopts ":ht:g:" opt; do
62 case "$opt" in
63 h) usage ;;
64 t) TOKEN_FILE="$OPTARG" ;;
65 g) GITLAB_URL="$OPTARG" ;;
66 :) log_error "Missing argument for -${OPTARG}"; usage ;;
67 \?) log_error "Invalid option: -${OPTARG}"; usage ;;
68 esac
69done
70shift $((OPTIND - 1))
71
72# ── Validate inputs ─────────────────────────────────────────────────
73[[ -z "$TOKEN_FILE" ]] && { log_error "Missing --token-file"; usage; }
74[[ -z "$GITLAB_URL" ]] && { log_error "Missing --gitlab-url"; usage; }
75[[ ! -f "$TOKEN_FILE" ]] && { log_error "Token file not found: ${TOKEN_FILE}"; exit 1; }
76
77GITLAB_URL="${GITLAB_URL%/}"
78
79TOKEN="$(<"$TOKEN_FILE")"
80readonly TOKEN GITLAB_URL
81
82# ── GitLab API helpers ───────────────────────────────────────────────
83
84# Authenticated GET request; returns JSON body.
85gitlab_get() {
86 curl --silent --fail --header "Authorization: Bearer ${TOKEN}" "$1"
87}
88
89# Return total page count for a paginated endpoint.
90gitlab_total_pages() {
91 local headers
92 headers="$(curl -X HEAD --silent -w '%{header_json}' \
93 --header "Authorization: Bearer ${TOKEN}" \
94 -o /dev/null "$1" 2>/dev/null)" || true
95
96 local pages
97 pages="$(echo "$headers" | jq -r '."x-total-pages"[0] // "1"')"
98 [[ -z "$pages" || "$pages" == "null" ]] && pages=1
99 echo "$pages"
100}
101
102# Iterate all pages of an endpoint, call a callback with each extracted value.
103# Usage: gitlab_paginate "URL?per_page=100" '.[].id' callback_fn
104gitlab_paginate() {
105 local base_url="$1"
106 local jq_filter="$2"
107 local callback="$3"
108
109 local total_pages
110 total_pages="$(gitlab_total_pages "$base_url")"
111
112 local page
113 for (( page = 1; page <= total_pages; page++ )); do
114 local response
115 response="$(gitlab_get "${base_url}&page=${page}")" || {
116 log_warn "API request failed: ${base_url}&page=${page}"
117 continue
118 }
119
120 local item
121 while IFS= read -r item; do
122 [[ -z "$item" ]] && continue
123 "$callback" "$item"
124 done < <(echo "$response" | jq -r "$jq_filter")
125 done
126}
127
128# ── Clone a single project ──────────────────────────────────────────
129CLONE_COUNT=0
130SKIP_COUNT=0
131
132clone_project() {
133 local project_id="$1"
134
135 local project_json
136 project_json="$(gitlab_get "${GITLAB_URL}/api/v4/projects/${project_id}")" || {
137 log_warn "Failed to fetch project ${project_id}"
138 return
139 }
140
141 local path_ns ssh_url
142 path_ns="$(echo "$project_json" | jq -r '.path_with_namespace')"
143 ssh_url="$(echo "$project_json" | jq -r '.ssh_url_to_repo')"
144
145 if [[ -z "$path_ns" || "$path_ns" == "null" ]]; then
146 log_warn "Could not determine path for project ${project_id} — skipping"
147 return
148 fi
149
150 if [[ -d "$path_ns" ]]; then
151 log_info "Already cloned: ${path_ns}"
152 (( SKIP_COUNT++ )) || true
153 return
154 fi
155
156 log_info "Cloning: ${path_ns}"
157 mkdir -p "$(dirname "$path_ns")"
158 if git clone --quiet "$ssh_url" "$path_ns"; then
159 log_ok "Cloned: ${path_ns}"
160 (( CLONE_COUNT++ )) || true
161 else
162 log_warn "git clone failed for ${path_ns}"
163 fi
164}
165
166# ── Clone all projects in a group ────────────────────────────────────
167clone_group_projects() {
168 local group_id="$1"
169 gitlab_paginate \
170 "${GITLAB_URL}/api/v4/groups/${group_id}/projects?per_page=100&archived=no" \
171 '.[].id' clone_project
172}
173
174# ── Process a group and all its subgroups recursively ────────────────
175process_group() {
176 local group_id="$1"
177
178 clone_group_projects "$group_id"
179
180 # Collect subgroup IDs, then recurse
181 local subgroup_ids=()
182 while IFS= read -r sid; do
183 [[ -z "$sid" ]] && continue
184 subgroup_ids+=("$sid")
185 done < <(
186 local total
187 total="$(gitlab_total_pages "${GITLAB_URL}/api/v4/groups/${group_id}/subgroups?per_page=100")"
188 for (( p = 1; p <= total; p++ )); do
189 gitlab_get "${GITLAB_URL}/api/v4/groups/${group_id}/subgroups?per_page=100&page=${p}" \
190 | jq -r '.[].id'
191 done
192 )
193
194 local sid
195 for sid in "${subgroup_ids[@]}"; do
196 log_info "Entering subgroup ${sid}"
197 process_group "$sid"
198 done
199}
200
201# ── Main ─────────────────────────────────────────────────────────────
202main() {
203 log_info "GitLab URL: ${GITLAB_URL}"
204 log_info "Token file: ${TOKEN_FILE}"
205 echo
206
207 # Iterate over every top-level group visible to the token
208 gitlab_paginate \
209 "${GITLAB_URL}/api/v4/groups?per_page=100&top_level_only=true" \
210 '.[].id' process_group
211
212 echo
213 log_ok "Done — cloned ${CLONE_COUNT} repo(s), skipped ${SKIP_COUNT} already present"
214}
215
216main