Last active 1 month ago

Получить исходники из конкретной группы

get_group_src.sh Raw
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 all projects from a GitLab group (including subgroups) preserving
23the 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 -i, --group-id=ID GitLab group ID to clone
30
31Examples:
32 $(basename "$0") -t ~/.gitlab_token -i 42
33 $(basename "$0") --token-file=token.txt --gitlab-url=https://gitlab.com --group-id=123
34EOF
35 exit 1
36}
37
38# ── Dependency check ─────────────────────────────────────────────────
39for cmd in curl jq git; do
40 if ! command -v "$cmd" &>/dev/null; then
41 log_error "Required command not found: ${cmd}"
42 exit 1
43 fi
44done
45
46# ── Parse arguments ──────────────────────────────────────────────────
47GITLAB_URL="https://git.mws-team.ru"
48TOKEN_FILE=""
49GROUP=""
50
51translated=()
52while (( $# )); do
53 case "$1" in
54 --help) translated+=("-h"); shift ;;
55 --token-file=*) translated+=("-t" "${1#*=}"); shift ;;
56 --gitlab-url=*) translated+=("-g" "${1#*=}"); shift ;;
57 --group-id=*) translated+=("-i" "${1#*=}"); shift ;;
58 --) translated+=("--"); shift; translated+=("$@"); break ;;
59 *) translated+=("$1"); shift ;;
60 esac
61done
62set -- "${translated[@]}"
63
64while getopts ":ht:g:i:" opt; do
65 case "$opt" in
66 h) usage ;;
67 t) TOKEN_FILE="$OPTARG" ;;
68 g) GITLAB_URL="$OPTARG" ;;
69 i) GROUP="$OPTARG" ;;
70 :) log_error "Missing argument for -${OPTARG}"; usage ;;
71 \?) log_error "Invalid option: -${OPTARG}"; usage ;;
72 esac
73done
74shift $((OPTIND - 1))
75
76# ── Validate inputs ─────────────────────────────────────────────────
77[[ -z "$TOKEN_FILE" ]] && { log_error "Missing --token-file"; usage; }
78[[ -z "$GITLAB_URL" ]] && { log_error "Missing --gitlab-url"; usage; }
79[[ -z "$GROUP" ]] && { log_error "Missing --group-id"; usage; }
80[[ ! -f "$TOKEN_FILE" ]] && { log_error "Token file not found: ${TOKEN_FILE}"; exit 1; }
81
82# Strip trailing slash from URL
83GITLAB_URL="${GITLAB_URL%/}"
84
85TOKEN="$(<"$TOKEN_FILE")"
86readonly TOKEN GITLAB_URL GROUP
87
88# ── GitLab API helpers ───────────────────────────────────────────────
89
90# Perform an authenticated GET request and return the JSON body.
91gitlab_get() {
92 local url="$1"
93 curl --silent --fail --header "Authorization: Bearer ${TOKEN}" "$url"
94}
95
96# Return total number of pages for a paginated endpoint.
97# Usage: gitlab_total_pages "https://gitlab.com/api/v4/..."
98gitlab_total_pages() {
99 local url="$1"
100 local headers
101 headers="$(curl -X HEAD --silent -w '%{header_json}' \
102 --header "Authorization: Bearer ${TOKEN}" \
103 -o /dev/null "$url" 2>/dev/null)" || true
104
105 local pages
106 pages="$(echo "$headers" | jq -r '."x-total-pages"[0] // "1"')"
107 # Fallback to 1 if empty or null
108 [[ -z "$pages" || "$pages" == "null" ]] && pages=1
109 echo "$pages"
110}
111
112# Iterate over all pages of a paginated endpoint, streaming JSON arrays.
113# Calls a callback function with each item's specified jq field.
114# Usage: gitlab_paginate "/api/v4/groups/42/projects?per_page=100" ".[].id" callback_fn
115gitlab_paginate() {
116 local endpoint="$1"
117 local jq_filter="$2"
118 local callback="$3"
119 local extra_params="${4:-}"
120
121 local base_url="${GITLAB_URL}${endpoint}"
122 [[ -n "$extra_params" ]] && base_url="${base_url}&${extra_params}"
123
124 local total_pages
125 total_pages="$(gitlab_total_pages "$base_url")"
126
127 local page
128 for (( page = 1; page <= total_pages; page++ )); do
129 local response
130 response="$(gitlab_get "${base_url}&page=${page}")" || {
131 log_warn "API request failed: ${base_url}&page=${page}"
132 continue
133 }
134
135 local item
136 while IFS= read -r item; do
137 [[ -z "$item" ]] && continue
138 "$callback" "$item"
139 done < <(echo "$response" | jq -r "$jq_filter")
140 done
141}
142
143# ── Clone a single project ──────────────────────────────────────────
144clone_project() {
145 local project_id="$1"
146
147 local project_json
148 project_json="$(gitlab_get "${GITLAB_URL}/api/v4/projects/${project_id}")" || {
149 log_warn "Failed to fetch project ${project_id}"
150 return
151 }
152
153 local path_ns ssh_url
154 path_ns="$(echo "$project_json" | jq -r '.path_with_namespace')"
155 ssh_url="$(echo "$project_json" | jq -r '.ssh_url_to_repo')"
156
157 if [[ -z "$path_ns" || "$path_ns" == "null" ]]; then
158 log_warn "Could not determine path for project ${project_id} — skipping"
159 return
160 fi
161
162 if [[ -d "$path_ns" ]]; then
163 log_info "Already cloned: ${path_ns}"
164 return
165 fi
166
167 log_info "Cloning: ${path_ns}"
168 local parent_dir
169 parent_dir="$(dirname "$path_ns")"
170 mkdir -p "$parent_dir"
171 git clone "$ssh_url" "$path_ns" --quiet || log_warn "git clone failed for ${path_ns}"
172}
173
174# ── Clone all projects in a group ────────────────────────────────────
175clone_group_projects() {
176 local group_id="$1"
177 log_info "Cloning projects in group ${group_id}"
178 gitlab_paginate "/api/v4/groups/${group_id}/projects?per_page=100" \
179 '.[].id' clone_project "archived=no"
180}
181
182# ── Process a group and all its subgroups recursively ────────────────
183process_group() {
184 local group_id="$1"
185
186 # Clone direct projects
187 clone_group_projects "$group_id"
188
189 # Recurse into subgroups
190 local subgroup_ids=()
191 while IFS= read -r sid; do
192 [[ -z "$sid" ]] && continue
193 subgroup_ids+=("$sid")
194 done < <(
195 local total
196 total="$(gitlab_total_pages "${GITLAB_URL}/api/v4/groups/${group_id}/subgroups?per_page=100")"
197 for (( p = 1; p <= total; p++ )); do
198 gitlab_get "${GITLAB_URL}/api/v4/groups/${group_id}/subgroups?per_page=100&page=${p}" \
199 | jq -r '.[].id'
200 done
201 )
202
203 for sid in "${subgroup_ids[@]}"; do
204 log_info "Entering subgroup ${sid}"
205 process_group "$sid"
206 done
207}
208
209# ── Main ─────────────────────────────────────────────────────────────
210main() {
211 log_info "GitLab URL: ${GITLAB_URL}"
212 log_info "Group ID: ${GROUP}"
213 log_info "Token file: ${TOKEN_FILE}"
214 echo
215
216 process_group "$GROUP"
217
218 echo
219 log_ok "Done"
220}
221
222main