Last active
July 17, 2026 22:30
-
-
Save enimiste/1c725ddce4335bfab92044a619c592e2 to your computer and use it in GitHub Desktop.
Renomme un dossier .git en .git.nosync (pour exclure la synchro iCloud) et remplace .git par un fichier pointeur "gitdir: ..." pour que Git continue de fonctionner normalement.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| set -e | |
| show_doc() { | |
| cat <<'EOF' | |
| ================================================================ | |
| git-nosync.sh | |
| ================================================================ | |
| Renomme un dossier .git en .git.nosync (pour exclure la synchro | |
| iCloud) et remplace .git par un fichier pointeur "gitdir: ..." | |
| pour que Git continue de fonctionner normalement. | |
| Ajoute aussi automatiquement "/.git.nosync" dans le fichier | |
| d'exclusion locale (.git.nosync/info/exclude), sinon Git affiche | |
| .git.nosync comme fichier "untracked" dans git status (car il | |
| n'est plus reconnu comme dossier spécial une fois renommé). | |
| Usage: | |
| ./git-nosync.sh /chemin/vers/mon-repo/.git | |
| ./git-nosync.sh /chemin/vers/mon-repo | |
| ./git-nosync.sh --rollback /chemin/vers/mon-repo | |
| ./git-nosync.sh -r /chemin/vers/mon-repo | |
| ./git-nosync.sh --all /chemin/vers/dossier-parent | |
| ./git-nosync.sh --all --rollback /chemin/vers/dossier-parent | |
| --rollback / -r : annule l'opération, c'est-à-dire remet | |
| .git.nosync à sa place en tant que .git (dossier). N'est exécuté | |
| que si les conditions de sécurité sont réunies (voir plus bas). | |
| --all <dossier-parent> : parcourt les sous-dossiers de premier | |
| niveau de <dossier-parent> et applique l'action (migration ou | |
| rollback, selon --rollback) uniquement sur ceux qui remplissent | |
| les conditions requises. Les autres sont ignorés silencieusement | |
| (juste listés). Une seule confirmation est demandée pour | |
| l'ensemble du lot, après affichage de la liste des repos | |
| concernés. | |
| ================================================================ | |
| EOF | |
| } | |
| show_doc | |
| usage() { | |
| echo "Usage: $0 [--rollback|-r] [--all <dossier-parent>] <chemin-vers-.git-ou-repo>" | |
| exit 1 | |
| } | |
| # Résoudre le chemin absolu (macOS n'a pas readlink -f par défaut) | |
| resolve_path() { | |
| cd "$(dirname "$1")" 2>/dev/null && echo "$(pwd)/$(basename "$1")" | |
| } | |
| # --- Parsing des arguments (ordre libre entre --rollback/-r et --all) --- | |
| ROLLBACK=0 | |
| BATCH=0 | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --rollback|-r) | |
| ROLLBACK=1 | |
| shift | |
| ;; | |
| --all) | |
| BATCH=1 | |
| shift | |
| ;; | |
| *) | |
| break | |
| ;; | |
| esac | |
| done | |
| if [ -z "$1" ]; then | |
| echo "Erreur: aucun chemin fourni." | |
| usage | |
| fi | |
| INPUT_PATH="$1" | |
| INPUT_PATH="$(resolve_path "$INPUT_PATH")" | |
| if [ -z "$INPUT_PATH" ] || [ ! -e "$INPUT_PATH" ]; then | |
| echo "Erreur: le chemin '$1' n'existe pas." | |
| exit 1 | |
| fi | |
| # ================================================================ | |
| # Fonctions "coeur" réutilisées en mode simple et en mode --all | |
| # Elles ne demandent AUCUNE confirmation : celle-ci est gérée par | |
| # l'appelant (une fois pour le mode simple, une fois pour tout le | |
| # lot en mode --all). | |
| # ================================================================ | |
| # Vérifie si un repo (chemin racine) est éligible à la migration. | |
| # Retourne 0 (succès) et affiche le chemin du .git si éligible. | |
| check_migrate_candidate() { | |
| _root="$1" | |
| if [ -d "$_root/.git" ]; then | |
| echo "$_root/.git" | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Vérifie si un repo (chemin racine) est éligible au rollback. | |
| check_rollback_candidate() { | |
| _root="$1" | |
| _git_file="$_root/.git" | |
| _nosync_dir="$_root/.git.nosync" | |
| [ -f "$_git_file" ] || return 1 | |
| [ -d "$_git_file" ] && return 1 | |
| _content="$(cat "$_git_file" 2>/dev/null || true)" | |
| case "$_content" in | |
| "gitdir: ./.git.nosync"|"gitdir: .git.nosync") | |
| ;; | |
| *) | |
| return 1 | |
| ;; | |
| esac | |
| [ -d "$_nosync_dir" ] || return 1 | |
| return 0 | |
| } | |
| # Applique la migration sur un repo donné (sans confirmation). | |
| do_migrate() { | |
| _root="$1" | |
| _git_dir="$_root/.git" | |
| _nosync_dir="$_root/.git.nosync" | |
| if [ -e "$_nosync_dir" ]; then | |
| echo " ✗ [$_root] '.git.nosync' existe déjà, ignoré." | |
| return 1 | |
| fi | |
| mv "$_git_dir" "$_nosync_dir" | |
| echo "gitdir: ./.git.nosync" > "$_root/.git" | |
| _exclude_file="$_nosync_dir/info/exclude" | |
| if [ -f "$_exclude_file" ] && ! grep -qxF "/.git.nosync" "$_exclude_file" 2>/dev/null; then | |
| echo "/.git.nosync" >> "$_exclude_file" | |
| fi | |
| if command -v git >/dev/null 2>&1 && ( cd "$_root" && git status >/dev/null 2>&1 ); then | |
| echo " ✓ [$_root] migré avec succès." | |
| else | |
| echo " ⚠ [$_root] migré, mais 'git status' a échoué, vérifie manuellement." | |
| fi | |
| return 0 | |
| } | |
| # Applique le rollback sur un repo donné (sans confirmation). | |
| do_rollback() { | |
| _root="$1" | |
| _git_file="$_root/.git" | |
| _nosync_dir="$_root/.git.nosync" | |
| rm "$_git_file" | |
| mv "$_nosync_dir" "$_git_file" | |
| if command -v git >/dev/null 2>&1 && ( cd "$_root" && git status >/dev/null 2>&1 ); then | |
| echo " ✓ [$_root] rollback effectué avec succès." | |
| else | |
| echo " ⚠ [$_root] rollback effectué, mais 'git status' a échoué, vérifie manuellement." | |
| fi | |
| return 0 | |
| } | |
| # ================================================================ | |
| # MODE --all (batch) | |
| # ================================================================ | |
| if [ "$BATCH" -eq 1 ]; then | |
| PARENT_DIR="$INPUT_PATH" | |
| if [ ! -d "$PARENT_DIR" ]; then | |
| echo "Erreur: '$PARENT_DIR' n'est pas un dossier." | |
| exit 1 | |
| fi | |
| echo "Analyse des sous-dossiers de '$PARENT_DIR' (1 niveau)..." | |
| echo "" | |
| CANDIDATES="" | |
| for SUBDIR in "$PARENT_DIR"/*/; do | |
| [ -d "$SUBDIR" ] || continue | |
| SUBDIR="${SUBDIR%/}" | |
| if [ "$ROLLBACK" -eq 1 ]; then | |
| if check_rollback_candidate "$SUBDIR" >/dev/null 2>&1; then | |
| CANDIDATES="$CANDIDATES | |
| $SUBDIR" | |
| fi | |
| else | |
| if check_migrate_candidate "$SUBDIR" >/dev/null 2>&1; then | |
| CANDIDATES="$CANDIDATES | |
| $SUBDIR" | |
| fi | |
| fi | |
| done | |
| CANDIDATES="$(echo "$CANDIDATES" | sed '/^$/d')" | |
| if [ -z "$CANDIDATES" ]; then | |
| if [ "$ROLLBACK" -eq 1 ]; then | |
| echo "Aucun sous-dossier éligible au rollback (.git fichier pointeur + .git.nosync dossier) trouvé." | |
| else | |
| echo "Aucun sous-dossier contenant un .git (dossier) trouvé." | |
| fi | |
| exit 0 | |
| fi | |
| if [ "$ROLLBACK" -eq 1 ]; then | |
| echo "Repos éligibles au ROLLBACK :" | |
| else | |
| echo "Repos éligibles à la MIGRATION :" | |
| fi | |
| echo "$CANDIDATES" | while IFS= read -r C; do | |
| echo " - $C" | |
| done | |
| echo "" | |
| printf "Confirmer l'action pour tous ces repos ? [y/N] " | |
| read -r CONFIRM | |
| case "$CONFIRM" in | |
| y|Y|yes|oui|o|O) | |
| ;; | |
| *) | |
| echo "Annulé." | |
| exit 0 | |
| ;; | |
| esac | |
| echo "" | |
| echo "$CANDIDATES" | while IFS= read -r C; do | |
| [ -n "$C" ] || continue | |
| if [ "$ROLLBACK" -eq 1 ]; then | |
| do_rollback "$C" || true | |
| else | |
| do_migrate "$C" || true | |
| fi | |
| done | |
| echo "" | |
| echo "Traitement du lot terminé." | |
| exit 0 | |
| fi | |
| # ================================================================ | |
| # MODE ROLLBACK (simple, un seul repo) | |
| # ================================================================ | |
| if [ "$ROLLBACK" -eq 1 ]; then | |
| if [ -f "$INPUT_PATH" ] && [ "$(basename "$INPUT_PATH")" = ".git" ]; then | |
| GIT_FILE="$INPUT_PATH" | |
| elif [ -e "$INPUT_PATH/.git" ]; then | |
| GIT_FILE="$INPUT_PATH/.git" | |
| else | |
| echo "Erreur: impossible de trouver un '.git' à cet emplacement." | |
| exit 1 | |
| fi | |
| REPO_ROOT="$(dirname "$GIT_FILE")" | |
| NOSYNC_DIR="$REPO_ROOT/.git.nosync" | |
| echo "Vérification des conditions de rollback..." | |
| if [ ! -f "$GIT_FILE" ]; then | |
| echo "Erreur: '$GIT_FILE' n'est pas un fichier (ou n'existe pas)." | |
| echo " Le rollback n'est possible que si .git est un fichier pointeur." | |
| echo " Abandon, aucune modification effectuée." | |
| exit 1 | |
| fi | |
| if [ -d "$GIT_FILE" ]; then | |
| echo "Erreur: '$GIT_FILE' est un DOSSIER (donc un vrai .git actif)." | |
| echo " Abandon pour éviter toute suppression accidentelle." | |
| exit 1 | |
| fi | |
| GIT_FILE_CONTENT="$(cat "$GIT_FILE" 2>/dev/null || true)" | |
| case "$GIT_FILE_CONTENT" in | |
| "gitdir: ./.git.nosync"|"gitdir: .git.nosync") | |
| ;; | |
| *) | |
| echo "Erreur: le contenu de '$GIT_FILE' ne correspond pas au pointeur attendu." | |
| echo " Contenu trouvé : $GIT_FILE_CONTENT" | |
| echo " Abandon, aucune modification effectuée." | |
| exit 1 | |
| ;; | |
| esac | |
| if [ ! -d "$NOSYNC_DIR" ]; then | |
| echo "Erreur: '$NOSYNC_DIR' n'existe pas ou n'est pas un dossier." | |
| echo " Abandon, aucune modification effectuée." | |
| exit 1 | |
| fi | |
| echo " ✓ '$GIT_FILE' est bien un fichier pointeur valide." | |
| echo " ✓ '$NOSYNC_DIR' est bien un dossier existant." | |
| echo "" | |
| echo "Actions prévues (rollback) :" | |
| echo " 1. Supprimer le fichier '$GIT_FILE'" | |
| echo " 2. mv '$NOSYNC_DIR' '$GIT_FILE'" | |
| echo "" | |
| printf "Confirmer le rollback ? [y/N] " | |
| read -r CONFIRM | |
| case "$CONFIRM" in | |
| y|Y|yes|oui|o|O) | |
| ;; | |
| *) | |
| echo "Annulé." | |
| exit 0 | |
| ;; | |
| esac | |
| echo "" | |
| do_rollback "$REPO_ROOT" | |
| exit 0 | |
| fi | |
| # ================================================================ | |
| # MODE NORMAL (simple, un seul repo) | |
| # ================================================================ | |
| if [ -d "$INPUT_PATH" ] && [ "$(basename "$INPUT_PATH")" = ".git" ]; then | |
| GIT_DIR="$INPUT_PATH" | |
| elif [ -d "$INPUT_PATH/.git" ]; then | |
| GIT_DIR="$INPUT_PATH/.git" | |
| else | |
| echo "Erreur: impossible de trouver un dossier .git à cet emplacement." | |
| usage | |
| fi | |
| REPO_ROOT="$(dirname "$GIT_DIR")" | |
| NOSYNC_DIR="$REPO_ROOT/.git.nosync" | |
| if [ ! -d "$GIT_DIR" ]; then | |
| echo "Erreur: '$GIT_DIR' n'est pas un dossier (peut-être déjà migré ?)." | |
| echo " Utilise --rollback si tu veux revenir en arrière." | |
| exit 1 | |
| fi | |
| if [ -e "$NOSYNC_DIR" ]; then | |
| echo "Erreur: '$NOSYNC_DIR' existe déjà. Abandon pour éviter d'écraser des données." | |
| exit 1 | |
| fi | |
| echo "Dossier .git détecté : $GIT_DIR" | |
| echo "Repo racine : $REPO_ROOT" | |
| echo "" | |
| echo "Actions prévues :" | |
| echo " 1. mv '$GIT_DIR' '$NOSYNC_DIR'" | |
| echo " 2. Créer '$REPO_ROOT/.git' contenant : gitdir: ./.git.nosync" | |
| echo "" | |
| printf "Confirmer ? [y/N] " | |
| read -r CONFIRM | |
| case "$CONFIRM" in | |
| y|Y|yes|oui|o|O) | |
| ;; | |
| *) | |
| echo "Annulé." | |
| exit 0 | |
| ;; | |
| esac | |
| echo "" | |
| do_migrate "$REPO_ROOT" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment