Affichage des articles dont le libellé est Shell. Afficher tous les articles
Affichage des articles dont le libellé est Shell. Afficher tous les articles

mercredi 20 juin 2018

Tricher avec les dates de commit de git

A l'origine, je voulais seulement vérifier que les informations user et email d'un repository git étaient présente.

En effet, sur la même machine, je peux être amené à travailler sur un repository personnel (le week-end ou le soir) ou sur un repository de l'entreprise qui m'emploie (en journée).
De là, j'ai découvert qu'on pouvait indiquer la date de commit et la date d'auteur à git... J'ai trouvé ça plus amusant de présenter un script qui modifie l'heure du commit (par defaut 19:00 + X).

Comment le mettre en place


Tout d'abord, dans le fichier .bash_aliases, je crée un alias qui surcharge la commande git :
alias git="~/bin/alias-git.sh"
Ensuite dans le répertoire ~/bin je crée le fichier alias-git.sh avec le contenu suivant :
#!/bin/sh

COUNTER_FILE="${HOME}/bin/.counter_date"

get_counter() {
  # Read counter file
  if [ -f "${COUNTER_FILE}" ]; then
    # Check if file is same day that today
    COUNTER_FILE_DATE=$(date -r "${COUNTER_FILE}" +"%Y%m%d")
    CURRENT_FILE_DATE=$(date +"%Y%m%d")

    if [ "${COUNTER_FILE_DATE}" -eq "${CURRENT_FILE_DATE}" ]; then
      COUNTER=$(cat "${COUNTER_FILE}")

      COUNTER=$(expr ${COUNTER} + 1)
    else
      # Reinit counter file
      COUNTER=0
      rm -f "${COUNTER_FILE}"
    fi
  else
    COUNTER=0
  fi
}

# Check only if git commit
if [ "$1" = "commit" ]; then
  if [ -d '.git' ]; then
    GIT_LOCAL_CONFIG=$(git config --list --local)

    IS_GITHUB_REPOSITORY=$(echo ${GIT_LOCAL_CONFIG} | grep 'github')
    IS_EMAIL=$(echo ${GIT_LOCAL_CONFIG}| grep 'user.email')
    IS_USERNAME=$(echo ${GIT_LOCAL_CONFIG} | grep 'user.name')

    if [ -n "${IS_GITHUB_REPOSITORY}" ]; then
      if [ -n "${IS_EMAIL}" ] && [ -n "${IS_USERNAME}" ]; then
        echo "** Changer happy hour ! **"

        get_counter

        # Take today, set hour to 19:00 and add minutes
        NEW_DATE=$(date -d "19:00 today + ${COUNTER} minutes" +'%Y-%m-%d %H:%M:%S')

        export GIT_COMMITTER_DATE="${NEW_DATE}"
        export GIT_AUTHOR_DATE="${NEW_DATE}"
      else
        echo "fatal: this is github repository without username and email set !" >&2

        exit 255
      fi
    fi
  else
    echo "fatal: not a git repository: .git" >&2

    exit 128
  fi
fi

git "$@"

RESULT=$?

unset GIT_COMMITTER_DATE
unset GIT_AUTHOR_DATE

# Counter set, save it only if git OK
if [ -n "${COUNTER}" ] && [ ${RESULT} -eq 0 ]; then
  echo "${COUNTER}" > "${COUNTER_FILE}"
fi

exit ${RESULT}

Comment ça fonctionne


Le script regarde la commande git qui est exécutée. S'il s'agit de la commande commit, elle fait quelques vérifications :
  • Est-on sur un repository Github ?
  • Est-ce le user et email est configuré ? Si ce n'est pas le cas, le script s'arrête en erreur.
  • Le script va lire un compteur.
  • La date de commit est le jour même mais avec l'heure de départ 19:00 à laquelle j'ajoute le compteur en tant que minute.

Cela signifie donc qu'à chaque commit sur un repository Github, le compteur est incrémenté de 1. Les commits seront horodatés : 19:00, 19:01, 19:02...

Ce que j'ai appris


Ce script m'aura permis d'apprendre de nouvelles choses sur git et comment manipuler/calculer les dates/heures en shell comme je ne l'avais jamais fait.
J'ai pu découvrir qu'il y avait 2 dates pour un commit et qu'on pouvait les spécifier avec GIT_COMMITTER_DATE et GIT_AUTHOR_DATE.

mardi 31 juillet 2012

Mettre à jour des propriétaires de fichier sous Unix (niveau avancé)

Il arrive parfois, des malheurs. Dernièrement, il m'ai arrivé, au travail, sous Linux en root de faire un rm -rf /*.
Ceux connaissant bien Unix comprenne l'étendu de la catastrophe !
Heureusement, seuls les répertoires "sbin", bin et etc on été supprimés.

Fort chanceusement, les postes sont tous des clones.
J'ai donc recopié les répertoires en question.

Oui mais voilà, le répertoire etc contient le fichier passwd et group qui associe UID et nom d'utilisateur.
De ce fait, je me retrouve avec des noms d'utilisateur inversé...

J'aurais pu m'arranger avec ces fichiers, mais les risques d'erreurs étaient trop nombreux.
Mieux vaut utiliser chmod.

D'accord, mais comment savoir les fichiers concernés et comment modifier juste l'utilisateur si c'est l'utilisateur qui est mauvais, ou simplement le groupe ?
Nous allons partir du principe que l'utilisateur toto doit devenir l'utilisateur emeric.

Avant d'écrire un script, il est bon d'essayer de le faire à la main pour comprendre ce qui va se passer.

