Thank you for reading this post, don't forget to subscribe!
задача - переносим гитлаб из облака в self-hosted, допустим сделали миграцию а потом нужно склонировать ВСЕ изменения которые успели сделать разработчики в облаке и ЗАТЕРЕТЬ ВСЕ изменения в self-hosted гитлабе.
используем следующий скрипт
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
#!/bin/bash # Требования: curl, jq, git # === Настройки исходного (облачного) GitLab === GITLAB_CLOUD_TOKEN="glpat-RnTc-AexVubiVn9q" GITLAB_CLOUD_DOMAIN="gitlab.com" GITLAB_CLOUD_GROUP="test-tech" # === Настройки целевого (self-hosted) GitLab === GITLAB_SELF_HOSTED_TOKEN="glpat-eddKV1_QnYFSSkgu" GITLAB_SELF_HOSTED_DOMAIN="gitlab.infra.test.tech" GITLAB_SELF_HOSTED_GROUP="test-tech" # Рабочая директория для временных данных WORKDIR="./migration_tmp" mkdir -p "$WORKDIR" START_DIR=$(pwd) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } urlencode() { local string="${1}" local strlen=${#string} local encoded="" local pos c o for (( pos=0 ; pos<strlen ; pos++ )); do c=${string:$pos:1} case "$c" in [-_.~a-zA-Z0-9]) o="$c" ;; *) printf -v o '%%%02X' "'$c" ;; esac encoded+="${o}" done echo "$encoded" } # === Шаг 1: Получаем список всех проектов (с пагинацией) === log "Облачный GitLab: Получаю список проектов группы '$GITLAB_CLOUD_GROUP' (включая подгруппы) с $GITLAB_CLOUD_DOMAIN…" ENCODED_GROUP=$(urlencode "$GITLAB_CLOUD_GROUP") page=1 projects=() while : ; do log "Загрузка страницы проектов номер $page" projects_json=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_CLOUD_TOKEN" \ "https://$GITLAB_CLOUD_DOMAIN/api/v4/groups/$ENCODED_GROUP/projects?include_subgroups=true&per_page=100&page=$page") if [[ "$projects_json" == "[]" ]]; then log "Больше нет проектов на странице $page. Завершаю получение списка." break fi while IFS=$'\t' read -r proj_path_ns proj_http_url; do projects+=("$proj_path_ns|||$proj_http_url") done < <(echo "$projects_json" | jq -r '.[] | [.path_with_namespace, .http_url_to_repo] | @tsv') log "Страница $page: Получено проектов: $(echo "$projects_json" | jq '. | length')" ((page++)) done log "Всего найдено проектов: ${#projects[@]}" log "============================================" # === Шаг 2: Обрабатываем каждый проект === for entry in "${projects[@]}"; do SOURCE_PATH_NS="${entry%%|||*}" SOURCE_REPO_URL="${entry##*|||}" RELATIVE_PATH=${SOURCE_PATH_NS#"$GITLAB_CLOUD_GROUP/"} PROJECT_NAME=$(basename "$SOURCE_PATH_NS") log "Облачный GitLab: Начало обработки проекта: $SOURCE_PATH_NS" # Формируем URL целевого репозитория TARGET_REPO_URL="https://oauth2:$GITLAB_SELF_HOSTED_TOKEN@$GITLAB_SELF_HOSTED_DOMAIN/$GITLAB_SELF_HOSTED_GROUP" if [[ "$RELATIVE_PATH" != "$PROJECT_NAME" ]]; then TARGET_REPO_URL="$TARGET_REPO_URL/$(dirname "$RELATIVE_PATH")" fi TARGET_REPO_URL="$TARGET_REPO_URL/$PROJECT_NAME.git" SOURCE_REPO_URL_WITH_TOKEN=$(echo "$SOURCE_REPO_URL" | sed "s|https://|https://oauth2:$GITLAB_CLOUD_TOKEN@|") TARGET_PROJECT_PATH="$GITLAB_SELF_HOSTED_GROUP/$RELATIVE_PATH" ENCODED_TARGET_PROJECT=$(urlencode "$TARGET_PROJECT_PATH") TARGET_PROJECT_JSON=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$ENCODED_TARGET_PROJECT") PROJECT_ID=$(echo "$TARGET_PROJECT_JSON" | jq -r '.id') if [[ -z "$PROJECT_ID" || "$PROJECT_ID" == "null" ]]; then log "Self-hosted GitLab: Ошибка - не удалось получить информацию о проекте $TARGET_PROJECT_PATH. Пропускаю проект." log "============================================" continue fi # Снимаем защиту с веток PROTECTED_BRANCHES=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$PROJECT_ID/protected_branches" | jq -r '.[].name') declare -a PROTECTED_ARRAY=() while IFS= read -r branch; do PROTECTED_ARRAY+=("$branch") done <<< "$PROTECTED_BRANCHES" for branch in "${PROTECTED_ARRAY[@]}"; do if [[ -n "$branch" ]]; then log "Self-hosted GitLab: Снимаю защиту с ветки: $branch" curl -s --request DELETE --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$PROJECT_ID/protected_branches/$(urlencode "$branch")" > /dev/null fi done # Клонируем self-hosted репозиторий, чтобы сохранить локальные изменения PROJECT_DIR="$WORKDIR/$PROJECT_NAME" rm -rf "$PROJECT_DIR" log "Self-hosted GitLab: Клонирую репозиторий $TARGET_REPO_URL…" git clone "$TARGET_REPO_URL" "$PROJECT_DIR" cd "$PROJECT_DIR" || continue # Указываем пользователя для git git config user.name "Migration Bot" git config user.email "migration-bot@test.tech" # Добавляем облачный репозиторий как remote 'cloud' log "Облачный GitLab: Добавляю репозиторий как remote 'cloud'…" log "g it remote add cloud $SOURCE_REPO_URL_WITH_TOKEN" git remote add cloud "${SOURCE_REPO_URL_WITH_TOKEN}" log "Облачный GitLab: Получаю изменения из remote 'cloud'…" git fetch cloud # Создаём локальные копии всех веток из облачного GitLab, которых нет локально for branch in $(git branch -r | grep 'cloud/' | sed 's#cloud/##'); do if ! git show-ref --verify --quiet "refs/heads/$branch"; then log "Создаю ветку $branch из облачного remote" git checkout -b "$branch" "cloud/$branch" fi done # Перебираем все локальные ветки и выполняем merge for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do log "Обновляю ветку $branch…" git checkout "$branch" if git show-ref --verify --quiet "refs/remotes/cloud/$branch"; then git merge "cloud/$branch" --no-edit else log "Облачный репозиторий не содержит ветку $branch, пропускаю merge." fi done # Удаляем remote 'cloud' перед push для надежности log "Удаляю remote 'cloud' перед push…" git remote remove cloud log "Self-hosted GitLab: Пушу обновлённые ветки…" git push origin --all git push origin --tags cd "$START_DIR" rm -rf "$PROJECT_DIR" # Восстанавливаем защиту веток for branch in "${PROTECTED_ARRAY[@]}"; do if [[ -n "$branch" ]]; then log "Self-hosted GitLab: Восстанавливаю защиту для ветки: $branch" curl -s --request POST --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ --data "name=$branch" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$PROJECT_ID/protected_branches" > /dev/null fi done log "Обработка проекта $SOURCE_PATH_NS завершена." log "============================================" done log "Все проекты обработаны." |
нам нужно сгенерировать токены в обалчном и в нашем гитлабе, указать домены и указать группу, проекты внутри группы будут обрабатываться рекурсивно
# === Настройки исходного (облачного) GitLab ===
GITLAB_CLOUD_TOKEN="glpat-BYpCKtMTE2P8PKn"
GITLAB_CLOUD_DOMAIN="gitlab.com"
# Укажите имя группы (без URL-кодирования), от которой начинается поиск проектов
GITLAB_CLOUD_GROUP="test-tech"
# === Настройки целевого (self-hosted) GitLab ===
GITLAB_SELF_HOSTED_TOKEN="glpat-gxqqBY3ArKf-z91A8FZB"
GITLAB_SELF_HOSTED_DOMAIN="gitlab.infra.test.tech"
# Укажите имя группы, в которой уже созданы проекты. Структура подгрупп должна совпадать с исходной.
GITLAB_SELF_HOSTED_GROUP="test-tech"
===========================================
Дописать ТОЛЬКО изменения из облака а локальные изменения НЕ затирать
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
#!/bin/bash # Требования: curl, jq, git # === Настройки исходного (облачного) GitLab === GITLAB_CLOUD_TOKEN="glpat-Rn5Tc-AexVubiVn9q" GITLAB_CLOUD_DOMAIN="gitlab.com" GITLAB_CLOUD_GROUP="test-tech" # === Настройки целевого (self-hosted) GitLab === GITLAB_SELF_HOSTED_TOKEN="glpat-edgyLUdKV1_QnYFSSkgu" GITLAB_SELF_HOSTED_DOMAIN="gitlab.infra.test.tech" GITLAB_SELF_HOSTED_GROUP="test-tech" # Рабочая директория для временных данных WORKDIR="./migration_tmp" mkdir -p "$WORKDIR" START_DIR=$(pwd) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } urlencode() { local string="${1}" local strlen=${#string} local encoded="" local pos c o for (( pos=0 ; pos<strlen ; pos++ )); do c=${string:$pos:1} case "$c" in [-_.~a-zA-Z0-9]) o="$c" ;; *) printf -v o '%%%02X' "'$c" ;; esac encoded+="${o}" done echo "$encoded" } # === Шаг 1: Получаем список всех проектов (с пагинацией) === log "Облачный GitLab: Получаю список проектов группы '$GITLAB_CLOUD_GROUP' (включая подгруппы) с $GITLAB_CLOUD_DOMAIN…" ENCODED_GROUP=$(urlencode "$GITLAB_CLOUD_GROUP") page=1 projects=() while : ; do log "Загрузка страницы проектов номер $page" projects_json=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_CLOUD_TOKEN" \ "https://$GITLAB_CLOUD_DOMAIN/api/v4/groups/$ENCODED_GROUP/projects?include_subgroups=true&per_page=100&page=$page") if [[ "$projects_json" == "[]" ]]; then log "Больше нет проектов на странице $page. Завершаю получение списка." break fi while IFS=$'\t' read -r proj_path_ns proj_http_url; do projects+=("$proj_path_ns|||$proj_http_url") done < <(echo "$projects_json" | jq -r '.[] | [.path_with_namespace, .http_url_to_repo] | @tsv') log "Страница $page: Получено проектов: $(echo "$projects_json" | jq '. | length')" ((page++)) done log "Всего найдено проектов: ${#projects[@]}" log "============================================" # === Шаг 2: Обрабатываем каждый проект === for entry in "${projects[@]}"; do SOURCE_PATH_NS="${entry%%|||*}" SOURCE_REPO_URL="${entry##*|||}" RELATIVE_PATH=${SOURCE_PATH_NS#"$GITLAB_CLOUD_GROUP/"} PROJECT_NAME=$(basename "$SOURCE_PATH_NS") log "Облачный GitLab: Начало обработки проекта: $SOURCE_PATH_NS" # Формируем URL целевого репозитория TARGET_REPO_URL="https://oauth2:$GITLAB_SELF_HOSTED_TOKEN@$GITLAB_SELF_HOSTED_DOMAIN/$GITLAB_SELF_HOSTED_GROUP" if [[ "$RELATIVE_PATH" != "$PROJECT_NAME" ]]; then TARGET_REPO_URL="$TARGET_REPO_URL/$(dirname "$RELATIVE_PATH")" fi TARGET_REPO_URL="$TARGET_REPO_URL/$PROJECT_NAME.git" SOURCE_REPO_URL_WITH_TOKEN=$(echo "$SOURCE_REPO_URL" | sed "s|https://|https://oauth2:$GITLAB_CLOUD_TOKEN@|") TARGET_PROJECT_PATH="$GITLAB_SELF_HOSTED_GROUP/$RELATIVE_PATH" ENCODED_TARGET_PROJECT=$(urlencode "$TARGET_PROJECT_PATH") TARGET_PROJECT_JSON=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$ENCODED_TARGET_PROJECT") PROJECT_ID=$(echo "$TARGET_PROJECT_JSON" | jq -r '.id') if [[ -z "$PROJECT_ID" || "$PROJECT_ID" == "null" ]]; then log "Self-hosted GitLab: Ошибка - не удалось получить информацию о проекте $TARGET_PROJECT_PATH. Пропускаю проект." log "============================================" continue fi # Снимаем защиту с веток PROTECTED_BRANCHES=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$PROJECT_ID/protected_branches" | jq -r '.[].name') declare -a PROTECTED_ARRAY=() while IFS= read -r branch; do PROTECTED_ARRAY+=("$branch") done <<< "$PROTECTED_BRANCHES" for branch in "${PROTECTED_ARRAY[@]}"; do if [[ -n "$branch" ]]; then log "Self-hosted GitLab: Снимаю защиту с ветки: $branch" curl -s --request DELETE --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$PROJECT_ID/protected_branches/$(urlencode "$branch")" > /dev/null fi done # Клонируем self-hosted репозиторий, чтобы сохранить локальные изменения PROJECT_DIR="$WORKDIR/$PROJECT_NAME" rm -rf "$PROJECT_DIR" log "Self-hosted GitLab: Клонирую репозиторий $TARGET_REPO_URL…" git clone "$TARGET_REPO_URL" "$PROJECT_DIR" cd "$PROJECT_DIR" || continue # Указываем пользователя для git git config user.name "Migration Bot" git config user.email "migration-bot@test.tech" # Добавляем облачный репозиторий как remote 'cloud' log "Облачный GitLab: Добавляю репозиторий как remote 'cloud'…" log "g it remote add cloud $SOURCE_REPO_URL_WITH_TOKEN" git remote add cloud "${SOURCE_REPO_URL_WITH_TOKEN}" log "Облачный GitLab: Получаю изменения из remote 'cloud'…" git fetch cloud # Перебираем все локальные ветки и выполняем merge for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do log "Обновляю ветку $branch…" git checkout "$branch" if git show-ref --verify --quiet "refs/remotes/cloud/$branch"; then git merge "cloud/$branch" --no-edit else log "Облачный репозиторий не содержит ветку $branch, пропускаю merge." fi done # Удаляем remote 'cloud' перед push для надежности log "Удаляю remote 'cloud' перед push…" git remote remove cloud log "Self-hosted GitLab: Пушу обновлённые ветки…" git push origin --all git push origin --tags cd "$START_DIR" rm -rf "$PROJECT_DIR" # Восстанавливаем защиту веток for branch in "${PROTECTED_ARRAY[@]}"; do if [[ -n "$branch" ]]; then log "Self-hosted GitLab: Восстанавливаю защиту для ветки: $branch" curl -s --request POST --header "PRIVATE-TOKEN: $GITLAB_SELF_HOSTED_TOKEN" \ --data "name=$branch" \ "https://$GITLAB_SELF_HOSTED_DOMAIN/api/v4/projects/$PROJECT_ID/protected_branches" > /dev/null fi done log "Обработка проекта $SOURCE_PATH_NS завершена." log "============================================" done log "Все проекты обработаны." |