Skip to content

Instantly share code, notes, and snippets.

@enimiste
Last active July 17, 2026 22:30
Show Gist options
  • Select an option

  • Save enimiste/1c725ddce4335bfab92044a619c592e2 to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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