Git est un système de contrôle de version. Assez proche de SVN par certains côtés, il présente néanmoins un grand nombre de différences.
Avant de découvrir les spécificités de Git, il est nécessaire de connaître les concepts généraux des systèmes de contrôle de version.
Cette page donne un bref aperçu des fonctionnalités de Git. Un autre bon aperçu est disponible ici (lien non BR)
Alors que CVS et SVN sont des systèmes de versionnement centralisés (i.e un serveur contient le dépôt, et chaque utilisateur effectue un checkout depuis ce dépôt), Git est complètement décentralisé. Cela signifie que chaque contributeur a sa propre copie locale de tout l'historique d'un projet. Si aujourd'hui vous considérez que l'espace disque sur votre ordinateur est un problème, allez faire un tour, partez admirer les merveilles de la technologies qui nous entoure, et revenez demain. Sur cette page, tous les examples sont tirés du Git de Frankiz, mais voici d'autres dépôts bien utiles :
Pour télécharger un projet existant, il y a deux possibilités : avec un compte de développeur ou sans compte. Avec un compte SSH simple, il suffit d'exécuter la commande suivante :
git clone mybeautifulname@git.frankiz.net:/hosting/git/frankiz
Sans compte, certains projets sont téléchargeables avec le protocole HTTP, mais vous ne pourrez pas uploader vos modifications.
git clone http://git.frankiz.net/frankiz
Cela crée un clone du dépôt d'origine (i.e une copie conforme), qui peut d'ailleurs servir de backup si le dépôt d'origine était perdu. Notre clone contient tout l'historique du dépôt depuis sa création, toutes les branches, etc. Cette première opération est très souple. On peut cloner des dépôt via SSH, HTTP, GIT, ou même en local le dépôt d'un autre utilisateur…
Par exemple la commande suivante clone dans le dossier platal la branche core/master d'une version de Plat/al potientiellement modifiée par le vice-prez 2010 :
git clone /home/2010/fishilico/dev/platal/.git platal -b core/master
Avant toute chose, il est essentiel de savoir où trouver de l'aide, d'autant plus que l'aide de Git est très bien faite.
Ceci est la question que vous devez vous posez avant de reprendre après une pause votre travail sur un projet versionné. Il peut y avoir eu des changements sur le projet entre temps, des patchs mal faits en production, … Pour cela, il y a trois commandes essentielles :
git status # On branch master nothing to commit (working directory clean)
git status # On branch master # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: un-fichier-random.blah # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # un-fichier-chombier-ajouté.42 no changes added to commit (use "git add" and/or "git commit -a")
git log commit 2a424aae1e746fb5f33de3b3bc3e8a31ee25c684 Author: Nicolas Iooss <fishilico@eleves.polytechnique.fr> Date: Fri Mar 9 00:27:35 2012 +0100
git diff
De plus, après avoir téléchargé les derniers commits avec git fetch, il est possible d'obtenir le message suivant :
git status # On branch master # Your branch is behind 'origin/master' by 42 commits, and can be fast-forwarded.
Cela signifie que la copie locale a 42 commits de retard sur le dépôt distant, mais qu'un git rebase origin/ master suffit pour mettre à jour la copie locale.
Pour restaurer un fichier, il suffit de le checkout à partir de l'index. En français, cela signifie que lorsque vous faites une modification sur un fichier, vous pouvez effacer les modifications et revenir la dernière version enregistrée dans l'index Git avec la commande :
git checkout -- chemin/vers/mon/fichier.blih
Pour réinitialiser l'index dans l'état indiqué par une branche (comme master par exemple) sans modifier les fichier, il suffit d'écrire :
git reset master
Si cela ne marche pas car des fichiers seront modifiés ou effacés, on peut forcer la main à Git, mais alors il faut être prêt à subir les conséqences que cela implique (perte des modifications non commitées)
git reset --hard master
Dans le cas général, vous travaillez sur la branche master et vous synchronisez votre projet sur un seul serveur. Si votre cas est plus compliqué, vous devez être suffisamment compétent pour vous débrouillez par vous-même. On disait donc… dans le cas simple, Git stocke en interne deux branches : master et origin/master. Sur Frankiz, c'est un peu plus compliqué car il y a une branche de production, mais les développeurs n'utilisent que master.
git branch -a
* master
remotes/origin/HEAD -> origin/master remotes/origin/f579e1971a66623b2948bf0e20c2e23481022b41 remotes/origin/fkz2 remotes/origin/kangz-h4ck3s remotes/origin/master remotes/origin/prod
Pour mettre à jour sa copie de travail, il faut donc commencer par mettre à jour les branches distantes, avec au choix l'une des commandes suivantes
git fetch
git fetch origin
Ensuite, il faut appliquer les éventuelles modifications locales à la suite de celles effectuées sur le dépôt, c'est le rebase.
git rebase origin/master
Git essaiera alors d'appliquer les commits locaux successivement, en indiquant chaque fois qu'il y a un problème (conflit ou fichier supprimé). En cas de problème, il faut
Ceci permet de transformer l'état suivant (les O sont les commits, A le moment où les deux versions ont commencé à diverger, B la version du dépôt, E la version locale, C et D des commits locaux)
-C--D--E / -O--A--O--B
En l'état plus propre :
-O--A--O--B--C'--D'--E'
Avec C, D et E convertis en C', D' et E' : ils ont été modifiés pour être des modifications relatives à B au lieu de A.
Certains développeurs utilisent la commande git pull pour mettre à jour leur dépôt. Cette pratique est justifiée lorsque la copie de travail était propre (sans commit en attente de push entre autres), mais sinon, cela peut avoir des conséquences très graves et sales. En effet, git pull est assimilé à la succession de commandes suivantes :
git fetch origin && git merge origin/master
Le problème est le merge. En reprenant les schémas précédents, cela transforme l'état suivant :
-C--D--E / -O--A--O--B
en :
-C--D--E / -O--A--O--B----M
où M est le commit de fusion de branches. Cela est très moche et à éviter absolument. Si vous vous retrouvez dans cette suituation (pour vérifier : git log), vous pouvez toujours tenter un git rebase origin/master qui peut fonctionner si le merge était trivial (sans conflit). Sinon, un git reset –hard origin/master s'impose pour assurer votre salut.
Avant de faire un merge particulièrement délicat, ou si l'on doit résoudre un bug alors que l'on a pas mal de modifications non commitées en cours, il est parfois utile de sauvegarder l'état courant de la copie de travail.
Pour cela, il faut utiliser avant un changement (avec git rebase par exemple) :
git stash
Une fois les modifications effectuées, on peut restorer la copie locale à l'état sauvegardé avec :
git stash pop
Un commit est un ensemble de fichiers modifiés. Cela permet plus de cohérence dans le suivi des versions. Par exemple, si l'ajout d'une fonctionnalité sur Frankiz modifie les fichiers blah, blih et bloh, le commit regroupe les modifications de ces trois fichiers, et associe à cette modification une date et un auteur.
Pour effectuer un commit, il faut :
git status git diff
git add path/to/blih path/to/blah path/to/bloh
git status
git commit -s
git fetch git rebase origin/master
git push
La dernière commande provient du fait que git n'est pas centralisé : il n'envoie pas les modifications locales au dépôt parent spontanément.
Ceci permet de faire quelques modifications locales puis de n'envoyer cela au reste des développeurs que lorsque les modifications sont stables et complètes, par exemple.
On peut voir la liste des branches accessibles avec :
git branch -a
Pour récupérer une autre branche, il faut d'abord l'ajouter localement :
git checkout -b NomLocal origin/NomDistant
Cela aura également pour effet de basculer sur la branche NomLocal.
Les modifications que l'on avait effectuées sont bien entendu conservées.
Une fois la branche ajoutée, on peut basculer de branche en branche à l'aide de :
git checkout NomLocal
Contrairement à SVN où il faut installer un serveur subversion, créer un dépôt Git est très rapide : dans le dossier que l'on souhaite versionner, il suffit de taper
git init
Et voilà, le dossier est versionné : on peut donc commencer à faire des modifications, des commits, etc. (bien entendu, on ne peut pas pusher ses commits, puisqu'il n'y a pas de serveur distant).
Cette particularité rend Git très efficace pour versionner un petit projet, un exercice de TD, etc.
Pour créer un dépôt qui peut être cloné, il faut exécuter les commandes suivantes :
git init --bare mon_projet cd mon_projet && git update-server-info
Enfin, pour configurer un serveur HTTP (gitweb), il ne faut pas oublier de créer un fichier vide git-daemon-export-ok dans le dossier, afin de permettre le clonage.
Git stocke les commits de manière très particulière : un commit correspond à peu près à une sauvegarde de l'état des fichiers, à un message, et à une référence vers le commit parent.
Contrairemant à svn, il n'y a donc pas de notion de numéro de révision : chaque commit est identifié par une chaîne hexadécimale de 40 caractères (par exemple : 04fd02e59b1bdc430c7a7dcc1ca9f4cbc2b04037 )
Un tag est en fait un nom «lisible» donné à un commit ; une branche indique également le commit principal d'une branche.
Dans un projet, il est inutile de versionner les fichiers temporaires et les fichiers compilés (qu'ils soient binaires ou non). On utilise généralement un Makefile pour créer ces fichiers. Pour éviter de les versionner, il suffit d'utiliser un fichier .gitignore bien placé. Généralement, chaque projet a un tel fichier dans son dossier principal. Voici par exemple un extrait du gitignore de Frankiz :
configs/frankiz.conf htdocs/.htaccess htdocs/data/* htdocs/css/* spool/* upgrade/2.0.0_to_3.0.0/unversionned
Si vous avez déjà commitez, vous aurez certainement remarqué qu'il faut utiliser git config pour configurer vos informations personnelles. Ainsi, les deux premières commandes que tout utilisateur doit taper avant de faire un commit sont :
git config --global user.name "Prénom Nom de famille" git config --global user.email "prenom.nom.promo@polytechnique.org"
Ensuite, si vous aimez les couleurs, vous avez envie de taper cette commande :
git config --global color.ui auto
Enfin, si vous en avez assez des projets qui ne mettent pas les fichiers temporaires dans le gitignore, vous pouvez configurer un fichier gitignore global. Si votre /home/user/.gitignore_global ressemble à
*~ *.swp *.tmp
il suffit de taper :
git config --global core.excludesfile /home/user/.gitignore_global
Tout cela permet de gérer la configuration globale, visible par
git config --global -l
En enlevant –global dans toutes les commandes précédentes, on configure la copie locale.
Voici un exemple de fichier ~/.gitconfig (qui stocke la configuration –global, le même format peut être utilisé dans les fichier $REPO/.git/config de chaque dépot) :
[[gc]] auto = 1 [[color]] ui = true [[color|"diff"]] whitespace = red reverse [[core]] whitespace=fix,-indent-with-non-tab,trailing-space,cr-at-eol [[alias]] st = status ci = commit br = branch co = checkout df = diff dc = diff --cached lg = log -p lo = log --graph --decorate --pretty=oneline --abbrev-commit -n 10 lol = log --graph --decorate --pretty=oneline --abbrev-commit lola = log --graph --decorate --pretty=oneline --abbrev-commit --all ls = ls-files # Show files ignored by git: ign = ls-files -o -i --exclude-standard amend = commit --amend -C HEAD [[branch]] autosetuprebase = always
Pour utiliser Git sur Windows, on peut par exemple télécharger MSys Git : http://msysgit.github.com/.