Git dans la pratique (1/2)

Nous avons déjà parlé de Git sur ce blog, sur la notion de DVCS, sur son utilisation pour réaliser un build incassable, et sur ces formidables outils de merge que sont les DVCS. Mais qu’en est-il des « Git va vous sauver la vie », « Git c’est trop cool, comment je faisais avant ? » ou des « Git c’est trop compliqué, j’comprends rien, pourquoi on n’utilise pas Subversion ? ». Qu’en est-il de l’utilisation de Git dans un projet ou Subversion aurait pu « faire l’affaire » (ie. un dépôt centralisé, une seule équipe de développeurs dans un même bureau) ?

Après quelques mois d’utilisation de Git dans un projet avec une dizaine de développeurs, la plupart ne connaissant Git que de nom et ayant tous une bonne connaissance de Subversion, chacune des citations précédentes deviennent plus claires :

  • Oui, Git c’est compliqué si l’on veut profiter de tout son potentiel et même parfois pour faire ce que l’on faisait avec d’autres outils de gestion de version,
  • Et oui, Git offre des fonctionnalités et une flexibilité qui peuvent améliorer votre quotidien de développeur.

Oui, Git c’est compliqué

Lorsque l’on n’a pas pris/eu le temps d’apprendre à se servir de Git et de comprendre son fonctionnement, il faut avouer que soit on n’en demande pas trop et on ne trouve pas Git plus compliqué qu’un Subversion (ie. git commit/push/pull c’est comme svn commit/update et ça me suffit), soit on avait une utilisation avancée de Subversion et on veut vraiment savoir ce qu’il est possible de faire avec Git et à ce moment là, ça se corse.

Si vous voulez vraiment utiliser Git au-delà du simple commit/push/pull, comprenez d’abord ce qu’est l’index et la différence entre une branche distante et locale. Dans la suite de l’article, lorsqu’on évoquera le dépôt Git, il s’agira de votre dépôt local sauf si spécifié autrement.

L’index

L’index est une zone qui permet de préparer un commit. En effet, un commit n’est créé qu’à partir des modifications ajoutées à l’index. C’est pour cela qu’il faut faire un git add (je préfère git stage qui est un alias à git add que je trouve plus parlant) des fichiers avant d’effectuer un commit.
Un fichier a potentiellement 4 états différents (sans même parler de branches et autres fonctionnalités telles que git stash que l’on abordera dans la seconde partie de l’article) dans un même dépôt Git :

  • son contenu sur votre copie de travail (on parle de la même copie de travail que celle de Subversion),
  • son contenu sur l’index,
  • son contenu sur la branche actuelle sur le dépôt local,
  • son contenu sur la branche actuelle sur le dépôt d’origine.

Un git stage monfichier met à jour l’état de monfichier sur l’index à partir de l’état de ce fichier dans la copie de travail. Attention toutefois à bien comprendre que git stage ajoute les modifications à l’index au moment où la commande est exécutée. Cela veut dire que si monfichier est modifié après un git stage, ces modifications ne seront pas présentes sur l’index et ne seront donc pas commitées lors du prochain git commit. Ceci nous permet de contrôler finement les modifications que l’on souhaite inclure dans le prochain commit (pour aller plus loin, je vous conseille de tester git stage --interactive : Interactive adding).

Pour retirer les modifications de l’index git unstage monfichier fera l’affaire (unstage n’est pas une vraie commande Git, mais seulement un alias. La liste complète d’alias utilisés est disponible à la fin de l’article).

Un point sur git reset

La commande reset permet, entre autre, de « remettre en état » l’index et/ou la copie de travail. Nous l’utilisons principalement pour 3 choses :

  • un git reset HEAD supprimera toutes les modifications ajoutées à l’index sans toucher à la copie de travail. C’est l’opération inverse d’un git stage. HEAD étant la référence sur le commit sur lequel on se trouve actuellement (et que l’on change avec git checkout).
  • Avec l’option --hard en plus, toutes les modifications de la copie de travail seront elles aussi supprimées : la copie de travail, l’index et la branche en cours seront dans le même état (ie. ce qui est l’équivalent du svn revert de Subversion).
  • En utilisant une autre valeur que HEAD, cela permet justement de déplacer la HEAD de la branche en cours. Par exemple, pour revenir 3 commits en arrière et donc « oublier » les 3 derniers :
    git reset --hard HEAD~3

