Mettre à jour plusieurs dépôts git

Pour tous ceux qui travaillent avec plusieurs dépôts git, voire une multitude, il est parfois très fastidieux de les tenir à jour. Le plus simple est la boucle bash qui fait tous les updates à la suite :

find . -type d -depth 1 -exec git --git-dir={}/.git --work-tree=$PWD/{} pull origin master \\;

C’est déjà bien pratique mais ce n’est pas très clair. C’est très verbeux, on distingue mal les problèmes des mises à jour. Il faut retrouver les dépôts non mis à jour à cause de fichiers en cours de modification ou à cause d’autres problèmes … Bref ce n’est pas la solution ultime.

Une recherche sur internet nous propose des quantités des solutions mais rien de simple, clair, facile à mettre en place. Voici donc un script qui fait un pull de tous les dépôts git présent dans le répertoire home de l’utilisateur courant.

#!/bin/bash -m

MAX_PROC=10
export SCREEN_COLS=$(tput cols)
export BRANCH_WIDTH=$(expr ${SCREEN_COLS} - 70 - 13)

export NORMAL="\\\\033[0;39m"
export ROUGE="\\\\033[1;31m"
export VERT="\\\\033[1;32m"
export JAUNE="\\\\033[1;33m"
export PF_NORMAL="\\e[0;39m%s\\e[m"
export PF_ROUGE="\\e[1;31m%s\\e[m"
export PF_VERT="\\e[1;32m%s\\e[m"
export PF_JAUNE="\\e[1;33m%s\\e[m"

START_TIME=$(date +%s%N | cut -b1-13)

export fifo=$(mktemp -u)
mkfifo $fifo
trap "rm -f $fifo; exit" INT TERM EXIT
echo "\\n" >$fifo &

log() {
  ( flock -n 200

    color=$1
    shift
    branchformat="%-${BRANCH_WIDTH}s"
    #"\\e[96m"
    if [ "$2" != "master" -a "$2" != "develop" ]; then branchformat="\\e[96m%-${BRANCH_WIDTH}s\\e[m"; fi
    printf "%-70s $branchformat $color\\n" $@

  ) 200>/var/lock/.$(basename "$0").lock
}
log_dirty(){ log $PF_JAUNE $@ "DIRTY"; }
log_updated(){ log $PF_VERT $@ "UPDATED"; }
log_uptodate(){ log $PF_VERT $@ "UP-TO-DATE"; }
log_error(){
  ( flock -n 200
    printf '%*s\\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' =
    printf "%-70s %-${BRANCH_WIDTH}s $PF_ROUGE %s\\n" $1 $2 $3;
    echo -e "\\n ${4} \\n";
    printf '%*s\\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' =
  ) 200>/var/lock/.$(basename "$0").lock
}

update_git_repo() {
  gitdir=$1
  GIT="git --git-dir=$gitdir --work-tree=$(dirname $gitdir)"
  CURRENT_REPO=$($GIT config --get remote.origin.url)
  CURRENT_BRANCH=$($GIT symbolic-ref -q --short HEAD || $GIT describe --tags --exact-match)

  if [[ ! -z $($GIT status --porcelain) ]]; then
    log_dirty $CURRENT_REPO $CURRENT_BRANCH
    return
  fi

  OUTPUT=$($GIT pull 2>&1)
  if [ $? -eq 0 ]; then
    if [[ $OUTPUT != *"Already"* ]]; then
      log_updated $CURRENT_REPO $CURRENT_BRANCH
    fi
  else
    log_error $CURRENT_REPO $CURRENT_BRANCH "ERROR" "--> ${OUTPUT}"
    echo "\\t * ${CURRENT_REPO}" >$fifo &
  fi
}

export -f update_git_repo
export -f log
export -f log_dirty
export -f log_error
export -f log_updated
export -f log_uptodate
find -L ~ -maxdepth 5 -path "*.git" -not -path "*zprezto*" -type d 2> /dev/null | xargs --max-proc=$MAX_PROC -n 1 -I {} bash -c "update_git_repo {}"

END_TIME=$(date +%s%N | cut -b1-13)
TIME_MS=$(expr $END_TIME - $START_TIME)
TIME=$(echo print $TIME_MS / 1000. | python)

echo -e "\\n"
echo "Updates terminated in $(expr $TIME)s"
echo -e "\\n"
ERRORS=$(cat <$fifo)
if [ ! -z "$ERRORS" ]; then
  echo -e "Repository not updated beacause of ${ROUGE}ERRORS${NORMAL} :"
  echo -e "${ROUGE}${ERRORS}${NORMAL}"
fi

Ce script donne en sortie une ligne pour chaque dépôt trouvé. Sur chaque ligne on aura l’url du dépôt, la branche courrante et le résultat du pull :

  • UP TO DATE: pour les dépôts déjà à jour
  • UPDATED: pour les dépôts mis à jour
  • DIRTY: pour les dépôts nécessitant un commit
  • ERROR: pour les dépôts en error. Dans ce cas, l’erreur est affiché en suivant.

A la fin de la procédure un récapitulatif des dépôts en erreur est affiché.

Voilà, j’espère que ça servira à d’autres, je suis ouvert à toutes amélioration.

EDIT 2016-03-21: Mise à jour du script
Limitation du nombre de thread via xargs. Sous les versions récentes de docker le nombre de process fils est limité et le script générait des erreurs de fork. En limitant le nombre de thread à 5 on évite ce problème sans que la durée totale de la mise à jour n’en soit trop impactée.

find -L ~ -maxdepth 5 -path "*.git" -not -path "*zprezto*" -type d 2> /dev/null | \\
  xargs --max-proc=$MAX_PROC -n 1 -I {} bash -c "update_git_repo {}"

De plus le wait n’est plus nécessiare, c’est xargs qui s’en occupe. Par contre il est nécessaire d’exporter toutes les variables et les fonctions.

EDIT 2016-03-17: Mise à jour du script
Amélioration du threading :

  • La fonction de log est synchrone pour éviter que plusieurs résultats s’affiche sur la même ligne.
  • L’affichage des erreurs est amélioré.
  • La synthèse des erreurs en fin de script marche à nouveau.
  • Utilisation de wait à la place de la boucle pour attendre les fils.

EDIT 2015-12-11: Mise à jour du script
Le scrip est modifié pour être multi-thread, c’est beaucoup plus rapide sur un grande quantité de repos.

Mettre à jour plusieurs dépôts git écrit à l'origine par Marthym pour J’ai acheté un PC neuf cassé ... le October 10, 2015.

Vus : 326
Publié par Marthym : 55