La commande ls -l permet de lister les fichiers et répertoires avec leurs informations. Le premier problème vient du fait que si c'est un répertoire, le contenu est listé.
La parade ? ls -l -d, -d pour afficher un répertoire comme s'il s'agissait d'un fichier.
$> ls -l
 drwxr-xr-x   2 root  wheel      512 Aug 13  2008 altroot/
 drwxr-xr-x   2 root  wheel     1024 Aug 13  2008 bin/
 -rw-r--r--   1 root  wheel    43348 Feb 20 01:52 boot
 -rw-r--r--   1 root  wheel  6861562 Aug 13  2008 bsd
 -rw-r--r--   1 root  wheel  5534077 Aug 13  2008 bsd.rd
 drwxr-xr-x   3 root  wheel    20992 Apr  3 08:29 dev/
 drwxr-xr-x  19 root  wheel     2560 Apr  3 08:29 etc/
$>
On a donc comme information le user, le group et le nom du fichier.
La difficulté vient du fait que les champs sont séparés avec un ou plusieurs espaces.

Le contournement va faire appel à une astuce du BASH, les tableaux et IFS (internal field separator).

BASH ne connait que les chaînes de caractères. Pour lui, un tableau est simplement une chaîne avec des séparateurs.
Pour créer un tableau, il suffit de déclarer :
monTableau=( 1 2 3 )
Nous allons redéfinir l'IFS car par défaut, il n'est pas initialisé comme on le souhaite, et des effets de bord risque d'apparaître.
On le définit comme un espace :
IFS=' '
Si nous prenons une ligne pour faire un test à la main, voici ce qu'on peut faire :
$> IFS=' '
$> TAB=( $(echo ' -rw-r--r--   1 root  wheel    43348 Feb 20 01:52 boot') ) 
$> echo ${#TAB[*]} / ${TAB[*]}
9/-rw-r--r-- 1 root wheel 43348 Feb 20 01:52 boot
On voit donc que les multiples espaces ne sont pas pris en compte. En effet, BASH initialise une "case" de tableau que s'il y a une valeur.

Il sera donc nécessaire de faire le ls -l -d sur chaque fichier unitairement. Pour cela, find sera utilisé. On utilisera la capacité de find pour exécuter le ls sur les fichiers trouvés va l'option -exec.

Les éléments sont suffisants pour créer déjà ce premier script
#!/bin/sh

# Sauvegarde l'ancien field separator
OLDIFS="$IFS";

# Saut de ligne est le separateur
IFS_NL=$'\n'
IFS_SPACE=' '

IFS="$IFS_NL"

# Recherche des fichiers uniquement
for fichier in $(find $1 -exec ls -l -d {} \;)
do
    # Definit le fiel separator en tant qu'espace
    IFS="$IFS_SPACE" ;

    fichier_propriete=( $(echo $fichier) )

    username=${fichier_propriete[2]}
    groupname=${fichier_propriete[3]}
    filename=${fichier_propriete[8]}

    echo ------------
    echo $username
    echo $groupname
    echo $filename
    echo -----------

    IFS="$IFS_NL"
done

IFS="$OLDIFS"
A présent, il ne reste plus qu'à détecter les fichiers concernés et faire la modification nécessaire (user ou group ou user/group). U simple grep sur la ligne retournée par find sera suffisant.
Enfin, pas tout à fait. Une fois que la ligne est identifiée comme devant être modifiée, il faudra de nouveau utiliser grep.

Voici enfin le rendu final :
#!/bin/sh

PATTERN_USER=toto
REPLACE_USER=emeric

# Sauvegarde l'ancien field separator
OLDIFS="$IFS";

# Saut de ligne est le separateur
IFS_NL=$'\n'
IFS_SPACE=' '

IFS="$IFS_NL"

# Recherche des fichiers uniquement
for fichier in $(find $1 -exec ls -l -d {} \;)
do
    user_detect=$(echo $fichier | grep $PATTERN_USER)

    if [ -n "$user_detect" ] ;
    then
        # Definit le fiel separator en tant qu'espace
        IFS="$IFS_SPACE" ;

        fichier_propriete=( $(echo $fichier) )

        username=${fichier_propriete[2]}
        groupname=${fichier_propriete[3]}
        filename=${fichier_propriete[8]}

        echo "Proceed file "$filename

        if [ -n $(echo "$username" | grep "$PATTERN_USER") ] ;
        then
            echo "  > Change user name"
            username="$REPLACE_USER"
        fi

        if [ -n $(echo "$groupname" | grep "$PATTERN_USER") ] ;
        then
            echo "  > Change groupe name"
            groupname="$REPLACE_USER"
        fi

        chown "$username":"$groupname" "$filename"

        IFS="$IFS_NL"
    fi
done

IFS="$OLDIFS" 
Attention toutefois, si le nom de fichier contient un espace, l'utilisation de fichier_propriete[8] posera un problème car il n'y aura que la première partie du nom.

Comment résoudre ce problème ? En utilisant le nom de fichier récupéré par find plutôt que celui de ls.
Pour cela, il faudra modifier le début du script :
# Recherche des fichiers uniquement
for fichier in $(find $1)
do
    ligne=$(ls -l -d $fichier)
    user_detect=$(echo $ligne | grep $PATTERN_USER)

    if [ -n "$user_detect" ] ;
    then
        # Definit le fiel separator en tant qu'espace
        IFS="$IFS_SPACE" ;

        fichier_propriete=( $(echo $ligne) )

        username=${fichier_propriete[2]}
        groupname=${fichier_propriete[3]}
        filename="$fichier"