#!/bin/bash # ============================================================ # register-submodules.sh - Регистрация всех вложенных репозиториев как подмодулей # # Версия: 1.0.6 # Автор: Michael BAG # Репозиторий: https://git.p7net.ru/tools/git_man # # Использование: # ./register-submodules.sh [--help] [--version] [--dry-run] [--force] [--ignore=all|dirty|untracked|none] # # Опции: # --help, -h Показать эту справку # --version, -V Показать версию скрипта # --dry-run Только показать, что будет сделано (без изменений) # --force, -f Принудительно перерегистрировать подмодули # --ignore=MODE Установить режим игнорирования для всех подмодулей # all - игнорировать всё (не показывать изменения) # dirty - игнорировать только изменения в файлах # untracked - игнорировать только неотслеживаемые файлы # none - показывать все изменения (по умолчанию) # --ignore-submodule PATH Установить игнорирование для конкретного подмодуля # --verbose, -v Подробный вывод # --quiet, -q Минимальный вывод (только ошибки) # # Описание: # Скрипт находит все вложенные репозитории (папки с .git), # определяет их URL и регистрирует как подмодули в .gitmodules. # Перед регистрацией снимает путь с .gitignore (если он там есть). # Также позволяет настроить игнорирование состояния подмодулей. # ============================================================ set -e # Остановка при ошибке (критичные шаги); ошибки регистрации ловятся явно SCRIPT_VERSION="1.0.6" # ============================================================ # Функция показа справки # ============================================================ show_help() { cat << EOF ============================================================ 📦 register-submodules.sh v${SCRIPT_VERSION} - Регистрация вложенных репозиториев ============================================================ ОПИСАНИЕ: Находит вложенные репозитории (.git), регистрирует их как подмодули. Перед регистрацией сам убирает путь из .gitignore. ИСПОЛЬЗОВАНИЕ: ./register-submodules.sh [ОПЦИИ] ОПЦИИ: --help, -h Показать эту справку --version, -V Показать версию скрипта --dry-run Только показать, что будет сделано (без изменений) --force, -f Принудительно перерегистрировать подмодули (удаляет существующие и создаёт заново) --ignore=MODE Установить режим игнорирования для ВСЕХ подмодулей all - игнорировать всё (полностью скрыть изменения) dirty - игнорировать только изменения в файлах untracked - игнорировать только неотслеживаемые файлы none - показывать все изменения (по умолчанию) --ignore-submodule PATH:MODE Установить режим для конкретного подмодуля Пример: --ignore-submodule tools/cam:dirty --remove-ignore Удалить все настройки игнорирования из .gitmodules --verbose, -v Подробный вывод --quiet, -q Минимальный вывод (только ошибки) ПРИМЕРЫ: # Базовая регистрация ./register-submodules.sh # Регистрация с игнорированием всех изменений в подмодулях ./register-submodules.sh --ignore=all # Регистрация с игнорированием только dirty-изменений ./register-submodules.sh --ignore=dirty # Удалить все настройки игнорирования ./register-submodules.sh --remove-ignore # Установить ignore только для конкретного подмодуля ./register-submodules.sh --ignore-submodule tools/cam:dirty # Просмотр без изменений ./register-submodules.sh --dry-run --ignore=all ============================================================ EOF exit 0 } show_version() { echo "register-submodules.sh ${SCRIPT_VERSION}" exit 0 } # ============================================================ # Инициализация переменных # ============================================================ DRY_RUN=false FORCE=false VERBOSE=false QUIET=false REMOVE_IGNORE=false IGNORE_MODE="" IGNORE_SUBMODULE="" # ============================================================ # Разбор аргументов командной строки # ============================================================ while [[ $# -gt 0 ]]; do case $1 in --help|-h) show_help ;; --version|-V) show_version ;; --dry-run) DRY_RUN=true shift ;; --force|-f) FORCE=true shift ;; --verbose|-v) VERBOSE=true shift ;; --quiet|-q) QUIET=true shift ;; --remove-ignore) REMOVE_IGNORE=true shift ;; --ignore=*) IGNORE_MODE="${1#*=}" shift ;; --ignore-submodule=*) IGNORE_SUBMODULE="${1#*=}" shift ;; *) echo -e "\033[0;31m❌ Неизвестная опция: $1\033[0m" echo " Используйте --help для справки" exit 1 ;; esac done # ============================================================ # Настройка вывода # ============================================================ if [ "$QUIET" = true ]; then exec 3>&1 exec >/dev/null 2>&1 fi # Цвета для вывода if [ "$QUIET" != true ]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' NC='\033[0m' else RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; MAGENTA=''; NC='' fi # Функции логирования log_info() { if [ "$QUIET" != true ]; then echo -e "${CYAN}ℹ️ $1${NC}" fi } log_success() { if [ "$QUIET" != true ]; then echo -e "${GREEN}✅ $1${NC}" fi } log_warning() { if [ "$QUIET" != true ]; then echo -e "${YELLOW}⚠️ $1${NC}" fi } log_error() { echo -e "${RED}❌ $1${NC}" >&2 } log_debug() { if [ "$VERBOSE" = true ] && [ "$QUIET" != true ]; then echo -e "${BLUE}🔍 $1${NC}" fi } log_step() { if [ "$QUIET" != true ]; then echo -e "${MAGENTA}▶ $1${NC}" fi } # ============================================================ # Проверка окружения # ============================================================ log_step "Проверка окружения..." if [ ! -d ".git" ]; then log_error "Текущая папка не является Git-репозиторием" echo " Запустите скрипт из корня основного репозитория" >&2 exit 1 fi if ! command -v git &> /dev/null; then log_error "Git не установлен" exit 1 fi log_success "Окружение проверено" # ============================================================ # Нормализация пути: ./foo/ -> foo # ============================================================ normalize_path() { local path="$1" path="${path#./}" path="${path%/}" printf '%s' "$path" } # ============================================================ # Снять путь из .gitignore (точные правила вида /path/, path/, /path) # ============================================================ unignore_path_from_gitignore() { local path path=$(normalize_path "$1") local gitignore=".gitignore" if [ ! -f "$gitignore" ]; then return 0 fi # Не игнорируется — ничего не делаем if ! git check-ignore -q "$path" 2>/dev/null \ && ! git check-ignore -q "$path/" 2>/dev/null \ && ! git check-ignore -q "./$path" 2>/dev/null; then return 0 fi # Совпадает ли строка .gitignore с путём (с/без / и комментария в конце) line_matches_path() { local raw="$1" local trimmed="${raw%%#*}" # trim spaces trimmed="${trimmed#"${trimmed%%[![:space:]]*}"}" trimmed="${trimmed%"${trimmed##*[![:space:]]}"}" case "$trimmed" in "/$path"|"/$path/"|"$path"|"$path/"|"./$path"|"./$path/") return 0 ;; esac return 1 } if [ "$DRY_RUN" = true ]; then echo " [DRY-RUN] убрать из .gitignore правила для: $path" local lineno=0 while IFS= read -r line || [ -n "$line" ]; do lineno=$((lineno + 1)) if line_matches_path "$line"; then echo " → $lineno:$line" fi done < "$gitignore" return 0 fi local tmp tmp=$(mktemp) local removed=0 while IFS= read -r line || [ -n "$line" ]; do if line_matches_path "$line"; then removed=$((removed + 1)) continue fi printf '%s\n' "$line" done < "$gitignore" > "$tmp" if [ "$removed" -gt 0 ]; then mv "$tmp" "$gitignore" log_success "Убран из .gitignore: $path ($removed правил)" git add -- "$gitignore" 2>/dev/null || true else rm -f "$tmp" # Путь всё ещё игнорируется более широким правилом — добавим исключение if git check-ignore -q "$path" 2>/dev/null || git check-ignore -q "$path/" 2>/dev/null; then log_warning "Широкое правило .gitignore; добавляю исключение: !/$path/" printf '\n# auto-unignore for submodule\n!/%s/\n' "$path" >> "$gitignore" git add -- "$gitignore" 2>/dev/null || true fi fi # Если после правок путь всё ещё ignored — для submodule add нужен --force if git check-ignore -q "$path" 2>/dev/null || git check-ignore -q "$path/" 2>/dev/null; then return 1 fi return 0 } # ============================================================ # Функция установки ignore для подмодуля # ============================================================ set_submodule_ignore() { local path="$1" local mode="$2" if [ "$DRY_RUN" = true ]; then echo " [DRY-RUN] git config --file .gitmodules submodule.$path.ignore $mode" return 0 fi git config --file .gitmodules "submodule.$path.ignore" "$mode" 2>/dev/null return $? } # ============================================================ # Функция удаления ignore для подмодуля # ============================================================ remove_submodule_ignore() { local path="$1" if [ "$DRY_RUN" = true ]; then echo " [DRY-RUN] git config --file .gitmodules --unset submodule.$path.ignore" return 0 fi git config --file .gitmodules --unset "submodule.$path.ignore" 2>/dev/null return $? } # ============================================================ # Регистрация уже существующего вложенного репозитория # ============================================================ register_existing_repo_as_submodule() { local repo="$1" local url="$2" git config -f .gitmodules "submodule.$repo.path" "$repo" git config -f .gitmodules "submodule.$repo.url" "$url" git config "submodule.$repo.url" "$url" git config "submodule.$repo.active" "true" if ! git add -f -- .gitmodules "$repo" 2>/dev/null; then return 1 fi # Перенести .git вложенного репо в .git/modules родителя (если доступно) git submodule absorbgitdirs -- "$repo" 2>/dev/null || true return 0 } # ============================================================ # Функция регистрации подмодуля # ============================================================ register_submodule() { local repo local url="$2" repo=$(normalize_path "$1") local still_ignored=false # Снять путь из .gitignore до регистрации if ! unignore_path_from_gitignore "$repo"; then still_ignored=true log_debug "Путь $repo всё ещё в ignore — будет использован --force" fi if [ "$DRY_RUN" = true ]; then if [ -e "$repo/.git" ]; then echo " [DRY-RUN] зарегистрировать существующий репозиторий как подмодуль: $repo ← $url" else echo " [DRY-RUN] git submodule add --force $url $repo" fi return 0 fi if [ "$FORCE" = true ]; then git config --remove-section "submodule.$repo" 2>/dev/null || true git config -f .gitmodules --remove-section "submodule.$repo" 2>/dev/null || true if [ -d "$repo" ] && [ -z "$(ls -A "$repo" 2>/dev/null)" ]; then rm -rf "$repo" fi fi if [ -f .gitmodules ] && git config -f .gitmodules --get "submodule.$repo.url" >/dev/null 2>&1; then log_debug "Подмодуль $repo уже зарегистрирован" return 2 fi # Каталог уже клонирован — регистрируем без повторного clone if [ -e "$repo/.git" ]; then if register_existing_repo_as_submodule "$repo" "$url"; then return 0 fi return 1 fi # Пустой / отсутствующий путь — обычный submodule add local add_args=() if [ "$still_ignored" = true ] || [ "$FORCE" = true ]; then add_args+=(--force) fi if git submodule add "${add_args[@]}" "$url" "$repo" 2>/dev/null; then return 0 fi return 1 } # ============================================================ # Информация о режиме работы # ============================================================ if [ "$QUIET" != true ]; then echo "" echo "============================================================" echo "📦 Регистрация вложенных репозиториев как подмодулей" echo "============================================================" echo "" if [ "$DRY_RUN" = true ]; then echo -e "${YELLOW}⚠️ РЕЖИМ ПРОСМОТРА (--dry-run) — изменения НЕ будут применены${NC}" echo "" fi if [ "$FORCE" = true ]; then echo -e "${YELLOW}⚠️ РЕЖИМ ПРИНУДИТЕЛЬНОЙ ПЕРЕРЕГИСТРАЦИИ (--force)${NC}" echo "" fi if [ -n "$IGNORE_MODE" ]; then echo -e "${CYAN}🔧 Режим игнорирования для ВСЕХ подмодулей: ${MAGENTA}$IGNORE_MODE${NC}" case "$IGNORE_MODE" in all) echo " → полностью скрывает изменения в подмодулях" ;; dirty) echo " → скрывает только изменения в файлах" ;; untracked) echo " → скрывает только неотслеживаемые файлы" ;; none) echo " → показывает все изменения (по умолчанию)" ;; *) log_warning "Неизвестный режим: $IGNORE_MODE. Используйте: all, dirty, untracked, none" ;; esac echo "" fi if [ -n "$IGNORE_SUBMODULE" ]; then echo -e "${CYAN}🔧 Режим игнорирования для конкретного подмодуля: ${MAGENTA}$IGNORE_SUBMODULE${NC}" echo "" fi if [ "$REMOVE_IGNORE" = true ]; then echo -e "${YELLOW}🔧 Удаление всех настроек игнорирования из .gitmodules${NC}" echo "" fi fi # ============================================================ # Удаление настроек игнорирования (если указано) # ============================================================ if [ "$REMOVE_IGNORE" = true ]; then log_step "Удаление настроек игнорирования..." if [ -f ".gitmodules" ]; then # Найти все секции подмодулей sections=$(git config --file .gitmodules --name-only --get-regexp "submodule\..*\.ignore" 2>/dev/null | cut -d. -f1-2 | sort -u) if [ -n "$sections" ]; then for section in $sections; do if [ "$DRY_RUN" = true ]; then echo " [DRY-RUN] git config --file .gitmodules --unset $section.ignore" else git config --file .gitmodules --unset "$section.ignore" 2>/dev/null log_success "Удалён ignore для: $section" fi done else log_info "Нет настроек игнорирования для удаления" fi if [ "$DRY_RUN" = false ] && [ -s ".gitmodules" ]; then git add .gitmodules 2>/dev/null || true log_success "Изменения добавлены в индекс" fi else log_warning "Файл .gitmodules не существует" fi if [ "$QUIET" != true ]; then echo "" echo -e "${GREEN}✅ Настройки игнорирования удалены${NC}" echo "" fi # Если только удаление — выходим if [ "$IGNORE_MODE" = "" ] && [ "$IGNORE_SUBMODULE" = "" ] && [ "$FORCE" = false ]; then exit 0 fi fi # ============================================================ # Поиск вложенных репозиториев # ============================================================ log_step "Поиск вложенных репозиториев..." TEMP_FILE=$(mktemp) # Исключаем корень «.»; нормализуем ./path → path (portable, без sed -i) find . -type d -name ".git" -exec dirname {} \; 2>/dev/null \ | while IFS= read -r p; do [ "$p" = "." ] && continue normalize_path "$p" printf '\n' done \ | grep -v '^$' \ | sort -u > "$TEMP_FILE" if [ ! -s "$TEMP_FILE" ]; then log_warning "Вложенные репозитории не найдены" rm -f "$TEMP_FILE" exit 0 fi TOTAL=$(wc -l < "$TEMP_FILE" | tr -d ' ') log_success "Найдено $TOTAL вложенных репозиториев" if [ "$VERBOSE" = true ]; then echo "" echo -e "${CYAN}📋 Список найденных репозиториев:${NC}" while IFS= read -r repo; do echo " 📁 $repo" done < "$TEMP_FILE" echo "" fi # ============================================================ # Основной цикл регистрации # ============================================================ log_step "Регистрация подмодулей..." REGISTERED=0 SKIPPED=0 ERRORS=0 NO_URL=0 while IFS= read -r repo; do [ -z "$repo" ] && continue repo=$(normalize_path "$repo") log_debug "Обработка: $repo" if [ ! -d "$repo" ]; then log_warning "Папка не существует: $repo" ERRORS=$((ERRORS + 1)) continue fi if ! url=$(git -C "$repo" remote get-url origin 2>/dev/null); then url="" fi if [ -z "$url" ]; then log_warning "Не удалось определить URL для: $repo (нет remote 'origin')" NO_URL=$((NO_URL + 1)) continue fi if [ "$QUIET" != true ]; then echo -e "${GREEN}➕ Регистрация:${NC} $repo" echo -e " ${CYAN}URL:${NC} $url" fi # set -e не должен прерывать цикл при ошибке одного подмодуля set +e register_submodule "$repo" "$url" result=$? set -e case $result in 0) log_success "Подмодуль зарегистрирован: $repo" REGISTERED=$((REGISTERED + 1)) # Если задан режим игнорирования для всех подмодулей if [ -n "$IGNORE_MODE" ]; then if set_submodule_ignore "$repo" "$IGNORE_MODE"; then log_debug "Установлен ignore=$IGNORE_MODE для $repo" fi fi ;; 2) log_debug "Пропущен (уже зарегистрирован): $repo" SKIPPED=$((SKIPPED + 1)) ;; *) log_error "Ошибка при регистрации: $repo" ERRORS=$((ERRORS + 1)) ;; esac echo "" done < "$TEMP_FILE" # ============================================================ # Настройка игнорирования для конкретного подмодуля # ============================================================ if [ -n "$IGNORE_SUBMODULE" ]; then # Формат: path:mode if [[ "$IGNORE_SUBMODULE" =~ ^([^:]+):(.+)$ ]]; then SUBMODULE_PATH="${BASH_REMATCH[1]}" SUBMODULE_MODE="${BASH_REMATCH[2]}" log_step "Настройка игнорирования для: $SUBMODULE_PATH (режим: $SUBMODULE_MODE)" # Проверить, существует ли такой подмодуль if git config --file .gitmodules --get "submodule.$SUBMODULE_PATH.url" >/dev/null 2>&1; then set_submodule_ignore "$SUBMODULE_PATH" "$SUBMODULE_MODE" if [ $? -eq 0 ]; then log_success "Установлен ignore=$SUBMODULE_MODE для $SUBMODULE_PATH" else log_error "Не удалось установить ignore для $SUBMODULE_PATH" ERRORS=$((ERRORS + 1)) fi else log_warning "Подмодуль $SUBMODULE_PATH не найден в .gitmodules" fi else log_error "Неверный формат для --ignore-submodule. Используйте: path:mode" log_error "Пример: --ignore-submodule tools/cam:dirty" ERRORS=$((ERRORS + 1)) fi echo "" fi # ============================================================ # Очистка # ============================================================ rm -f "$TEMP_FILE" # ============================================================ # Итоговый вывод # ============================================================ if [ "$QUIET" != true ]; then echo "" echo "============================================================" echo -e "${CYAN}📊 Статистика${NC}" echo "============================================================" echo -e " ${BLUE}Всего найдено:${NC} $TOTAL" echo -e " ${GREEN}Зарегистрировано:${NC} $REGISTERED" echo -e " ${YELLOW}Пропущено:${NC} $SKIPPED" echo -e " ${YELLOW}Без URL:${NC} $NO_URL" if [ $ERRORS -gt 0 ]; then echo -e " ${RED}Ошибок:${NC} $ERRORS" fi if [ -n "$IGNORE_MODE" ]; then echo "" echo -e "${CYAN}🔧 Режим игнорирования:${NC} $IGNORE_MODE" echo " Файл .gitmodules обновлён" fi if [ "$DRY_RUN" = true ]; then echo "" echo -e "${YELLOW}⚠️ Это был просмотр (--dry-run). Изменения НЕ применены.${NC}" echo " Уберите --dry-run для выполнения." fi echo "" fi # ============================================================ # Следующие шаги # ============================================================ if [ "$DRY_RUN" = false ] && [ $REGISTERED -gt 0 ]; then if [ "$QUIET" != true ]; then echo -e "${GREEN}✅ Подмодули зарегистрированы!${NC}" echo "" echo -e "${CYAN}Следующие шаги:${NC}" echo " 1. Проверьте изменения: git status" echo " 2. Посмотрите подмодули: git submodule status" echo " 3. Закоммитьте изменения: git commit -m \"Зарегистрированы все подмодули\"" echo " 4. Отправьте на сервер: git push" echo "" echo -e "${YELLOW}⚠️ При первом клонировании выполните:${NC}" echo " git submodule update --init --recursive" echo "" fi elif [ "$DRY_RUN" = false ] && [ -n "$IGNORE_MODE" ]; then if [ "$QUIET" != true ]; then echo -e "${GREEN}✅ Настройки игнорирования обновлены!${NC}" echo "" echo -e "${CYAN}Следующие шаги:${NC}" echo " 1. Проверьте изменения: git status" echo " 2. Закоммитьте изменения: git commit -m \"Настроен ignore для подмодулей\"" echo " 3. Отправьте на сервер: git push" echo "" fi fi if [ "$QUIET" != true ]; then echo "============================================================" echo "" fi exit 0