Mise en place d'un serveur et client Git

From Deimos.fr / Bloc Notes Informatique
Jump to: navigation, search

Contents

Git Logo

1 Introduction

Git est un logiciel de gestion de versions décentralisé. C'est un logiciel libre créé par Linus Torvalds, le créateur du noyau Linux, et distribué sous la GNU GPL version 2.

Comme BitKeeper, Git ne repose pas sur un serveur centralisé. C'est un outil bas niveau, qui se veut simple et très performant, dont la principale tâche est de gérer l'évolution du contenu d'une arborescence.

Git indexe les fichiers d'après leur somme de contrôle calculée avec la fonction SHA-1. Quand un fichier n'est pas modifié, la somme de contrôle ne change pas et le fichier n'est stocké qu'une seule fois. En revanche, si le fichier est modifié, les deux versions sont stockées sur le disque.

Git n'était pas, au départ, à proprement parler un logiciel de gestion de versions. Linus Torvalds expliquait que, « par bien des aspects, vous pouvez considérer git comme un système de fichiers : il permet un adressage associatif, et possède la notion de versionnage, mais surtout, je l'ai conçu en résolvant le problème du point de vue d'un spécialiste des systèmes de fichiers (mon métier, ce sont les noyaux !), et je n'avais absolument aucun intérêt à créer un système de gestion de version traditionnel. ». Il a aujourd'hui évolué pour intégrer toutes les fonctionalités d'un gestionnaire de versions.

Git est considéré comme performant, au point que certains autres logiciels de gestion de version (Darcs, Arch), qui n'utilisent pas de base de données, se sont montrés intéressés par le système de stockage des fichiers de Git pour leur propre fonctionnement. Ils continueraient toutefois à proposer des fonctionnalités plus évoluées.

2 Installation

Tout d'abord sur le serveur, nous allons installer Git :

Command aptitude
aptitude install git git-core

Si vous voulez rendre le serveur accessible via une adresse en git://, il faut également installer ceci :

Command aptitude
aptitude install git-daemon-run

3 Configuration

3.1 Serveur

Tout d'abord, nous allons créer le repository :

Command
$ mkdir myproject
$ cd myproject
$ git init
Initialized empty Git repository in .git/

Maintenant que notre projet est créer, nous allons lui ajouter un petit fichier histoire d'être sûr que tout fonctionne correctement :

Command touch
touch test

Puis nous allons l'ajouter, puis le commiter :

Command git
 $ git add .
 $ git commit -m "test"
 Created initial commit c491bd6: test
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 afile

Pour vérifier l'état à n'importe quel moment, vous allez effectuer un status :

Command git
git status

Ensuite, nous clonons ce repository :

Command
$ cd ..
$ git clone --bare myproject myproject.git
Initialized empty Git repository in /var/cache/git/myproject.git/
0 blocks
$ ls myproject.git
branches  config  description  HEAD  hooks  info  objects  refs

  • clone : permet de créer une copie du dépôt en local
  • --bare : copie ne contenant que les infos du dossier myproject
  • myproject.git : dossier que git nous a créer

3.1.1 Le protocole git