Pour résumer la commande reset :

  • l’option --soft ne modifie que la HEAD,
  • l’option --mixed (qui est l’option par défaut) modifie la HEAD et l’index,
  • et enfin, l’option --hard modifie la HEAD, l’index et la copie de travail (c’est l’équivalent d’un svn revert dans Subversion).

git reset

Enfin, pour bien suivre les modifications qui sont sur l’index et celles qui n’y sont pas :

  • git diff crée un diff entre la copie de travail et l’index,
  • et git diff --cached crée un diff entre l’index et le dernier commit de la branche actuelle

Git index

Les branches locales et distantes

Une autre notion pas toujours bien comprise au départ est qu’une branche d’un dépôt distant (le dépôt origin par exemple qui est le nom du dépôt à partir duquel nous faisons un git clone) n’existe pas forcément dans son dépôt local. Pour être plus précis, un git pull ne créera pas de branches sur lesquelles nous pourrons travailler mais seulement des images de ces branches qui ne seront pas accessibles pour commiter dessus. git branch -r liste les branches distantes alors que git branch ne liste que les branches locales au dépôt :

$ git branch
* master
$ git branch -r
origin/master
origin/integration

Pour pouvoir commiter sur une branche d’un dépôt distant, il faut créer une branche locale qui suit la branche distante :

$ git branch --track integration origin/integration

Dans cet exemple on crée une branche locale integration qui suit la branche integration du dépôt distant origin. Les branches distantes sont nommées ${nom_du_dépôt}/${nom_de_la_branche}. Ainsi, lorsque nous exécuterons un git pull depuis cette branche locale integration, celle-ci sera mise à jour avec les modifications provenant de la branche suivie origin/integration. De même, un git push enverra les modifications locales vers la branche suivie.

Après avoir mis à jour les références des branches distantes avec un git remote update, les alias suivants (cf. l’encadré sur les alias) vous seront utiles :

  • git logpush listera les commits qui seront poussés lors du prochain git push origin $(git currentbranch),
  • git logpull listera les commits que l’on s’apprête à merger lors du prochain git pull,
  • git whatsnew détaillera sous forme d’un diff ce qui va être poussé au prochain git push origin $(git currentbranch),
  • git whatscoming détaillera sous forme d’un diff ce qui va être mergé au prochain git pull.

branches distantes Git

Oui, Git est efficace et flexible

Avec les notions abordées dans cette première partie, nous verrons dans un deuxième article quelques fonctionnalités qu’offre Git pour gérer le versionnement des sources de son projet et qui en font un outil particulièrement efficace et flexible.

Annexes

Quelques alias utilisés dans l’article

Il suffit d’ajouter dans la section alias du fichier ~/.gitconfig :

...
[alias]
        st = status
        stp = status --porcelain
        ci = commit
        br = branch
        co = checkout
        rz = reset --hard HEAD
        pullr = pull --rebase
        unstage = reset HEAD
        lol = log --graph --decorate --pretty=oneline --abbrev-commit
        lola = log --graph --decorate --pretty=oneline --abbrev-commit --all
        lpush = "!git --no-pager log origin/$(git currentbranch)..HEAD --oneline"
        lpull = "!git --no-pager log HEAD..origin/$(git currentbranch) --oneline"
        whatsnew = "!git diff origin/$(git currentbranch)...HEAD"
        whatscoming = "!git diff HEAD...origin/$(git currentbranch)"
        currentbranch = "!git branch | grep \"^\\*\" | cut -d \" \" -f 2"
...
[color]
        branch = auto
        diff = auto
        status = auto
        interactive = auto

