Travaux dirigés : Outils pour la programmation C - Git

ENSEIRB - MATMECA


Utiliser Git en local

Le programme git est un gestionnaire de version. Il permet de gérer des versions de votre projet, donc de revenir à une version antérieure, de comparer l'état du projet entre deux versions, de développer des branches parallèles et de les fusionner. On verra dans la partie suivante qu'il permet aussi à différents développeurs de gérer leurs développements en parallèle.
  1. Placer vous dans le répertoire dédié au TP et tapez git init. Vous venez de transformer ce répertoire en dépot git !
  2. Il faut maintenant spécifier quels vont être les fichiers dont on va suivre les versions (ce seront des "tracked files"). Dans un projet, on suivra tous les fichiers sources (.c,.h), les makefile et les fichiers de configuration (comme Doxyfile par exemple). À l'inverse, on ne suit pas les fichiers compilés (.o, bibliothèques, exécutables) qui peuvent être reconstruit à partir des fichiers suivis. Pour suivre un fichier, on tape git add [fichier] ou git add [repertoire] pour ajouter tout un répertoire au dépôt.

    La commande git status permet de voir quels sont les fichiers suivis, ceux qui ne le sont pas et (plus tard), ceux qui ont été modifiés récemment.

    Pour éviter de versionner par inadvertance des fichiers non désirés, on peut créer à la racine du dépôt un fichier .gitignore qui contient la description des noms de fichiers qu'on ne veut pas versionner; par exemple

    *.o
    *~
    lib
    Ces fichiers n'apparaîtront plus lorsqu'on appelle git status.

  3. Quand votre projet est dans un état que vous voulez enregistrer, il faut effectuer un commit : git commit -a; un éditeur se lance alors et vous pouvez ajouter un commentaire relatif à votre commit. L'option -a indique que vous voulez versionner les fichiers ajoutés et tous ceux qui ont été modifiés depuis le dernier commit. Vous pouvez visualiser la liste de vos commits avec la commande git log. Vous voyez ainsi le commit où vous vous situez (HEAD) et les commits qui le précèdent.
  4. Ajouter à votre fichier matrix.c la fonction suivante et le #include nécessaire:
    matrix_t clone_matrix(matrix_t m) {
      matrix_t c = m;
      size_t space=c.row_size*c.col_size*sizeof(double);
      c.values = malloc(space);
      memcpy(c.values, m.values, space);
      return c;
    }
    
    Vous pouvez voir la différence entre l'état de vos fichiers et le dernier commit en tapant git diff (on peut éventuellement spécifier un nom de fichier). Effectuez un second commit pour enregistrer l'ajout de cette fonction.
  5. Lorsque vous tapez git log, vous voyez que chaque commit est caractérisé par une longue suite de chiffres et de lettres qu'on appelle le sha du commit. Vous pouvez en particulier remettre votre projet dans un état qui correspond à celui d'un commit. Pour cela, taper git checkout [sha]. Remettez votre projet dans l'état du premier commit et vérifiez le contenu du fichier matrix.c.
  6. Comme nous l'apprennent les films de science-fiction, voyager dans le passé peut créer un nouvelle continuité. C'est exactement ce qu'il se passe avec git. En fait, git est capable de gérer plusieurs versions parallèles du projet, c'est ce qu'on appelle les branches. La commande git branch permet de visualiser les branches. Pour créer une nouvelle branche là où se trouve votre HEAD, il suffit de taper git branch [nom branche]. Pour se placer sur le commit le plus récent d'une branche, il suffit de taper git checkout [branche]. Revenez sur la branche principale qui s'appelle "master".
  7. Revenez au premier commit, créez une branche "identity", placez-vous dessus, insérez dans le fichier matrix.c la fonction ci-dessous et effectuez un commit.
    matrix_t make_identity_matrix(unsigned n) {
      matrix_t id = make_matrix(n,n);
      unsigned i,j;
      for(i=0; i<n; i++)
        for(j=0; j<n; j++)
          m_set(id, i, j, i==j);
      return id;
    }
    
    Vous avez maintenant deux branches "master" et "identity" dont les contenus sont différents. Ainsi, vous pouvez garder une version stable de votre projet sur une branche, "master" par exemple, et faire une autre branche pour développer une nouvelle partie de votre projet. Lorsque celle-ci est au point, vous pouvez la fusionner. Pour cela, il faut se placer sur la branche où l'on veut récupérer les modifications et faire git merge [autre branche]. Fusionnez ainsi la branche "identity" sur la branche "master".

    Lors d'un merge, git "rejoue" sur la branche courante les modifications effectuées sur la branche que l'on fusionne depuis qu'elles sont séparées. Il se peut que des modifications ayant eu lieu en parallèle sur les 2 branches soit conflictuelles; dans ce cas, git ne sait pas quoi faire. Dans chaque fichier où il y a eu conflit, il garde les 2 versions possibles, il faut alors que le développeur décide lui-même quelle est la bonne version et mette à jour le fichier. Il faut ensuite faire git add [fichier] pour indiquer que le conflit sur ce fichier est résolu. Lorsque tous les conflits sont résolus, on fait un commit.

    Une fois la fusion réussie, on n'a potentiellement plus besoin de la branche que l'on a fusionnée. On peut à ce moment la supprimer : git branch -d [branche]. Pour ce TP, on va la garder encore un peu.

    Vous pouvez consulter l'historique de votre branche en tapant git log --graph --all

  8. Placez-vous sur la branche "identity" et insérez dans le fichier vector.c la fonction ci-dessous et le #include nécessaire.
    vector_t clone_vector(vector_t v) {
      vector_t c = v;
      size_t space=c.size*sizeof(double);
      c.values = malloc(space);
      memcpy(c.values, v.values, space);
      return c;
    }
    
    Mince! Il aurait été plus judicieux de faire cette modification sur la branche "master". Essayez un git checkout master. Ça ne marche pas, on ne peut pas changer de branche avec des fichiers dans un état intermédiaire. Pour ne pas perdre ce qu'on vient de faire, on pourrait faire un commit, mais on n'a peut-être pas envie de sauvegarder cet état sur la branche "identity". La commande git stash permet de "mettre de côté" ce qu'on vient de faire. Tapez cette commande; vous constatez que vos fichiers sont revenus dans l'état du dernier commit. Vous pouvez maintenant repasser sur la branche "master". Vous pouvez même récupérer les modifications que vous aviez mises de côté en tapant git stash pop. Vérifiez que tous vos fichiers sont bien dans l'état voulu.
  9. Supprimer la branche "identity".

