From ba1193a132aaa7fe1f891ffe920fbbc060cc4fb3 Mon Sep 17 00:00:00 2001 From: mihailkudravcev Date: Mon, 20 Jul 2026 16:59:55 +0300 Subject: [PATCH] Release 1.0.6: auto-unignore, macOS fix, --version Co-authored-by: Cursor --- CHANGELOG.md | 27 +++++ README.md | 26 ++++- register-submodules.sh | 235 ++++++++++++++++++++++++++++++++++------- 3 files changed, 248 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d523e15..b12cb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,32 @@ --- +## [1.0.6] - 2026-07-20 + +### Добавлено + +- 🧹 **Автоснятие пути из `.gitignore`** перед регистрацией подмодуля + - Удаляются точные правила вида `/path/`, `path/`, `/path` + - Если срабатывает более широкое правило — добавляется исключение `!/path/` +- 📦 Регистрация **уже существующих** вложенных клонов без повторного `clone` + (`gitlink` + `git submodule absorbgitdirs`) +- 🏷️ Константа версии `SCRIPT_VERSION` и опции `--version` / `-V` + +### Исправлено + +- 🍎 Совместимость с **macOS/BSD `sed`**: корень `.` больше не попадал в список + (раньше `sed -i` без аргумента backup падал тихо, скрипт пытался + зарегистрировать `.` и обрывался из‑за `set -e`) +- Ошибка одного подмодуля больше не останавливает весь цикл регистрации +- Нормализация путей `./foo` → `foo` + +### Документация + +- 📖 Обновлён **README.md** +- 📝 Обновлён **CHANGELOG.md** + +--- + ## [1.1.0] - 2025-07-19 ### Добавлено @@ -107,6 +133,7 @@ | Версия | Дата | Изменения | |--------|------|-----------| +| **1.0.6** | 2026-07-20 | Автоснятие из `.gitignore`, fix macOS sed, `--version` | | **1.1.0** | 2025-07-19 | Добавлена настройка игнорирования подмодулей | | **1.0.0** | 2025-07-19 | Первый стабильный релиз | | **0.1.0** | 2025-07-19 | Предварительная версия (прототип) | diff --git a/README.md b/README.md index 8921ca9..feb1955 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Git Management Tools (`git_man`) +**Версия:** 1.0.6 + Набор скриптов и утилит для управления Git-репозиториями, работы с подмодулями и автоматизации повседневных задач. --- @@ -12,6 +14,7 @@ - [Установка](#установка) - [Использование](#использование) - [Требования](#требования) +- [История версий](#история-версий) - [Автор](#автор) - [Лицензия](#лицензия) @@ -23,6 +26,7 @@ Основные задачи: - Автоматическая регистрация вложенных репозиториев как подмодулей +- Снятие путей из `.gitignore` при регистрации - Централизованное управление множеством репозиториев - Настройка игнорирования состояния подмодулей в основном репозитории - Автоматизация рутинных операций @@ -34,7 +38,7 @@ ### `register-submodules.sh` **Описание:** -Автоматически находит все вложенные Git-репозитории в текущей папке и регистрирует их как подмодули в `.gitmodules`. Поддерживает настройку игнорирования состояния подмодулей. +Автоматически находит все вложенные Git-репозитории в текущей папке и регистрирует их как подмодули в `.gitmodules`. Перед регистрацией снимает путь из `.gitignore` (точные правила или исключение `!/path/`). Уже существующие клоны регистрируются без повторного clone. Поддерживает настройку игнорирования состояния подмодулей. **Использование:** ```bash @@ -46,6 +50,7 @@ | Опция | Описание | |-------|----------| | `--help`, `-h` | Показать справку | +| `--version`, `-V` | Показать версию скрипта (`1.0.6`) | | `--dry-run` | Только показать, что будет сделано (без изменений) | | `--force`, `-f` | Принудительная перерегистрация подмодулей | | `--ignore=MODE` | Установить режим игнорирования для ВСЕХ подмодулей | @@ -176,6 +181,25 @@ git submodule add https://git.p7net.ru/tools/git_man.git tools/git_man --- +## 📌 История версий + +Текущая версия: **1.0.6** (2026-07-20). + +Кратко по релизам — см. [CHANGELOG.md](./CHANGELOG.md). + +| Версия | Дата | Суть | +|--------|------|------| +| **1.0.6** | 2026-07-20 | Автоснятие из `.gitignore`, fix macOS, `--version` | +| **1.1.0** | 2025-07-19 | Настройка `ignore` для подмодулей | +| **1.0.0** | 2025-07-19 | Первый стабильный релиз | + +Проверка версии: +```bash +./register-submodules.sh --version +``` + +--- + ## 👤 Автор **Michael BAG** diff --git a/register-submodules.sh b/register-submodules.sh index d1612c3..c2ebb8a 100755 --- a/register-submodules.sh +++ b/register-submodules.sh @@ -2,14 +2,16 @@ # ============================================================ # register-submodules.sh - Регистрация всех вложенных репозиториев как подмодулей # +# Версия: 1.0.6 # Автор: Michael BAG # Репозиторий: https://git.p7net.ru/tools/git_man # # Использование: -# ./register-submodules.sh [--help] [--dry-run] [--force] [--ignore=all|dirty|untracked|none] +# ./register-submodules.sh [--help] [--version] [--dry-run] [--force] [--ignore=all|dirty|untracked|none] # # Опции: # --help, -h Показать эту справку +# --version, -V Показать версию скрипта # --dry-run Только показать, что будет сделано (без изменений) # --force, -f Принудительно перерегистрировать подмодули # --ignore=MODE Установить режим игнорирования для всех подмодулей @@ -24,25 +26,33 @@ # Описание: # Скрипт находит все вложенные репозитории (папки с .git), # определяет их URL и регистрирует как подмодули в .gitmodules. +# Перед регистрацией снимает путь с .gitignore (если он там есть). # Также позволяет настроить игнорирование состояния подмодулей. # ============================================================ -set -e # Остановка при ошибке +set -e # Остановка при ошибке (критичные шаги); ошибки регистрации ловятся явно + +SCRIPT_VERSION="1.0.6" # ============================================================ # Функция показа справки # ============================================================ show_help() { - cat << 'EOF' + cat << EOF ============================================================ -📦 register-submodules.sh - Регистрация вложенных репозиториев +📦 register-submodules.sh v${SCRIPT_VERSION} - Регистрация вложенных репозиториев ============================================================ +ОПИСАНИЕ: + Находит вложенные репозитории (.git), регистрирует их как + подмодули. Перед регистрацией сам убирает путь из .gitignore. + ИСПОЛЬЗОВАНИЕ: ./register-submodules.sh [ОПЦИИ] ОПЦИИ: --help, -h Показать эту справку + --version, -V Показать версию скрипта --dry-run Только показать, что будет сделано (без изменений) --force, -f Принудительно перерегистрировать подмодули (удаляет существующие и создаёт заново) @@ -85,6 +95,11 @@ EOF exit 0 } +show_version() { + echo "register-submodules.sh ${SCRIPT_VERSION}" + exit 0 +} + # ============================================================ # Инициализация переменных # ============================================================ @@ -104,6 +119,9 @@ while [[ $# -gt 0 ]]; do --help|-h) show_help ;; + --version|-V) + show_version + ;; --dry-run) DRY_RUN=true shift @@ -214,6 +232,95 @@ 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 для подмодуля # ============================================================ @@ -245,33 +352,82 @@ remove_submodule_ignore() { 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="$1" + 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 - echo " [DRY-RUN] git submodule add $url $repo" + 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 --file .gitmodules --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 git config --file .gitmodules --get "submodule.$repo.url" >/dev/null 2>&1; then + if [ -f .gitmodules ] && git config -f .gitmodules --get "submodule.$repo.url" >/dev/null 2>&1; then log_debug "Подмодуль $repo уже зарегистрирован" return 2 fi - - git submodule add "$url" "$repo" 2>/dev/null - return $? + + # Каталог уже клонирован — регистрируем без повторного 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 } # ============================================================ @@ -366,8 +522,15 @@ fi log_step "Поиск вложенных репозиториев..." TEMP_FILE=$(mktemp) -find . -type d -name ".git" -exec dirname {} \; 2>/dev/null | sort > "$TEMP_FILE" -sed -i '/^\.$/d' "$TEMP_FILE" 2>/dev/null || true +# Исключаем корень «.»; нормализуем ./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 "Вложенные репозитории не найдены" @@ -381,9 +544,9 @@ log_success "Найдено $TOTAL вложенных репозиториев" if [ "$VERBOSE" = true ]; then echo "" echo -e "${CYAN}📋 Список найденных репозиториев:${NC}" - cat "$TEMP_FILE" | while read repo; do + while IFS= read -r repo; do echo " 📁 $repo" - done + done < "$TEMP_FILE" echo "" fi @@ -397,43 +560,38 @@ SKIPPED=0 ERRORS=0 NO_URL=0 -while read repo; do +while IFS= read -r repo; do [ -z "$repo" ] && continue + repo=$(normalize_path "$repo") log_debug "Обработка: $repo" - if [ -d "$repo" ]; then - cd "$repo" 2>/dev/null - if [ $? -ne 0 ]; then - log_error "Не удалось перейти в папку: $repo" - ERRORS=$((ERRORS + 1)) - cd - > /dev/null 2>/dev/null || true - continue - fi - - url=$(git remote get-url origin 2>/dev/null) - cd - > /dev/null 2>/dev/null || true - - if [ -z "$url" ]; then - if [ "$VERBOSE" = true ] || [ "$QUIET" != true ]; then - log_warning "Не удалось определить URL для: $repo (нет remote 'origin')" - fi - NO_URL=$((NO_URL + 1)) - continue - fi - else + 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) @@ -442,8 +600,7 @@ while read repo; do # Если задан режим игнорирования для всех подмодулей if [ -n "$IGNORE_MODE" ]; then - set_submodule_ignore "$repo" "$IGNORE_MODE" - if [ $? -eq 0 ]; then + if set_submodule_ignore "$repo" "$IGNORE_MODE"; then log_debug "Установлен ignore=$IGNORE_MODE для $repo" fi fi