Si vous souhaitez rendre accessible git via son protocole (git://) vous devez configurer le fichier suivant comme ceci :

Configuration File /etc/sv/git-daemon/run
#!/bin/sh
exec 2>&1
echo 'git-daemon starting.'
exec chpst -ugitdaemon \
  "$(git --exec-path)"/git-daemon --verbose --base-path=/var/cache /var/cache/gi

Ensuite pour chacun de vos projets que vous voulez rendre accessible depuis l'extérieur, il faudra placer un fichier 'git-daemon-export-ok' :

Command touch
touch /var/cache/git/myproject.git/git-daemon-export-ok

Relancer le deamon et c'est maintenant accessible sur le port 9418.

3.2 Client

Vous devez avant de commencer installer également Git, comme effectué sur le serveur.

Ensuite avec votre utilisateur courant, informez lui que vous voulez commiter (assurez vous que votre utilisateur existe bien sur le serveur) :

Command git
git config --global user.email "deimos@deimos.fr"
git config --global user.name "deimosfr"

Ce qui devrait donner ceci dans votre ~/.gitconfig :

Configuration File ~/.gitconfig
[user]
       email = deimos@deimos.fr
       name = Deimos

Si vous souhaitez avoir des identifiants différents pour un repository différent, il faudra simplement retirer global (en vous plaçant dans le repository en question) :

Command git
git config user.email "deimos@deimos.fr"
git config user.name "Deimos"

Maintenant nous allons récupérer notre projet sur notre git. Nous utilisons ici la méthode avec ssh, mais git sait également fonctionner sur rsync, http, https et git-daemon.

3.2.1 Avec SSH

Voici la méthode avec SSH :

Command git
 $ git clone ssh://username@host/var/cache/git/myproject
 Initialized empty Git repository in /var/cache/git/myproject/.git/
 Password:
 remote: Counting objects: 1, done.
 remote: Total 1 (delta 0), reused 0 (delta 0)
 Receiving objects: 100% (1/1), done.
 $ ls
 myproject
 $ ls myproject
 test

  • Pour les mises à jour du dépôt, il va falloir utiliser l'option push. Sachez qu'avant, les modifications devront être effectuées en locale (c'est l'avantage de git), je parle donc d'un add et d'un commit par exemple (nous le verrons juste après) :
Command git
git push ssh://username@host/var/cache/git/myproject.git

ou

git push ssh://username@host/var/cache/git/myproject.git master

'master' ici correspond au nom de la branche qui nous intéresse (voir plus bas pour l'utilisation des branches).

  • Pour ajouter un fichier à un dépôt SSH :
Command git
git remote add test ssh://username@host/var/cache/git/myproject.git
git commit -a
git push test

A l'avenir, afin d'éviter de se prendre la tête avec la partie SSH et ses commandes spéciales, ajoutez ceci dans votre fichier ~/.gitconfig :

Command
[remote "myproject"]
      url = ssh://username@host/var/cache/git/myproject/

3.2.2 Avec Git-daemon

Git refusera de se synchroniser si le dossier en question ne contient pas un fichier appeller 'git-daemon-export-ok'. Une fois ce fichier créer, le dossier en question sera accessible par tout le monde :

Command git
git clone git://serveur.git/git/myproject

ou

git clone git://serveur.git/git/myproject.git

Nous pouvons utiliser les options suivantes :

  • '--export-all' : cette option n'oblige plus à créer le fichier 'git-daemon-export-ok'
  • '--user-path=gitexport' : cette option va permettre d'utiliser des URL sur les dossiers d'accueil des utilisateurs. Ainsi git://deimos-laptop/~deimos/myproject.git va pointer sur /home/deimos/gitexport/myproject.git

3.2.3 Sur HTTP (avec Nginx)

J'ai passé pas mal de temps à faire cohabiter Git over http(s) et Gitweb, mais ça y est ça fonctionne.

Notes Notes
Préférez la méthode Gitweb uniquement si vous n'avez pas besoin de de git over http(s)

Voici la méthode que j'ai utilisé :

Configuration File /etc/nginx/sites-available/git.deimos.fr
server {
    listen 80;
    listen 443 ssl;
 
    ssl_certificate /etc/nginx/ssl/deimos.fr/server-unified.crt;
    ssl_certificate_key /etc/nginx/ssl/deimos.fr/server.key;
    ssl_session_timeout 5m;
 
    server_name git.deimos.fr;
    root /usr/share/gitweb/;
 
    access_log /var/log/nginx/git.deimos.fr_access.log;
    error_log /var/log/nginx/git.deimos.fr_error.log;
 
    index gitweb.cgi;
 
    # Drop config
    include drop.conf;
 
    # Git over https
    location /git/ {
        alias /var/cache/git/;
        if ($scheme = http) {
            rewrite ^ https://$host$request_uri permanent;
        }
    } 
 
    # Gitweb
    location ~ gitweb\.cgi {
        fastcgi_cache mycache;
        fastcgi_cache_key $request_method$host$request_uri;
        fastcgi_cache_valid any 1h;
        include fastcgi_params;
        fastcgi_pass  unix:/run/fcgiwrap.socket;
    } 
}

Ici j'ai mon git over https qui fonctionne (le http est redirigé vers https) et mon gitweb également puisque tout ce qui est gitweb.cgi est matché. Maintenant, pour la partie git, nous allons devoir autoriser les repository que nous voulons. Pour celà, il va falloir renommer un fichier dans notre repository et lancer une commande :

Command
cd /var/cache/git/myrepo.git
hooks/post-update{.sample,}
su - www-data -c 'cd /var/cache/git/myrepo.git && /usr/lib/git-core/git-update-server-info'

Remplacez www-data par l'utilisateur qui a les droits sur le repository. Utilisez www-data pour que ce soit nginx qui ait les droits. Ensuite, vous avez les droits pour cloner :

Command git
git clone http://www.deimos.fr/git/deimosfr.git deimosfr

3.2.4 Configurer un proxy

Si vous devez passer à travers un proxy sur Git, vous pouvez le faire comme ceci (pour sa partie globale) :

Command git
git config --global http.proxy http://proxy:8080

Pour vérifier les paramètres actuels :

Command git
git config --get http.proxy

Si vous avez besoin de retirer cette configuration :

Command git
git config --global --unset http.proxy

4 Utilisation

4.1 Mettre à jour son dépôt local

Si vous souhaitez mettre à jour votre dépôt local par rapport au serveur, c'est simple :

Command git
git pull

ou

git pull ssh://username@host/var/cache/git/myproject/

4.2 Ajouter des fichiers

Pour ajouter des fichiers en masse, utilisez cette commande :

Command git
git add *

4.3 Commiter les changements

Si vous souhaitez mettre en même temps un log et commiter, le tout en une seule ligne (sinon enlevez -m...) :

Command git
git commit -a -m "Mon premier ajout"

Ceci va donc mettre à jour en local le repository (n'oubliez pas le push ensuite si vous travaillez avec un serveur distant).

4.4 Changer l'éditeur et le visualiseur par défaut

Vous pouvez forcer par exemple vim et less and entrant ces lignes :

Command git
git config --global core.editor vim
git config --global core.pager "less -FSRX"

4.5 Obtenir les logs

Vous pouvez consulter à n'importe quel moment les logs des anciens commits via cete commande :

Command git
git log

Si vous souhaitez obtenir les logs d'un fichier bien précis :

Command git
git log test

Si votre fichier a été renommé, utilisez l'option --follow pour vois également son nouveau nom.
Pour voir les logs des 2 derniers jours :

Command git
git log --since="2 days ago"

4.6 Faire une recherche

Il est possible de faire une recherche de contenu dans les fichiers comme ceci :

Command git
git grep deimos *

4.7 Ignorer des fichiers

Vous avez peut être envie de ne pas autoriser certains fichiers tel que les fichiers temporaire de vim par exemple. Il va vous falloir placer un fichier .gitignore qui sera à la racine de la copie de travail qui sera en général ajouté lui même au dépôt, soit dans le fichier .git/info/exclude. Voici un exemple :

Configuration File .gitignore
*~
*.o

4.8 S'assurer qu'il n'y a rien à commiter

Il est possible de s'assurer qu'il n'y a rien a commiter de cette façon là :

Command git
> git stash
No local changes to save

4.9 Configurer des alias

Il est possible de configurer des alias pour les commandes un peu longues. Par exemple si vous êtes habitué à SVN :

Command git
git config --global alias.co 'checkout'
git config --global alias.ci 'commit -a -m'

Ici 'git co' correpond à la commande 'git checkout'. Cette configuration (si elle est globale ou non) peut être située à 2 endroits :

  • ~/.gitconfig pour en bénéficier dans tous vos dépôts.
  • .git/config d’un projet pour restreindre son accès à cet unique projet.

4.10 Revert : annuler un commit

Git revert permet d'annuler le précédent commit en en introduisant un nouveau. Tout d'abord nous allons faire un git log pour récupérer le numéro du commit :

Command git
$ git log
commit : bfbfefa4d9116beff394ad29e2721b1ab6118df0

Puis nous allons faire un revert pour annuler l'ancien :

Command git
git revert bfbfefa4d9116beff394ad29e2721b1ab6118df0

En cas de conflit, il suffit de faire les modifications à la main, de refaire un add puis un commit.

Note : vous pouvez aussi utiliser les quelques premiers caractère de l'identifiant (SHA-1) du commit tel que bfbfe. Git fera automatiquement la completion (a la condition qu'un autre commit ne commence pas avec les mêmes caractères).

4.11 Reset : effacer tous les commits à partir d'un ancien

Git reset va supprimer complètement tous les commits effectués après une certaine version :

Command git
git reset bfbfefa4d9116beff394ad29e2721b1ab6118df0

Git reset permet de réécrire l'histoire. Vous pourrez vous en convaincre avec un 'git log'. Celà peut avoir de grave conséquences si vous travaillez à plusieurs sur un projet. Non seulement parce que les repository en locale des autres personnes vont devoir être resynchroniser, mais en plus parce qu'il peut y avoir beaucoup de conflits. Il est donc conseiller d'utiliser cette commande avec précaution.
Ensuite vous devez refaire un 'git add' de vos fichiers, puis un 'git commit'.

Si vous voulez que la copie de travail en local reflète le dépôt, ajoutez l'option --hard lors du reset :

Command git
git reset --hard bfbfefa4d9116beff394ad29e2721b1ab6118df0

Dans le cas ou vous êtes dans une salle journée et donc en mode "boulet", si vous avez supprimé des mois de taf et que vous voulez revenir sur vos pas, git a conservé l'état précédent, faites donc :

Command git
git reset --hard ORIG_HEAD

HEAD signifie le dernier commit par défaut.

Si vous avez un message du type :

! [rejected]        master -> master (non-fast forward)

Et que vou voulez vraiment forcer le commit pour qu'il ressemble exactement à ce que vous avez sur votre machine :

Command git
git push origin +master

4.12 Restaurer un fichier d'un ancien commit

Vous pouvez choisir de restaurer uniquement un fichier bien particulier d'un ancien commit via la commande checkout :

Command git
git checkout bfbfe test

4.13 Supprimer un fichier

Il existe 2 méthodes, ma préférée, car je la trouve simple :

Command git
git rm mon_fichier

La 2ème méthode consite à faire un rm standart, puis ua prochaine git commit -a, il détectera que ce fichier n'existe plus et le supprimera du dépôt.

4.14 Déplacement d'un fichier

Pour déplacer un fichier au sain de son dépôt git, il faut utiliser cette commande :

Command git
git mv test1 test2

4.15 Lister le contenu du dépôt

On peut utiliser cette commande pour lister le contenu d'un dépôt :

Command git
git ls-files

Note : les dossiers vide ne seront pas afficher

4.16 Créer une archive du dépôt

Pour faire une archive au format tar gzip, nous allons utiliser l'option archive :

Command git
git archive --format=tar --prefix=version_1/ HEAD | gzip > ../version_1.tgz

  • --prefix : permet de donner un dossier qui sera créer lors de la décompression.
  • HEAD : permet de spécifier le commit (ici le 1er)

4.17 Voir un fichier en particulier d'un commit

Il est possible de voir un fichier (README) d'un commit :

Command git
git show 291d2ee31feb4a318f77201dea941374aae279a5:README

ou voir tous les diffs d'un coup si on ne spécifie pas le fichier :

Command git
git show 291d2ee31feb4a318f77201dea941374aae279a5

4.18 Réécrire l'histoire

Si vous avez oublié de faire quelque chose à un précédent commit et que vous souhaitez revenir en arrière, il faut faire un rebase interactif en indiquant le commit le plus loin que l'on souhaite modifier:

Command git
git rebase -i cbde26ad^

In the default editor, modify 'pick' to 'edit' in the line whose commit you want to modify. Make your changes and then commit them with the same message you had before: Dans l'éditeur, modifiez 'pick' en 'edit' sur la ou les lignes que vous souhaitez modifier. Enregistrez et quittez. Faites tous vos changements et ensuite validez le commit:

Command git
git commit -a --amend
ou
git commit -a --amend --no-edit

Ensuite, pour passer au commit suivant:

Command git
git rebase --continue

Jusqu'à ce que ce soit terminé.

4.19 Faire des diff entre différentes versions

Il est très facile avec git de faire des diff entre différentes versions. Par exemple, de la version HEAD à 2 versions précédentes :

Command git
git diff HEAD~2 HEAD

4.20 Modifier le texte du dernier commit

Pour voir l'état du dernier log, nous pouvons faire :

Command git
git log -n 1

Pour modifier le texte de ce dernier log, nous allons utiliser l'option amend :

Command git
git commit -a --amend

Cela permet donc de changer juste le texte, il n'y aura pas de nouveau numéro commit. Seul le numéro d'identification changera.

4.21 Modifier la date du dernier commit

Il est possible de modifier la date du dernier commit:

Command git
GIT_COMMITTER_DATE="`date`" git commit --amend --date "`date`"

Ou sinon on peut spécifier la date en dur:

Command git
GIT_COMMITTER_DATE="Fri Nov 17 12:00:00 CET 2014" git commit --amend --date "Fri Nov 17 12:00:00 CET 2014"

4.22 Modifier l'autheur de commits

Lorsque l'on utilise plusieurs comptes Git sur une même machine, il arrive facilement de se tromper lorsque l'on clone un repository et que l'on ne met pas le bon username et adresse mail. Du coup lorsque l'on s'en rend compte, il est trop tard et il faut réécrire une partie d l'histoire pour remettre les bonnes informations:

Command git
> git filter-branch --env-filter '
oldname="old username"
oldemail="old email address"
newname="new username"
newemail="old username address"
[ "$GIT_AUTHOR_EMAIL"="$oldemail" ] && GIT_AUTHOR_EMAIL="$newemail"
[ "$GIT_COMMITTER_EMAIL"="$oldemail" ] && GIT_COMMITTER_EMAIL="$newemail"
[ "$GIT_AUTHOR_NAME"="$oldname" ] && GIT_AUTHOR_NAME="$newname"
[ "$GIT_COMMITTER_NAME"="$oldname" ] && GIT_COMMITTER_NAME="$newname"
' HEAD

4.23 Synchroniser qu'une partie d'un dépôt

Si vous souhaitez n'avoir qu'une partie d'un dépôt (également appelé sparse), vous allez devoir en premier lieu récupérer le dépôt dans son intégralité, puis lui spécifier que ce qui vous intéresse. Il ne restera alors que ce qui vous intéresse :

Command
git clone ssh://deimos@git/var/cache/git/git_deimosfr .
git config core.sparsecheckout true
echo configs/puppet/ > .git/info/sparse-checkout
git read-tree -m -u HEAD

Ici dans le dépôt git_deimosfr, seul le dossier "configs/puppet/" ne m'intéresse. Vous pouvez ajoutez plusieurs dossiers dans le fichier ".git/info/sparse-checkout" ligne par ligne si vous souhaitez conserver plusieurs fichiers.

4.24 Utiliser un repository git externe dans un git

Si par exemple vous avez déjà un Git dans lequel vous voulez intégrer un autre Git, mais externe, il vous faudra utiliser les submodules. Pour ma part, j'ai un dossier puppet avec tous pleins de modules dedans. Une partie de ces modules n'a pas été créer par moi même et est maintenu par d'autres personnes ayant un Git. Je souhaite faire pointer certains modules vers les git externe. Voici comment précéder :

  • J'ajoute la source externe à ce git :
Command git
> git submodule add git://git.black.co.at/module-common configs/puppet/modules/common
Cloning into configs/puppet/modules/common...
remote: Counting objects: 423, done.
remote: Compressing objects: 100% (298/298), done.
remote: Total 423 (delta 155), reused 203 (delta 70)
Receiving objects: 100% (423/423), 53.96 KiB, done.
Resolving deltas: 100% (155/155), done.

Il ne vous reste plus qu'a commiter et push si vous voulez rendre cette configuration valable sur le serveur.

  • Maintenant prenons le cas d'un client qui fait un pull. Il va récupérer toute votre arborescence, mais pas les liens externes, pour cela, il devra exécuter les commandes suivantes :
Command git
> git submodule init
Submodule 'configs/puppet/modules/common' (git://git.black.co.at/module-common) registered for path 'configs/puppet/modules/common'
> git submodule update
Cloning into configs/puppet/modules/common...
remote: Counting objects: 423, done.
remote: Compressing objects: 100% (298/298), done.
remote: Total 423 (delta 155), reused 203 (delta 70)
Receiving objects: 100% (423/423), 53.96 KiB, done.
Resolving deltas: 100% (155/155), done.
Submodule path 'configs/puppet/modules/common': checked out 'ba28f3004d402c250ef3099f95a1ae13740b009f'

Si lors d'un clone, vous souhaitez que les submodules soient également prit avec, vous devez rajouter l'option "--recursive". Exemple :

Command git
git clone --recursive git://git.deimos.fr/git/git_deimosfr

4.25 Les branches

4.25.1 Création d'une branche

Si par exemple, la version actuelle que vous avez sur git vous convient et que vous souhaitez la passer en stable, il vous faut utiliser les branches et alors créer une nouvelle branche en tant que branche de développement :

Command git
git branch devel

4.25.2 Envoyer une branche au serveur

Pour envoyer une branche au serveur :

Command
git push origin <ma_branche>

4.25.3 Lister des branches

Maintenant, la branche devel est créer, pour le vérifier :

Command git
$ git branch -a
  devel
* master

L''*' signifie la branche courante (en cours d'utilisation).

Lorsque vous utilisez des branches distantes, vous pouvez les lister via cette option :

Command git
$ git branch -r
  devel
* master

4.25.4 Changement de branche

Pour changer de branche, il suffit d'utiliser l'option checkout :

Command git
$ git checkout devel
Switched to branch "devel"

Vérifions :

Command git
$ git branch
* devel
  master

Si l'on veut repasser sur la branche précédente, nous pouvons à n'importe quel moment faire :

Command git
$ git checkout -f master
$ git branch
* master
  devel

L'option -f correspond à l'option --hard du reset qui permet de synchroniser en local les changements effectués sur le dépôt.

Si vous travaillez sur une branche distante, faites comme ceci par exemple :

Command git
$ git checkout deimos/myproject -b master
$ git branch
* master
  devel

4.25.5 Récupérer une branche depuis un serveur distant

Une fois que votre "pull" est fait, changez de branche comme ceci (vous pouvez la nommer différemment si vous le souhaitez) :

Command git
git checkout -b <new_branch> origin/<new_remote_branch>

  • new_branch : le nom de ma nouvelle branche locale
  • new_remote_branch : le nom de la nouvelle branche distante (côté serveur)

4.25.6 Supprimer une branche

Pour supprimer une branche, rien de plus simple :

Command git
git branch -d devel

Si celà fonctionne parfait, si vous souhaitez forcer en cas de problème, utiliser '-D' à la place de '-d'. Pour appliquer les changements sur le serveur remote :

Command git
git push origin :devel

Ceci supprimera la branche devel sur le serveur distant.

4.25.7 Merger des branches

L'option merge consiste à fusionner 2 branches complètement. Comme par exemple faire fusionner une branche devel avec une stable pour que la devel devienne stable :

Command git
git checkout master
git merge devel

4.25.8 Rebase : Appliquer un patch sur plusieurs branches

Imaginons que nous avons travailler sur la branche devel mais qu'un bug en master a été découvert. Il est normal que si le master bénéficie du fix, la branche devel également. Nous allons donc nous placer dans la branche master, appliquer le patch, puis merger avec la branche devel le patch. On se place donc sur la branche devel, puis on exécute ces commandes :

Command git
git checkout devel
git rebase master

Il est possible de le faire de façon interactive grâce à l’argument -i :

Command
git rebase -i master

4.25.9 Créer une branche lors d'une restauration

Vous pouvez créer une branche lors d'une restauration d'un fichier en vous plaçant préalablement dans la branche et en utilisant l'option -b :

Command git
$ git checkout bfbfefa4d9116beff394ad29e2721b1ab6118df0 -b version_2
Switched to a new branch "version_2"

4.26 Les Tags

Les tags sont très pratiques pour marquer une version bien spécifique. Par exemple, pour releaser une version 1.0, il est possible de créer un tag et de s'en resservir par la suite pour télécharger cette version particulière. On peut tagger ce que l'on veut, ça permet de se repérer facilement.

4.26.1 Créer un Tag

Pour créer un tag, c'est facile :

Command git
git tag '1.0'

Notes Notes
Il est possible de signer les tag via GPG

4.26.2 Lister les tags

Pour lister les tags disponibles :

Command git
git tag

4.26.3 Supprimer un tag

Pour supprimer un tag:

Command git
git tag -d v0.1
git push origin :refs/tags/v0.1

4.26.4 Envoyer les tags sur le serveur

Une fois les tags créés en local, il est possible de les pousser sur le serveur:

Command
git push --tags

4.27 Gérer les conflits

Il est possible de gérer les conflits de façon interactive :

Command git
git mergetool

4.28 Résolution automatique de conflits déjà vu

Il est possible de demander à git de résoudre automatiquement des problèmes sur lesquels il serait déjà tombé :

Command git
git config --global rerere.enabled 1

4.29 Utiliser git pour trouver un bug

Imaginons qu’un bug sournois surgisse soudainement et vienne perturber la bonne marche de votre projet. Après quelques investigations dignes d’un fumeur de pipe anglais, vous découvrez que le vil dysfonctionnement n’est apparu que récemment. Il s’agit bien là d’une régression !

Certes, vous pourriez perdre du temps à corriger cette régression de la manière classique, en plongeant dans le code poussiéreux et plein de toiles d’araignées virtuelles, mais il serait bien plus simple de découvrir exactement à quelle moment elle est apparue, quel est le commit correspondant, pour avoir une idée très précise de sa cause.

C’est exactement ce que permet de faire git-bisect, d’une manière particulièrement intuitive. Indiquez lui un commit présentant le dysfonctionnement (HEAD), et un commit passé, n’importe lequel, qui ne la présente pas.

Git vous place alors dans votre historique de développement, pile au milieu entre les deux commit. Après vérification, vous indiquez à git si la régression apparaît ou pas. Et on recommence. Chaque fois, on divise par 2 l’intervalle des commits ayant potentiellement introduit le bug, jusqu’à débusquer le fautif. Génial, non ?

Passons à la pratique :

Command
> git bisect start
> git bisect bad <mauvaise_version>
> git bisect good <bonne_version>
 
Bisecting: 8 revisions left to test after this

8 commits ont potentiellement introduit la régression. On indique si le commit actuel est correct ou pas :

Command git
> git bisect good # ou git bisect bad
Bisecting : 4 revisions left to test after this

Et on continue, encore et encore, jusqu’à ce qu’on se trouve le mauvais commit à l’origine de ce temps perdu (mais ça aurait pu être pire) :

37b4745bf75e44638b9fe796c6dc97c1fa349e8e is first bad commit

A tout moment, vous pouvez obtenir un historique de votre parcours :

Command git
git bisect log

Si a un moment précis, vous souhaitez ne pas tester un commit en particulier, pour n’importe quelle raison, utilisez la commande :

Command git
git bisect skip

Tout ceci est bel et bon, mais il y a mieux (non ?! et si !). Supposions que vous soyez un fan de TDD. Vous disposez sûrement d’un script qui teste automatiquement si le code actuel est bon ou pas. Il est alors possible d’automatiser la recherche :

Command git
git bisect start HEAD <bad_commit> --
git bisect run script

Allez prendre un café, git travaille pour vous. Note : le script doit renvoyer 0 si le code est correct, 1 s’il est incorrect, et 125 s’il est intestable (git skip). Pendant la bisection, git créé une branche spéciale dédiée. N’oubliez pas de repasser sur votre branche de développement quand vous aurez débusqué la régression. Il est possible de le faire simplement en tapant :

Command git
git bisect reset

Voilà, avec tout ça, fini les fastidieuses recherches de régression.[1]

4.30 Connaître ligne par ligne les commits

Ceci est la solution de taper sur les doigts d'une personne qui a réaliser un commit. Entre autre, vous avez la possibilité de connaître ligne par ligne qui a écrit quoi :

Command git
> git blame <fichier>
^a35889c (Deimos 2010-04-20 17:36:29 +0200   1) <?php
^a35889c (Deimos 2010-04-20 17:36:29 +0200   2) class DeleteHistory extends SpecialPage
^a35889c (Deimos 2010-04-20 17:36:29 +0200   3) {
724f724d (Deimos 2011-06-14 23:15:40 +0200   4) 	function __construct()
724f724d (Deimos 2011-06-14 23:15:40 +0200   5) 	{
724f724d (Deimos 2011-06-14 23:15:40 +0200   6) 		// Need to belong to Administor group
724f724d (Deimos 2011-06-14 23:15:40 +0200   7) 		parent::__construct( 'DeleteHistory', 'editinterface' );
724f724d (Deimos 2011-06-14 23:15:40 +0200   8) 		wfLoadExtensionMessages('DeleteHistory');

4.31 Déplacer un dossier et son historique vers un autre repository

On peut parfois avoir besoin de recréer un autre repository vierge pour contenir le dossier d'un autre repository. Ca a été mon cas pour l'extension DeleteHistory de Mediawiki qui j'ai créer. Au début, j'avais un repository appelé 'mediawiki_extensions' ou j'avais 2 extensions dedans. Et puis avec le temps et les version de Mediawiki qui avançaient, il était préférable de faire une séparation par plugins tout en gardant l'historique. Je vais donc vous expliquer comment j'ai fais (j'ai suivit cette documentation[2]).

Nous allons travailler en local avec mon ancien repository que nous allons appeler oldrepo et mon nouveau repository newrepo (quelle originalité).

Warning WARNING
Faites un repository temporaire si vous disposez déjà de celui ci car il sera supprimé à la fin

Nous allons donc cloner ce repo :

Command git
git clone git://git.deimos.fr/oldrepo.git
git clone git://git.deimos.fr/newrepo.git

Puis nous allons filtrer sur le dossier qui nous intéresse, appelons le folder2keep :

Command git
git filter-branch --subdirectory-filter folder2keep -- -- all

Là, tous les fichiers et dossiers de folder2keep sont à la racine de notre repository. Si vous le souhaitez, vous pouvez créer un dossier et tout placer dedans, mais ceci n'est pas obligatoire :

Command
mkdir new_directory/
git mv * new_directory/

Remplacez * par tous vos éléments que vous souhaitez placer dedans et commitez :

Command git
git commit -m "Collected the data I need to move"

Maintenant, nous allons aller dans notre fameux dossier et faire une référence locale entre les 2 :

Command
cd ../newrepo/
git remote add oldrepo ../oldrepo/

Ensuite nous allons rappatrier les sources, créer la branche principale et merger le tout :

Command
git fetch oldrepo
git branch oldrepo remotes/oldrepo/master
git merge oldrepo

Maintenant nous allons transférer l'historique et faire un peu de ménage :

Command git
git remote rm oldrepo
git branch -d oldrepo
git push origin master

Et voilà, le repository temporaire (vieux) peut être maintenant supprimé.

4.32 Hooks

Il existe des hooks dans Git permettant de faire des pre ou post traitement. Voici un use case permettant d'effectuer une action (lancer une commande via ssh) lorsqu'un tag arrive sur le serveur. L'idée est de pouvoir en fonction d'un tag (exemple: prod ou preprod) déployer une nouvelle version d'un soft sur X serveurs. Voici la liste des options qui vont être nécessaire:

  1. Créer un hook dans le repository
  2. Créer un utilisateur dédié si vous utilisez Gitlab/GitHub/Gitolite
  3. Générer une clef privée SSH avec l'utilisateur 'git'
  4. Copier via ssh-copy-id vers les serveurs distant la clef de l'utilisateur 'git' (généralement www-data)
  5. Créer un script de déploiement et mettre les bons droits
  6. Cloner le repository sur tous les serveurs nécessaire
Configuration File repo.git/hooks/post-receive
  1. #!/bin/bash                                                                                                                                                                                                                                                                    
  2.  
  3. # Create your environment with DNS/IP servers separated by spaces
  4. name=( list array )
  5. prod=( X.X.X.X Y.Y.Y.Y )
  6. preprod=( Z.Z.Z.Z )
  7.  
  8. # Folder to deploy on client side
  9. folder='/var/www/cloned_repository'
  10.  
  11. # Set SSH remote username
  12. ssh_username='www-data'
  13.  
  14. # Log file
  15. logfile='/var/log/git-deploy-hook.log'
  16.  
  17. ########################################################################
  18. # get infos
  19. read oldrev newrev refname
  20.  
  21. # vars
  22. tag=`echo $refname | cut -d/ -f3`
  23. environment=`echo $tag | cut -d- -f1`
  24.  
  25. # function to call distant git deployments
  26. deploy_new_version() {
  27.     declare -a servers=("${!1}")
  28.  
  29.     echo $(date +'%Y/%m/%d - %H-%M-%S ') "Begin deployment hook for $environment env" >> $logfile
  30.     for server in $(seq 0 $((${#servers[@]} - 1))) ; do
  31.         echo "Deploying $tag" >> $logfile
  32.         ssh $ssh_username@${servers[$server]} /usr/bin/git_deploy.sh $tag $folder 2>&1 >> $logfile
  33.     done
  34.     echo $(date +'%Y/%m/%d - %H-%M-%S ') "Deployment hook finished for $environment env" >> $logfile
  35. }
  36.  
  37. # check if a tag has been pushed
  38. if [[ -n $(echo $refname | grep -Eo "^refs/tags/$environment") ]]; then
  39.     echo "Git hook is called for $environment environment"
  40.     if [ ${!environment[0]} ] ; then
  41.         # Deploy the correct environment
  42.         deploy_new_version $environment[@]
  43.     else
  44.         echo "Error, $environment is not recognized as an existing environment"
  45.         exit 1
  46.     fi
  47. fi

Vous devez modifier les environnement avec ceux que vous voulez pour que quand un tag arrivera avec le nom du tag en question, il y ai une action qui soit déclenchée derrière. Par exemple pour déployer sur les serveurs de prod, il faudra mettre un tag de type "prod-v1.0".

Switchez avec l'utilisateur git (ou celui qui a les droits) et copiez la clé publique vers tous les serveurs sur lesquelles vont devoir agir les tags:

Command
su - git
ssh-copy-id www-data@x.x.x.x

Créez ensuite le script de déploiement:

Configuration File /usr/bin/git_deploy.sh
#!/bin/bash
logfile='/var/log/git-deploy-hook.log'
echo $(date +'%Y/%m/%d - %H-%M: ') 'Begin deployment hook' >>$logfile
cd $2
git fetch origin -v >> $logfile 2>&1
git fetch origin -v --tags >> $logfile 2>&1
git stash drop >> $logfile 2>&1
git checkout -f tags/$1 >> $logfile 2>&1
echo $(date +'%Y/%m/%d - %H-%M: ') 'Deployment finished' >> $logfile

Vous pouvez rajouter toutes les commandes que vous voulez. Il faut bien entendu copier ce script sur tous les serveurs cible et lui donner les droits d'exécution.

Créez ensuite le fichier de logs et mettez les bons droits:

Command
touch /var/log/git-deploy-hook.log
chow www-data. /var/log/git-deploy-hook.log
chmod 755 /usr/bin/git_deploy.sh

Clonez ensuite les repository avec les utilisateurs finaux:

Command
cd /var/www
git clone git@gitlab/cloned_repository.git
chown -Rf www-data. /var/www/cloned_repository

Et voilà, il ne reste plus qu'à pusher un tag sur un commit:

Command
git tag -a prod-v1.0 -m 'production version 1.0' 9fceb02
git push --tags

5 Utilitaires

5.1 Gitk

Gitk permet d'avoir une version graphique de l'état de votre git (breanches etc...).

Gittk.jpg

6 FAQ

6.1 warning: You did not specify any refspecs to push, and the current remote

Si vous avez ce genre de message quand vous faites un push :

warning: You did not specify any refspecs to push, and the current remote
warning: has not configured any push refspecs. The default action in this
warning: case is to push all matching refspecs, that is, all branches
warning: that exist both locally and remotely will be updated.  This may
warning: not necessarily be what you want to happen.
warning: 
warning: You can specify what action you want to take in this case, and
warning: avoid seeing this message again, by configuring 'push.default' to:
warning:   'nothing'  : Do not push anything
warning:   'matching' : Push all matching branches (default)
warning:   'tracking' : Push the current branch to whatever it is tracking
warning:   'current'  : Push the current branch

Lancez simplement cette commande en mettant ce qui vous corresponds le plus :

Command git
git config --global push.default matching

7 Références

  1. ^ http://www.miximum.fr/methodes-et-outils/79-debusquer-une-regression-avec-git-bisect
  2. ^ http://st-on-it.blogspot.fr/2010/01/how-to-move-folders-between-git.html

http://www.kernel.org/pub/software/scm/git/docs/user-manual.html
http://git.wiki.kernel.org/index.php/GitFaq
http://alexgirard.com/git-book/index.html
How To Install A Public Git Repository On A Debian Server
Git it
http://www.unixgarden.com/index.php/administration-systeme/git-les-mains-dans-le-cambouis
http://stackoverflow.com/questions/1811730/how-do-you-work-with-a-git-repository-within-another-repository
http://chrisjean.com/2009/04/20/git-submodules-adding-using-removing-and-updating/