Git

Published: 07-01-2015

Updated: 31-05-2017

By: Maxime de Roucy

tags: git gitolite

Mes notes et commandes intéressantes concernant git.

Suppression des dernier commit dans un dépôt « bare » (source) :

max@laptop % git update-ref refs/heads/<branchName> <commitSha>

Suppression d’un branche distante (source, les deux commande sont équivalentes) :

max@laptop % git push origin --delete <branchName>
max@laptop % git push origin :<branchName>

<branchName> contient uniquement le nom de la branche sur le dépôt distant. Par exemple si git branch -a retourne remotes/origin/systemd, <branchName> correspond à systemd.

Faire un merge en créant explicitement un commit de merge (pas de fast-forward) :

max@laptop % git merge --no-ff <branchName>

Compresser/nettoyer/optimiser l’historique :

max@laptop % git gc

Création d’une branche complètement décorrélée/détachée des autres branches :

max@laptop % git checkout --orphan
max@laptop % git rm --cached .

Cache/Stage une partie d’un fichier (source) :

max@laptop % git add --patch …/fichier

Les différentes options qui sont proposées sont :

Visualisation des logs de plusieurs branches (source) :

max@laptop % git log --graph --decorate --oneline --all

Push d’une nouvelle branche locale sur un dépôt distant et ajout de la référence upstream sur cette branche locale. Cette commande peut être lancée alors que le « working tree » est sur n’importe quelle branche.

max@laptop % git branch -vv
* master 73420d4 [origin/master] ingres backup
  nspawn 7a3d421 networking

max@laptop % git push -u origin nspawn
…
To git@craoc.fr:pelican
 * [new branch]      nspawn -> nspawn
La branche nspawn est paramétrée pour suivre la branche distante nspawn depuis origin.

max@laptop % git branch -vv
* master 73420d4 [origin/master] ingres backup
  nspawn 7a3d421 [origin/nspawn] networking

Pour mettre à jour la liste des branches distantes (source) :

max@laptop % git remote update origin --prune

Faire une recherche dans le code et sur toutes ses versions (source) :

max@laptop % git grep <regexp> $(git rev-list --all)
max@laptop % git rev-list --all | xargs git grep <regexp>

bare repository

Les dépôts « bare » sont des dépôts sans répertoire de travail. Gitolite stock les dépôt sous leur forme « bare ». Si vous voulez supprimer les dernier commit d’un dépôt bare, et que vous savez ce que vous faite il faut utiliser la commande git update-ref (source). git reset ne fonctionne pas dans un dépôt « bare ».

max@ % sudo -s -u git
git@ % cd ~/repositories/test.git
git@ % git log
commit 3214471bb28b9735c4e8d663bb977fb638ccfddb
Author: Maxime de Roucy <maxime.deroucy@gmail.com>
Date:   Thu Aug 6 13:30:02 2015 +0200

    bbbb

commit e8f3b1c21ccd6ac52622b6b9efa8f6e765d238ea
Author: Maxime de Roucy <maxime.deroucy@gmail.com>
Date:   Thu Jul 9 23:38:11 2015 +0200

    aaaa

git@ % git update-ref refs/heads/master e8f3b1c21ccd6ac52622b6b9efa8f6e765d238ea
git@ % git log
commit e8f3b1c21ccd6ac52622b6b9efa8f6e765d238ea
Author: Maxime de Roucy <maxime.deroucy@gmail.com>
Date:   Thu Jul 9 23:38:11 2015 +0200

    aaaa

Les « reflogs » ne sont pas activé par défaut sur les dépôts « bare » (exepté sur le dépôts gitolite-admin… en tout cas chez moi, je ne sais pas si c’est utile).

duplication de dépôt

J’ai eu à transférer un dépôt d’un serveur à un autre. Je ne sais plus exactement quelle procédure j’ai appliqué mais je l’ai tiré de la page d’aide github duplicating a repository.

git blame

blame indique le dernier commit ayant modifier la ligne.

Si on veut voir l’avant dernier commit :

max@laptop % git blame -L 5,5 -- content/distro/deb-build.md
1d4f70ba (Maxime de Roucy 2016-10-29 18:14:55 +0200 5) summary: Quelques notes sur la création/compilation de paquets Debian
max@laptop % git blame -L 5,5 1d4f70ba~1 -- content/distro/deb-build.md
762bbfc3 (Maxime de Roucy 2015-07-18 13:44:05 +0200 5) summary: Comment j'ai re-packagé `gdm` (paquet `gdm3`) pour déboguer et corriger le problème évoqué dans mon précédent article sur Xephyr.

