Files
git_man/register-submodules.sh
T
2026-07-20 16:59:55 +03:00

723 lines
27 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================
# register-submodules.sh - Регистрация всех вложенных репозиториев как подмодулей
#
# Версия: 1.0.6
# Автор: Michael BAG <mk@p7net.ru>
# Репозиторий: 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