Git collaboratif

On peut communiquer entre plusieurs dépôts git concernant le même projet. Dans ce cas, on utilise généralement un dépôt central sur lequel les développeurs placent les versions qu'ils souhaitent partager. Même dans cette configuration, tout ce qu'on a dit plus haut reste vrai et on peut créer des branches locales sans les partager. Bien sûr, le dépôt central doit être accessible à tous les collaborateurs, soit sur une machine où ils ont chacun un compte, soit sur un site fournissant des dépôts git. Il en existe de nombreux qui peuvent héberger vos projets. L'école en propose un à ses élèves.
  1. Allez sur la plateforme Thor, connectez-vous, allez dans l'onglet "Projects" et choisissez "On request". Suivez les instructions pour créer un dépôt git; créer un dépôt pour 2 (ou 3) d'entre vous. Lorsqu'il est créé, placez-vous dans un répertoire indépendant du dépôt local créé dans la première partie et appliquez l'instruction git clone [depot distant] donné sur la page web. Allez dans le répertoire ainsi créé, faites un git status pour voir l'état de votre nouveau dépôt local.
  2. À faire par un seul élève du binôme: créer dans le dépôt un répertoire doc, copier les fichiers Makefile et Doxyfile (dans doc) ainsi que les répertoires src et include. Mettre tous les fichiers sous version et faire un commit. Pour communiquer les commits de la branche courante au dépôt distant, on applique git push.

    Ensuite, l'autre élève peut récupérer ce qui est sur le dépôt distant en faisant git pull --rebase (l'option --rebase n'est ici pas nécessaire, elle indique que si on a fait des commits en local sur la branche, on veut récupérer l'état actuel du dépôt puis appliquer nos commits, ce qui est en général le comportement le plus sain).

  3. Créez chacun une nouvelle branche (avec un nom distinct de l'autre) à partir de la branche "master" et faite une modification (par exemple déclarer dans les entêtes les fonctions ajoutées plus tôt) avant de faire un commit. Pour que votre branche soit accessible des autres développeurs, il faut la pousser sur le dépôt distant avec git push -u origin [ma branche] (origin est le nom par défaut du dépôt distant; git permet de travailler avec plusieurs dépôts distants pour un même projet, mais c'est beaucoup plus délicat).
  4. Vous visualisez les branches disponibles sur le dépôt distant avec git branch -r (-r pour remote). Pour cela, il faut que vous ayez localement l'information que ces branches existent; pour mettre à jour cette information, il faut taper git fetch.
  5. Pour récupérer une branche distante, tapez git checkout --track origin/[branche distante]; vérifier que vous disposez bien d'une nouvelle branche.
  6. Utilisez l'outil gitg pour manipuler votre dépôt.
Ce TP ne présente que les fonctionnalités les plus courantes de git. Utiliser git help ou git help [commande] pour aller plus loin. Par ailleurs, il est bon de savoir que les paramètres de votre dépôt sont dans le fichier .git/config que vous pouvez éditer pour les ajuster.