L’option -w permet de ne pas prendre en compte un commit si celui-ci n’a modifier que des espaces dans la ligne courante.

hooks

Dans le dépôt qui stock ce site web j’ai mis un hook qui permet de tester si les commits envoyé (push) sont correct et les déplois.

J’utilisais le hook pre-receive, j’utilise maintenant le hook update. Il permettent tous les deux de rejeter les commits si le script échoue (source).

Pour pouvoir lancer les tests il faut un dossier de travail contenant les fichiers du dépôt. Un dépôts gitolite ne contient pas ces fichiers, il est sous forme « bare ». Pour créé les fichier, la méthode préconisée sur le site codeutopia.net consiste à utiliser git archive. Cette méthode est aussi décrite dans les notes de l’auteur de gitolite sur le déploiement.

Au début je faisait donc du « archive dump with staging » mais cela pause quelque problèmes :

L’auteur de gitolite recommande d’utiliser la méthode checkout mais celle ci n’est pas adapté à mon cas d’utilisation. Lorsque l’on fait un checkout dans le dépôt bare pour créé un répertoire de travail séparé cela modifie aussi le HEAD enregistré dans le dépôt. Cela pose au problème si vous voulez créé un repertoire de travail correspondant à une branche différente de la branche principale du dépôt (je fait expret de ne pas ecrire master car il est possible de changer la branche principal d’un dépôt). Cela pose aussi problème si on utilise cette méthode dans le hook pre-receive dans lequelle les nouvelles données n’ont pas encore été mergées dans les branches. En effet, si on utilise git checkout -f <commit> à la place de git checkout -f master pour créé le répertoire de travail correspondant au nouvelle donnée, on place le dépôt dans un état « DETACHED HEAD ». D’ailleur les versions récente de git empèche les checkout dans un pre-receive.

Avec git 2.5 est arrivé git worktree qui permet créer un « working tree » git séparé du dépôt git initial. Ces « working tree » on l’avantage de posséder leur propre HEAD.

La solution que j’ai adopté est donc de créer un « working tree » git séparé grace à cette commande et d’utiliser git checkout dans celui-ci (dans le hook update). On sera toujours dans un état « DETACHED HEAD » mais uniquement dans le « working tree » et pas dans le dépôt, donc ce n’est pas génant.

Voici mon script de hook update que j’utilise dans le dépôt correspondant à ce site web :

#!/bin/bash

# if anything goes wrong the script is stopped and the push is abort
set -e -o pipefail -u

PELICAN_DIR="$HOME/pelican"

suppr=""
trap 'rm -f $suppr' EXIT

refname="$1"
oldrev="$2"
newrev="$3"

# Anything echo'd will show up in the console for the person who's doing a push

# Only run this script for the master branch.
if test "$refname" != "refs/heads/master"
then
        exit 0
fi

# pelican: check if "tags:" metadata is empty
if git diff $oldrev $newrev | grep -P '^\+tags:\s*$'
then
        echo "You have an element without tag."
        exit 1
fi

echo "Preparing to run pelican for $newrev"

# the default umask set in .gitolite.rc is 0077
# nginx needs to be able to read the data
umask 0022
if test -d $PELICAN_DIR
then
        env --unset=GIT_DIR git -C $PELICAN_DIR checkout --detach -f $newrev
else
        # if the $PELICAN_DIR isn't created we create it as a working tree
        # the $PELICAN_DIR has to be a working tree
        git worktree add --detach $PELICAN_DIR $newrev
fi

echo "Running pelican for $newrev"
pushd "$PELICAN_DIR" > /dev/null
tmpfile=`mktemp` && suppr="$suppr $tmpfile"
pelican &> $tmpfile
if grep -q -e '^ERROR' -e '^WARNING' $tmpfile
then
        cat $tmpfile
        exit 1
fi
# move the output to the nginx directory
mv /var/www/git/pelican.craoc.fr{,.old}
mv "$PELICAN_DIR/output" /var/www/git/pelican.craoc.fr
rm -rf /var/www/git/pelican.craoc.fr.old
popd > /dev/null

Les deux commandes qui illustre mes précédents propos sont :

git worktree add --detach $PELICAN_DIR $newrev
git -C $PELICAN_DIR checkout --detach -f $newrev

Pour que git ne m’avertisse plus de « DETACHED HEAD » à chaque push j’ai mis « detachedHead = false » dans la configuration du dépôt (source).

git@server % cd ~/repositories/mondepot.git
git@server % git config --local --bool advice.detachedHead false

Sources :