9 commentaires sur “Git dans la pratique (1/2)”

  • Les commandes whatsnew et whatscoming occasionnent des problèmes pour ma part : $ git whatsnew fatal: ambiguous argument 'origin/$(git': unknown revision or path not in the working tree. Use '--' to separate paths from revisions $ git whatscoming fatal: ambiguous argument 'HEAD...origin/$(git': unknown revision or path not in the working tree. Use '--' to separate paths from revisions Savez-vous ce qui ne va pas ?
  • Dans quel environnement exécutez-vous Git ? La syntaxe de ces commandes est celle de SH et ne fonctionneront donc pas dans un shell Windows. Si tel est votre cas, vous utilisez surement msysgit qui vient avec un shell bash.exe que vous pouvez exécuter avant d'utiliser Git et l'une de ces commandes.
  • C'est effectivement en utilisant msysgit que cela occasionne ces erreurs En tout cas, bravo pour les autres alias ;)
  • Bonjour, Je m'interesse depuis peu a git pour evaluer sa mise en place sur notre projet. 4 dev mais avec un besoin de creer des branches et un frein psychologique énorme utilisant subversion. Mise à part la description de l'index, je dois avouer que le reste de l'article m'a completement perdu. Etat ? pourquoi different reset ? quelles differnecs avec les branches svn ? Je m'attendais a un vrai cas pratique. Bon je glisse sur la deuxième partie
  • Bonjour n!co, Les différents reset existent à cause de l'index. Il y en plusieurs car tout dépend si on veux : - modifier le commit sur lequel une branche (la HEAD de master par exemple) pointe (avec --soft). Avec HEAD en paramètre, celà ne sert à rien. Par contre avec un commit précédent, celà permet "d'oublier" les commits qui suivent. Il ne modifie pas l'index et les fichiers locaux. cela permet de créer un nouveau commit. - de synchroniser l'index (avec --mixed, ou sans l'option) avec le dernier commit de la branche, avec HEAD en paramètre, mais de garder les modifications sur les fichiers. Celà est très utile pour supprimer les modifications qui ont été ajoutée à l'index (cf. l'alias unstage). Avec un paramètre différent de HEAD, celà permet le même cas d'usage que avec --soft mais sans rien garder sur l'index. - enfin, avec l'option --hard cela permet de supprimer toute les modifications des fichiers locaux avec HEAD en paramètre (comme le fait un svn revert). Avec un commit différent de HEAD en paramètre, celà permet de revenir en arrière dans l'historique des commits mais cette fois-ci, plus aucune trace des modifications n'est gardée, que ce soit dans les fichiers locaux, l'index ou les commits de la branche courante (il est possible de les retrouver si c'était une erreur mais par défaut il n'apparaissent plus dans l'historique). La différence entre les branches Git et SVN est que celles de SVN n'existe que sur le dépôt centralisé. Elles n'existent pas en local. En effet, en local, l'espace de travail local (après un svn checkout ou un svn switch) ne reflète l'état que d'une seule branche à la fois. Avec Git, deux particularités au niveau des branches le différencie de SVN : - le commit est local (il n'est pas nécessaire d'être connecté au dépôt que l'on a cloné (ie. checkout dans le vocabulaire SVN)) : les branches sur lesquelles nous commitons sont donc elles aussi locales. - il n'y a pas de dépôt centralisé, tout les dépôts sont équivalents et il est possible d'échanger (push ou pull) avec chacun d'eux. Les branches peuvent donc exister sur un dépôt et pas un autre, d'où la différence entre branches locales et branches distantes. La deuxième partie est plus sur la pratique au quotidien et s'appuie sur les 2 notions évoquées dans cette première partie. Après, un article expliquant la façon d'organiser nos dépôt entre eux et les branches mériterait d'être publié :)
  • J'utilise "git svn", et les commandes suivantes ne fonctionnent pas : lpush = "!git --no-pager log origin/$(git currentbranch)..HEAD --oneline" lpull = "!git --no-pager log HEAD..origin/$(git currentbranch) --oneline" whatsnew = "!git diff origin/$(git currentbranch)...HEAD" whatscoming = "!git diff HEAD...origin/$(git currentbranch)" En effet, un "git branch -r" montre que l'équivalent svn de "origin/master" est "trunk" (logique!)... A priori, il faudrait que je trouve une commande git svn qui affiche le lien entre une branch locale et la branche svn distante. Je n'ai rien trouvé pour le moment, si vous avez une idée je suis preneur.
  • Voici un alias qui renvoie la remote branch trackée : trackingbranch = "!git branch -vv | grep '^\\*' | sed -e 's/^\\*\\s\\+[^ ]\\+\\s\\+[0-9a-f]\\+\\s\\+\\[\\([^]:]\\+\\).*\\].*/\\1/'" du coup, les alias deviennent : lpush = "!git --no-pager log $(git trackingbranch)..HEAD --oneline" lpull = "!git --no-pager log HEAD..$(git trackingbranch) --oneline" whatsnew = "!git diff $(git trackingbranch)...HEAD" whatscoming = "!git diff HEAD...$(git trackingbranch)"
  • LOL. Désolé David, c'est la première réaction qu'a provoqué chez moi cette commande de folie :)
    1. Laisser un commentaire

      Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *


      Ce formulaire est protégé par Google Recaptcha