Git : cloner un projet, travailler à plusieurs et créer des branches

logo Git
Logo de git (http://​git​-scm​.com) Git Logo by Jason Long is licen­sed under the Crea­tive Com­mons Attri­bu­tion 3.0 Unpor­ted License

 Git est un logi­ciel de contrôle de ver­sions de fichiers. Il est dis­tri­bué sous licence GNU GPLv2 et est dis­po­nible sur les prin­ci­paux sys­tèmes d'exploitation.

Dans l'article pré­cé­dent, nous avions vu com­ment ins­tal­ler et confi­gu­rer git, com­ment créer un dépôt pour un pro­jet, ain­si que les prin­cipes de base de ges­tion de ver­sions.

Dans cet article, nous ver­rons com­ment clo­ner un pro­jet (par exemple pour tra­vailler à plu­sieurs ou pour faire des sau­ve­gardes), com­ment syn­chro­ni­ser les dif­fé­rentes copies tout en détec­tant et résol­vant les pro­blèmes éven­tuels, et com­ment créer des branches et indi­quer les étapes qui cor­res­pondent à des ver­sions.

Cloner un projet (par ex. pour travailler à plusieurs ou pour sauvegarder)… et synchroniser les différentes copies

git clone pour dupliquer un dépôt

La com­mande "git clone </path/to/existing/git/repository> </path/where/it/should/be/cloned>" per­met de clo­ner un dépôt exis­tant (en local ou à tra­vers un réseau si le che­min est une URL) à un autre endroit. La méta­phore du clo­nage doit être com­prise ici comme une copie exacte : après "git clone" les deux dépôts sont les images par­faites l'une de l'autre : ils pos­sèdent le même his­to­rique, et il n'y en a pas un qui est plus légi­time que l'autre (en fait, on ver­ra dans la sec­tion sur "git pull" que suite au clo­nage la branche mas­ter du clone fait réfé­rence à la branche mas­ter du clo­né). Si plu­sieurs per­sonnes tra­vaillent sur un pro­jet, elles peuvent ain­si en obte­nir cha­cune une copie.
Dans un pre­mier temps, nous allons syn­chro­ni­ser les dépôts, puis nous ver­rons dans un second temps com­ment détec­ter et gérer les conflits qui peuvent sur­ve­nir lors de modi­fi­ca­tions concur­rentes. Nous sup­po­se­rons que le pro­jet ini­tial était dans le réper­toire repoA, et qu'on le clone dans un autre réper­toire repoB situé n'importe où sauf sous l'arborescence de repoA évi­dem­ment. Pour sim­pli­fier nous pren­drons repoA et repoB au même niveau, même si en pra­tique ça n'a pas grand inté­rêt.
$ git status
On branch master
nothing to commit, working directory clean
$ cd ..
$ mkdir repoB
$ git clone repoA repoB
Cloning into 'repoB'...
done.
$ ls -a repoA
. .. doc firstFile.txt firstFile.txt.bak .git .gitignore licence.txt log README.txt
$ ls -a repoB
. .. doc firstFile.txt .git .gitignore licence.txt README.txt
Au passage, vous remarquerez que le fichier .gitignore (c'est le même fichier qu'au premier article) dans repoA a bien été pris en compte et que le fichier firstFile.txt.bak ainsi que le répertoire log ont bien été ignorés lors du clonage dans repoB.

git pull pour maintenir à jour une copie locale d'un dépôt de référence

Nous allons commencer par une configuration simple où repoA est le dépôt de référence d'un projet où se font toutes les modifications, et vous souhaitez uniquement maintenir à jour votre copie locale repoB sans que vous y apportiez la moindre modification.

Maintenant que le dépôt d'origine repoA a été cloné, nous allons commencer par y faire des modifications puis un nouveau commit.

$ cd repoA
$ git status
On branch master
nothing to commit, working directory clean
$ echo "modification from repoA" >> firstFile.txt
$ git add firstFile.txt
$ git commit -m "some improvement from repoA"
$ git status
On branch master
nothing to commit, working directory clean

Maintenant, un "git status" dans repoB montre que celui-ci est à jour (par rapport à lui-même), mais que les modifications que nous venons de faire dans repoA n'ont pas été reportées dans repoB.
$ cd ../repoB
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
$ diff ../repoA/firstFile.txt ./firstFile.txt
8d7
< modification from repoA
Pour synchroniser son dépôt avec celui que nous avions cloné (ou un autre), il faut faire un appel explicite à "git pull". Ce dernier ne se fait pas automatiquement (heureusement, imaginez un peu la pagaille). Ci-dessous, la ligne 19 lorsque l'on regarde le contenu du fichier firstFile.txt après le "git pull" montre que la modification que nous avions faite dans repoA a bien été répercutée dans repoB.

Remarquez que dans repoB, nous faisons simplement "git pull" sans lui dire explicitement qu'il faut aller chercher dans repoA. En fait, le "git clone repoA repoB", git a bien indiqué dans repoB que ce dernier avait été généré à partir de repoA en le désignant comme "origin" (comparez donc les fichiers repoA/.git/config et repoB/.git/config). Consultez la documentation sur les dépôts distants pour plus de précisions.

$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /home/olivier/tmp/jnk/repoA
c939f1f..b88eea6 master -> origin/master
Updating c939f1f..b88eea6
Fast-forward
firstFile.txt | 1 +
1 file changed, 1 insertion(+)
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

Pour récapituler :

  • Le dépôt de référence fait donc un cycle classique :
    1. modifications,
    2. git add,
    3. git commit.
  • Le dépôt clone se maintient à jour en faisant "git pull".

git push pour propager sur le dépôt d'origine les modifications que vous avez faites en local

Alors que dans la section précédente les modifications se faisaient toujours dans le même sens, nous allons maintenant voir que git vous permet de propager sur le dépôt d'origine des modifications que vous avez faites sur son clone. Puisque les modifications se font en sens inverse de celles du "git pull", il faut évidemment utiliser "git push" depuis repoB. Afin d'éviter les conflits potentiels lorsque plusieurs utilisateurs font des modifications sur leur clone, nous allons créer une branche spécifique dans laquelle nous allons faire nos modifications, puis nous allons envoyer cette branche dans repoA avec un "git push", et enfin fusionner cette branche avec la branche en cours de repoA avec un "git merge". La section suivante revient plus en détail sur les branches.

Dans l'exemple suivant, nous nous plaçons dans repoB, nous créons une nouvelle branche myBranchFromB (ligne 6) dans laquelle nous faisons des modifications sur le fichier firstFile.txt (lignes 8 à 10). Remarquez que lors du "git commit" lignes 11 puis 14, ces opérations se font sur la nouvelle branche myBranchFromB. Enfin, lors du "git push" les lignes 22 et 23 montrent que la nouvelle branche est transmise à repoA.

$ git pull
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
$ git checkout -b myBranchFromB
Switched to a new branch 'myBranchFromB'
$ echo "modifications from repoB" >> firstFile.txt
$ git add firstFile.txt
$ git commit -m "improvements from repoB"
[myBranchFromB 098fc69] improvements from repoB
1 file changed, 1 insertion(+)
$ git status
On branch myBranchFromB
nothing to commit, working directory clean
$ git push origin myBranchFromB
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 328 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
To /home/olivier/tmp/jnk/repoA/
* [new branch] myBranchFromB -> myBranchFromB
En revenant à repoA, nous pouvons vérifier que tant que l'on est sur la branche master, les modifications ne sont pas prises en compte (lignes 3 et 6 à 12), mais qu'elles sont bien dans la branche myBranchFromB que nous venons d'envoyer avec le "git push" (lignes 14, 16 et 19 à 26). Nous revenons donc à la branche master (ligne 27) pour incorporer les changements de la branche myBranchFromB avec un "git merge" (ligne 29).
$ cd ../repoA
$ git status
On branch master
nothing to commit, working directory clean
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
$ git checkout myBranchFromB
Switched to branch 'myBranchFromB'
$ git status
On branch myBranchFromB
nothing to commit, working directory clean
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
modifications from repoB
$ git checkout master
Switched to branch 'master'
$ git merge myBranchFromB
Updating 842cc68..09805d8
Fast-forward
firstFile.txt | 1 +
1 file changed, 1 insertion(+)
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
modifications from repoB
À ce point, nous avons effectué des modifications dans la branche myBranchFromB de repoB, envoyé cette branche dans repoA et nous l'y avons fusionné avec la branche principale (appelée master) de repoA. Néanmoins, la branche master de repoB n'a pas encore été mise à jour. Il nous faut donc enfin faire un "git pull" depuis repoB.

$ cd ../repoB
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
$ git pull
From /home/olivier/tmp/jnk/repoA
842cc68..09805d8 master -> origin/master
Updating 842cc68..09805d8
Fast-forward
firstFile.txt | 1 +
1 file changed, 1 insertion(+)
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
modifications from repoB
Pour récapituler :

  • depuis le dépôt clone
    • n'oubliez pas de commencer par un "git pull" pour être certain de partir de la dernière version de référence
    • créez une nouvelle branche avec git checkout -b nomNouvelleBranche
    • faites un ou plusieurs cycles classiques
      • modifications,
      • git add
      • git commit
    • faites un "git push" (comme nous l'avons vu précédemment, il est possible de faire plusieurs cycles de "commit" atomiques puis un "git push" lorsque l'on est satisfait)
  • depuis le dépôt origine
    • assurez-vous d'être sur la bonne branche (par exemple master) avec un "git checkout nomBonneBranche"
    • faites un "git merge nomNouvelleBranche" de la branche que vous venez de pousser depuis le dépôt clone
    • résolvez les conflits éventuels (cf. section suivante)
    • faites un "git branch -d nomNouvelleBranche" pour supprimer la  branche qui est devenue inutile maintenant que vous l'avez intégrée
  • depuis le dépôt clone
    • repassez dans la bonne branche (par exemple master)
    • faites un "git pull" pour récupérer la dernière version à jour avec les modifications que vous venez de pousser, les modifications éventuelles d'autres utilisateurs et la résolution des conflits éventuels.
    • faites un "git branch -d nomNouvelleBranche" sur la nouvelle branche pour la supprimer également.

Détection et résolution de conflits en cas de modifications concurrentes sur deux instances

Évidemment, la situation que nous venons de voir peut se révéler problématique si pendant que vous êtes occupé à faire vos modifications entre votre "git pull" et votre "git push" quelqu'un fait d'autres modifications sur le dépôt d'origine (ou fait un autre "git push" avant le vôtre).

Une fois de plus, git est là pour vous sauver la vie :

  • si les modifications concurrentes portent sur des fichiers différents ou même des endroits différents d'un même fichier, git se débrouille pour tout intégrer (alors qu'avec de simples copies de fichiers, les modifications du dernier écraseraient les modifications antérieures)
  • si les modifications concurrentes portent sur les mêmes endroits dans un fichier,
    • git vous signale qu'il y a un conflit (et vous dit où)
    • git vous prépare la zone en délimitant les deux modifications concurrentes
    • git vous laisse gérer la résolution

Là encore, notez bien l'intérêt de ne travailler sur des changements les plus petits possibles. Il est bien plus facile de fusionner deux branches ayant peu de modifications que deux branches ayant fortement divergé et comportant des différences aux mêmes endroits.

$ cd repoB
$ git pull
Already up-to-date.
$ git checkout -b myBranchFromB
Switched to a new branch 'myBranchFromB'
$ echo "conflict from repoB" >> firstFile.txt
$ git add firstFile.txt
$ git commit -m "improvement from repoB likely to cause conflict"
[myBranchFromB db36634] improvement from repoB likely to cause conflict
1 file changed, 1 insertion(+)
$ cd ../repoA
$ echo "conflict from repoA" >> firstFile.txt
$ git add firstFile.txt
$ git commit -m "improvement from repoA likely to cause conflict"
[master 348ff6b] improvement from repoA likely to cause conflict
1 file changed, 1 insertion(+)
$ cd ../repoB
$ git push origin myBranchFromB
Counting objects: 26, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (21/21), done.
Writing objects: 100% (26/26), 2.12 KiB | 0 bytes/s, done.
Total 26 (delta 11), reused 0 (delta 0)
To /home/olivier/tmp/jnk/repoA/
* [new branch] myBranchFromB -> myBranchFromB
À ce point, nous avons ajouté une ligne dans le fichier firstFile.txt dans repoB et une autre ligne au même endroit du fichier firstFile.txt dans repoA. Nous procèdons ensuite comme à la section précédente pour envoyer sur repoA la branche myBranchFromB de repoB.

Il ne reste plus qu'à fusionner la branche myBranchFromB avec la branche principale de repoA (et les ennuis commencent).

$ cd ../repoA
$ git merge myBranchFromB
Auto-merging firstFile.txt
CONFLICT (content): Merge conflict in firstFile.txt
Automatic merge failed; fix conflicts and then commit the result.
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
modifications from repoB
<<<<<<< HEAD
conflict from repoA
=======
conflict from repoB
>>>>>>> myBranchFromB
Lors du "git merge", git détecte bien le conflit (ligne 4) et nous dit dans quel fichier cela s'est produit. Dans le fichier, git a ajouté les deux portions qui posent problème en les délimitant par des lignes de chevrons et en les séparant par une ligne de '=' (lignes 15 à 19). C'est alors à l'utilisateur d'éditer la zone à la main et de supprimer les délimitations.

$ vi firstFile.txt
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
modifications from repoB
Manually-resolved conflict from repoA and repoB
$ git add firstFile.txt
$ git commit -m "Fixed conflicts from repoA and repoB"
[master ab5a47f] Fixed conflicts from repoA and repoB
$ git status
On branch master
nothing to commit, working directory clean
$ git branch -d myBranchFromB
Deleted branch myBranchFromB (was db36634).

Enfin, il ne reste plus qu'à mettre à jour repoB.

$ cd ../repoB
$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 3 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
$ git pull
Updating 09805d8..ab5a47f
Fast-forward
firstFile.txt | 1 +
1 file changed, 1 insertion(+)
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

Adding a fourth line
Adding a fifth line
modification from repoA
modifications from repoB
Manually-resolved conflict from repoA and repoB
$ git branch
* master
myBranchFromB
$ git branch -d myBranchFromB
Deleted branch myBranchFromB (was db36634).

Créer des branches et des versions

Intérêt des branches

Dans la section sur "git push", nous avons eu un premier aperçu de la notion de branche. En fait, git permet de créer autant de branches que nous en avons besoin. La documentation de git comporte des explications très claires sur la notion de branche qui en plus abordent des points un peu plus techniques comme la notion de HEAD.

Les branches sont au cœur d'organisation des projets avec git. Un exemple d'usage est le marquage de version. Les tags, que nous verrons plus loin, c'est cool, mais si vous passez en version majeure suivante (donc, brisure de compatibilité), il faut aussi penser à ceux qui ne suivront pas l'update et resteront à la version «ancienne», et qui voudront des corrections de bugs et même des mises à jour. Un exemple parlant est python : il y a deux versions qui cohabitent, et même si l'une est destinée à mourir bientôt, elle est encore développée et enrichie. Même si la réalité est plus complexe, nous pouvons imaginer qu'un repo git de Python serait composé de deux branches principales, «Python2» et «Python3», qui permettent au deux versions de cohabiter et d'évoluer indépendamment. Il y en aurais bien d'autres (une pour chaque fonctionnalité en cours de développement), mais ces deux là seraient les «master».

Un vrai exemple du monde de la vérité vraie : la SFML, une librairie graphique initiée et maintenue par un français (cocorico), et qui initialement était au C++ ce que la SDL est au C. Les branches sont ici utilisées pour les différents portage de la librairies sur différents langages, et github permet de les visualiser joliment. Nous voyons également très bien toutes les branches dont le nom commence par «bugfix», qui sont en fait créées uniquement pour résoudre un des problèmes identifiés par le mécanismes de gestion de problèmes, qui ne sera pas abordé ici, car cela n'a rien à voir avec git (pas directement). Nous trouvons également les branches ayant pour nom «2.3.x» par exemple, et nous pouvons constater que de nombreuses personnes crééent leur propre branche (ça s'appelle un «fork», et c'est une notion à la base du développement de logiciel libre) pour développer une fonctionnalité ou corriger un bug, puis effectuent un merge de leur branche avec la branche principale (via les pull requests, où une personne demande à ce qu'une de ses branches soit mergée avec une branche d'un autre repo qui ne lui appartient pas).

En général, un dépôt git est composé d'au moins deux branches, souvent nommées master et dev. Master est la branche dite principale, où le code est stable. Autrement dit, lorsque vous récupérez les fichiers de la branche master d'un projet, vous devriez avoir le code le plus fonctionnel possible. (c'est souvent la release qui est stockée en master)

La branche dev, quant à elle, est une branche où le code est en développement. C'est à partir de celle-ci que se créent des branches telles que «bugfix53» qui va implémenter la correction du bug numéro 53. Lorsque la branche dev est stable et pleine de nouvelles fonctionnalités, elle est mergée avec master, qui est alors passé à la version suivante. (et il est possible de placer des tags sur les derniers commits pour montrer où en est la version)

Quand le projet devient plus gros, ou, mieux, qu'une nouvelle version importante va sortir, c'est une bonne idée de créer un fork de master nommé selon la version (par exemple «4.x», ou «5.3.x»). Dés lors, comme la branche dev continue de merger avec le master, les fonctionnalités n'ayant pas été ajoutées avant devront attendre la prochaine version du logiciel (et donc la prochaine branche) pour être utilisées. Avec ce système, il est possible de se passer de la branche dev et créer les nouvelles fonctionnalités directement en forkant le master.

Autre exemple : sur le repo de Julia, nous observons plus de 183 branches et 192 pull requests. Chaque branche est créée par un contributeur avec un nom adapté au problème auquel cette branche va répondre, et lorsque c'est terminé, la branche sera «mergée» à la branche principale.

Souvent, il y a un peu de discussion pour que le code ajouté soit parfait, juste, en accord avec le projet, n'introduise pas de conflit, ou pour avoir des messages de commit de meilleure qualité.

Certaines personnes attendent de leurs contributeurs qu'ils suivent une ligne de conduite parfois assez stricte. Cela peut sembler tyrannique, mais c'est comme obliger un style de code dans un projet à plusieurs : sans uniformisation, le code est d'autant plus dur à lire et hétérogène dans sa construction, alors autant se forcer à écrire les choses de la même façon dés le début.

Le scénario suivant est fortement inspiré de la documentation et vous donne l'idée générale :

  • vous faites une branche pour ajouter une nouvelle fonctionnalité
  • en plein milieu on vous demande de corriger un bug en urgence
    • vous ne le faites pas dans votre nouvelle branche car le code n'est pas encore fonctionnel
    • vous ne le faites pas non plus dans la branche principale car celle-ci ne doit pas être utilisée comme branche de développement
      • vous créez une seconde branche depuis la branche principale pour corriger le bug
      • vous fusionnez cette seconde branche avec la branche principale une fois la correction terminée
      • vous terminez votre ajout de fonctionnalité sur la première branche
      • vous fusionnez votre première branche avec la (nouvelle) branche principale en réglant les conflits éventuels

Commandes utiles pour manipuler les branches

La commande "git branch" permet de lister les branches existantes. Elle a notamment deux filtres :

  • "git branch --merged" indique uniquement les branches qui ont déjà été fusionnées.
  • "git branch --no-merged" indique uniquement les branches qui n'ont pas encore été fusionnées.

 

La commande "git checkout nomDeLaBranche" permet de passer sur la branche nomDeLaBranche (qui doit déjà exister), et qui devient alors la branche courante.

La commande "git branch -b nomDeLaBranche" permet de créer une nouvelle branche. La branche ainsi créé est identique à la branche courante : elle contient le même historique et les même commits, mais une modification sur l'une n’impactera pas l'autre. La commande "git checkout -b nomDeLaBranche" a exactement le même effet, mais "switche" sur celle-ci juste après : la branche courante devient nomDeLaBranche. Nous pouvons donc en déduire l'équation suivante : "git branch -b nomDeLaBranche" + "git checkout nomDeLaBranche" = "git checkout -b nomDeLaBranche".

La commande "git branch -d nomDeLaBranche" permet de détruire une branche. Si la branche n'a pas été totalement mergée, aucune action ne sera opérée, et git expliquera la marche à suivre pour résoudre le problème ; en général cela arrive quand vous essayez de détruire une branche que vous n'avez pas mergée. Détruire malgré tout la branche (avec l'option -D, comme l'expliquera git) supprimera DÉFINITIVEMENT les modifications n'ayant pas été rapatriées sur une autre branche. Ce cas de figure arrive typiquement lorsque vous développez une correction de bug sur une branche dédiée, et que le bug est corrigé par quelqu'un d'autre : votre branche est inutile, elle peut donc être détruite.

Enfin, la commande "git merge nomDeLaBranche" permet de fusionner la branche nomDeLaBranche avec la branche courante, en détectant éventuellement les conflits. La branche cible (nomDeLaBranche) ne sera pas modifiée lors de cette opération.

Utiliser des tags pour indiquer les versions

Nous avons vu que git log donne l'historique de tous les commit. Nous avons aussi lourdement insisté sur les avantages de faire de nombreux commit atomiques plutôt qu'un gros commit chaque fois que vous produisez une nouvelle version stable. On comprend donc bien que repérer parmi cette longue liste le commit qui correspond à la version 5.2 de votre projet ne va pas être facile, d'autant plus que le message associé à chaque commit décrit ce qui a changé, et pas forcément le fait que ce commit constitue une étape importante pour le projet.

En plus du message de description, vous pouvez associer un tag à un commit. En fait git supporte deux types de tag:

  • un tag léger (lightweight tag) est simplement une association entre une chaîne de caractères (le tag) et un commit ;
  • un tag annoté (annotated tag) est un objet qui contient la chaîne de caractères ainsi que la date, l'identité de l'auteur, etc. Il peut éventuellement être signé.

On utilise principalement les tags légers pour la maintenance, les besoins temporaires et les affaires internes, et les tags annotés pour les annotations importantes.

La commande git tag renvoie la liste de tous les tags d'un projet, triés par ordre alphabétique.

La commande git show someTagValue renvoie les informations du tag et une description du commit associé.

La commande git tag -d someTagValue détruit le tag someTagValue.

Créer un tag léger

La commande "git tag someTagValue" permet de créer un tag léger. Le tag est alors associé au dernier commit.

La commande git tag someTagValue commitCheckSum permet d'associer un tag léger à un commit antérieur. La commande git log (mais c'est assez verbeux), ou mieux git log --pretty=oneline est alors bien pratique pour retrouver la valeur de checksum de chaque commit en faisant.

$ git tag
$ git log --pretty=oneline
ab5a47f1438ac91c1403cfe348b1ffe17a517646 Fixed conflicts from repoA and repoB
348ff6b484cb63d5b526fa160773fc19b2b0c388 improvement from repoA likely to cause conflict
db36634059cb8a18707b440cf76fc7aab648685c improvement from repoB likely to cause conflict
09805d8e4a418e2dfcda0bc1c6f7a70fa4c7c8d4 improvements from repoB
842cc6890d9bf62cd0817a7cb14ecf81c525ebaf some improvement from repoA
7deabd7f55558ab1ce5d7f5c55d13e358f4f51c9 added a .gitignore file to ignore backup files and the log directory
fcae2678d2c8c19933c8d64aeb3dff205b654421 added two more lines to explain how git diff works
61b5f1da1168e3bfd89a10c65dd1dab6ee25d3fd this commit only adds the fourth line; the fifth is ignored as it is not yet in the index
e26b158e016d50b75a551438ccaa936b59afb799 First commit
$ git tag afterConflictResolution
$ git tag
afterConflictResolution
$ git tag gitignore 7deabd7f55558ab1ce5d7f5c55d13e358f4f51c9
$ git tag
afterConflictResolution
gitignore
$ git show gitignore
commit 7deabd7f55558ab1ce5d7f5c55d13e358f4f51c9
Author: Gérard Menvuça <super.gege@wanadold.fr>
Date: Sat Apr 11 00:28:19 2015 +0200

added a .gitignore file to ignore backup files and the log directory

Créer un tag annoté

la commande git tag -a someTagValue -m "some description for the tag" permet de créer un tag annoté. Là encore, cela associe le tag annoté au dernier commit, et il suffit de préciser le checksum si on veut associer le tag à un commit antérieur.

Dans l'exemple ci-dessous, nous associons le tag annoté v2.0 au dernier commit, et le tag v1.0 au commit concernant le fichier .gitignore, juste avant la partie sur la résolution de conflits. Remarquez que le tag léger gitignore et le tag annoté v1.0 sont associés au même commit, et que les commandes git show respectives montrent les informations supplémentaires associées au tag annoté.

$ git log --pretty=oneline
ab5a47f1438ac91c1403cfe348b1ffe17a517646 Fixed conflicts from repoA and repoB
348ff6b484cb63d5b526fa160773fc19b2b0c388 improvement from repoA likely to cause conflict
db36634059cb8a18707b440cf76fc7aab648685c improvement from repoB likely to cause conflict
09805d8e4a418e2dfcda0bc1c6f7a70fa4c7c8d4 improvements from repoB
842cc6890d9bf62cd0817a7cb14ecf81c525ebaf some improvement from repoA
7deabd7f55558ab1ce5d7f5c55d13e358f4f51c9 added a .gitignore file to ignore backup files and the log directory
fcae2678d2c8c19933c8d64aeb3dff205b654421 added two more lines to explain how git diff works
61b5f1da1168e3bfd89a10c65dd1dab6ee25d3fd this commit only adds the fourth line; the fifth is ignored as it is not yet in the index
e26b158e016d50b75a551438ccaa936b59afb799 First commit
$ git tag -a v2.0 -m "version 2.0 after conflict resolution"
$ git tag
afterConflictResolution
gitignore
v2.0
$ git tag -a v1.0 -m "version 1.0 including .gitignore" 7deabd7f55558ab1ce5d7f5c55d13e358f4f51c9
$ git tag
afterConflictResolution
gitignore
v1.0
v2.0
$ git show v1.0
tag v1.0
Tagger: Gérard Menvuça <super.gege@wanadold.fr>
Date: Fri Apr 17 01:59:54 2015 +0200

version 1.0 including .gitignore

commit 7deabd7f55558ab1ce5d7f5c55d13e358f4f51c9
Author: Gérard Menvuça <super.gege@wanadold.fr>
Date: Sat Apr 11 00:28:19 2015 +0200

added a .gitignore file to ignore backup files and the log directory
$ git show gitignore
commit 7deabd7f55558ab1ce5d7f5c55d13e358f4f51c9
Author: Gérard Menvuça <super.gege@wanadold.fr>
Date: Sat Apr 11 00:28:19 2015 +0200

added a .gitignore file to ignore backup files and the log directory

Les forges logicielles

Sous ce nom barbare débordant de sens complexes et pointus se cachent des sites que beaucoup de monde utilise parfois sans le remarquer. L'un des exemples les plus connus est sourceforge, qui est utilisée par beaucoup de logiciels pour y être… forgés.

Le principe d'une forge logicielle est de permettre à un (groupe d')utilisateur(s) ou une association de travailler collaborativement sur un projet, avec une uniformisation des outils de gestion. Et dans gestion, il y a gestion de versions, dont parle cet article, mais pas que. Une forge logicielle propose en général un wiki intégré, un système de tickets, voire des logiciels de gestion de projet pour générer des diagrammes de gantt, de l'UML,… En d'autres termes, la forge logicielle est le terrain de jeu de la science du génie logiciel, et en utiliser les bases ne peut qu'être utile et efficace.

Par exemple, dans le dépôt sourceforge de Potassco, on trouve les fichiers dans un dépôt svn (un autre gestionnaire de versions), deux mailing lists, un système de tickets pour remonter les bugs et proposer des modifications (de futures branches, pour enrichir de futures versions !),  un wiki,…

Dans ce tuto, nous avons beaucoup vu de liens vers une des plus fameuses forges : github. Bien qu'elle ne soit en elle-même pas libre (son code source n'est pas distribué ni sous licence libre), c'est une des plateformes principales de l'univers du libre. Elle propose, gratuitement pour les projets open-source, payant sinon, un espace de stockage illimité pour les projets, un unique gestionnaire de versions (git !) avec une interface graphique dédiée à la visualisation des dépôts et pull requests, un système de wiki, de gestion de problèmes,…

Il est à noter qu'au moment où ces lignes sont écrites, deux forges logicielles ont disparu en peu de temps : google code et gitorious ont été respectivement fermée et rachetée, car trop forte concurrence ou absorption par un autre projet de forge. Une autre, et pas des moindres, sourceforge, commence sérieusement à inquiéter de par son comportement que d'aucun diront amoral, au point que de nombreux projets, suite aux déboires du projet The Gimp, migrent vers d'autres forges (ce dernier lien montre au passage une bonne quantité de forges, avec les gestionnaires de versions supportés par chacune)

Les forges logicielles forment un sujet suffisamment vaste pour être traité dans un article dédié. En général, regarder les offres et fonctionnalités de chacune d'entre elles suffit à se faire une idée. Parmi les autres alternatives :

  • gitlab, une solution efficace pour l'auto-hébergement, ou comme solide alternative à github.
  • framagit, tenu par l'association Framasoft et basée sur gitlab. Le nombre de dépôts est limité, mais c'est une asso française et, pour le coup, des plus libristes qui soient !
  • bitbucket, qui propose notamment une offre gratuite pour les petits dépôts privés.
  • gogs, versé dans l'auto-hébergement (merci Pierre Marijon).
  • gitchain, un nouveau projet orienté pure décentralisation, ce qui parait important, dans ce monde où l'on a trop tendance à faire confiance au «cloud».
  • redmine, une forge très complète, utilisée dans le domaine du génie logiciel.
  • SourceSup, la forge de Renater.

Choisir une forge n'est pas une décision primordiale : vous arriverez certainement à travailler sur plusieurs projets, tous hébergés dans une forge différente. Il peut également arriver qu'un projet change de forge ; d'abord parce que rien ne l'interdit, mais aussi parce que parfois, comme pour sourceforge, la philosophie d'une forge est remise en question et que les créateurs d'un projet préfèrent rejoindre une forge plus en adéquation avec leurs besoins. Pour passer d'une forge à l'autre, quelques commandes suffisent souvent.

Sachez qu'avec tout ce que vous savez de git, il ne vous est pas difficile de créer un compte sur gitlab ou framagit, et de vous lancer ! Les seules commandes qui changeront seront les premiers appels à git clone, git push et git pull. Il faudra donner des URL ou des arguments particuliers, qu'en général la forge vous donne directement pour vous faire gagner du temps.

Aujourd'hui, il est rare qu'un projet n'ait pas une visibilité sur une ou plusieurs forges logicielles. Les implémentations de langages, par exemple, sont très visibles par ce biais, ainsi que le noyau linux ou galaxy.

Au niveau de l'enseignement en informatique, certains professeurs observent les statistiques offertes par la forge pour juger rapidement du partage des tâches et de la régularité de travail des membres d'une équipe ; par exemple, github se prête assez bien à ce jeu car fournissant une grande quantité de graphiques colorés et rigolos.

Tuner son git

C'est comme le permis de conduire : maintenant que nous avons fait plein de théorie et de bla bla sur les bonnes pratiques, nous allons rentrer dans le dur du sujet : le tuning ! (rien à voir avec Turing)

Les jantes

Je suis certain que beaucoup d'entre vous trouvent extrêmement long les commandes de git, telles que git commit, avec 10 touches à frapper sans compter «enter», ou git pull, qui donne l'impression de ne fonctionner qu'en hiver. Eh bien, c'est comme le bashrc pour remplacer «ls» par «l» et «cd ..» par «c.» : nous allons faire du tuning !

À ce stade de l'article, vous devriez avoir un fichier ~/.gitconfig. Sinon, créez-le et demandez-vous si vous n'avez pas sauté la première partie. C'est ce fichier que nous allons modifier sauvagement :

[user]
name = Gerard
email = gerard.menvuca@wanadold.py
[alias]
st = status
ci = commit
cim = commit -m
cam = commit -Am
cia = commit --amend
yolo = commit -Am "deal with it"
br = branch
co = checkout
df = diff
dfs = diff --staged
lg = log -p
lo = log --decorate
lol = log --graph --decorate --pretty=oneline --abbrev-commit
lola = log --graph --decorate --pretty=oneline --abbrev-commit --all
pa = add --patch
a = add
ap = add -p
ai = add -i
who = shortlog -s --
ps = push
pl = pull
diffstat = diff --stat -r
ss = stash
sa = stash apply
sp = stash pop
sl = stash list
sd = stash drop

Ça c'est du tuning ! Les trois premières lignes devraient déjà exister, et d'autres aussi éventuellement (certaines ont été retirées dans l'exemple, car ce fichier contient généralement des valeurs privées pour authentification auprès de forges). Si elle n'existe pas déjà, créez la section [alias] et mettez-y des alias cools et musclés pour vous simplifier la vie. Des plus utiles au moins utiles (d'après l'auteur) :
    • ceux qui font économiser deux touches : «a», «df», «ps», «pl», voire 4 pour «ci», «br» et «co»
    • ceux qui évitent d'oublier des caractères éloignés sur le clavier : «cim», «cia», «ap», «dfs»
    • ceux qui permettent d'être efficace avec les stashs : «s*» (voir les liens plus bas)
    • ceux qui font éviter des options zarbi : «lol», «who»
    • ceux qu'on n'utilise jamais : «yolo», «who», «diffstat» (trop longue à taper…)

Voilà ! Libre à vous de recopier ces lignes, d'en enlever, d'en ajouter,… Bref, de les adapter à votre usage. Toutes ne sont pas utiles, mais elles donnent des idées et des pistes pour ceux qui veulent booster un peu leur productivité.

En prime, voilà quelques alias à mettre dans son bashrc/zshrc, qui se couplent assez bien avec le gitconfig :

alias 'g'='git'
alias 'gps'='g ps'
alias 'gpl'='g pl'
… etc
 

La carrosserie

Moulte chemins mènent à l'épanouissement de git dans votre outils de travail : si par chance vous n'utilisez pas d'interface graphique autre que celle de votre (émulateur de) terminal, il existe de nombreux modules, packages, layouts, hack propres et moins propres qui permettent à git. d'être un poil plus visuel, coloré ou rigolo.

De plus, de nombreux éditeurs de textes et IDE proposent des facilités d'usage de git. Entre autres :

  • vim propose trois modules d'intérêt (il y en a beaucoup d'autres) qui permettent de jouer avec git simplement : le puissant fugitive, l'utile extradite et l'informatif gitgutter,
  • bash et zsh peuvent être tunés avec oh my git.
  • python possède un module pour intéragir avec git : pygit.

Le mot de la fin

Tuner son git, c'est comme tuner son bashrc : c'est gagner beaucoup de temps en s'amusant, et c'est nécessaire pour faire de son environnement de travail un lieu productif et simple. Cela mériterait un article à part entière, mais ici nous nous contenterons de ce simple énoncé : que ce soit avec les alias ou les gestionnaires de versions, le principe est le même : faites bosser l'ordinateur, surtout pas vous !

Soyez fainéants, et vous vous simplifierez la vie.

Du git dans les Internets

  • des commandes utiles pour git dans la vie de tous les jours : via
  • tig, une interface en mode texte pour git
  • le principe de git stash expliqué avec pédagogie et en français : via
  • un petit billet simple et français sur la contribution (fork, pull request) : via
  • une explication par bitbucket des contributions [EN] : via
  • une question de fond par framasoft sur l'usage des forges comme github, problématique à laquelle répond en partie gitchain : via
  • comment contribuer sur github (ça s'applique à toutes les autres forges) : via
  • il existe aussi pas mal de tuto vidéo, essentiellement en anglais : via
  • stackoverflow propose un tag git, et un tas de réponses à des questions allant du simple au complexe : via (ou ici pour les gestionnaires de versions en général, ou pour une vision plus abstraite)
  • un article court mais intense sur les bases : via
  • il existe des centaines de cheatsheet sur internet, certaines interactives : via via via (merci hedjour)
  • plus d'info sur sourceforge et ses nouvelles manies [EN] : via (réponse de sourceforge)
  • configurer git pour utiliser un éditeur particulier : via
  • si vous voulez jouer avec git : via ou découvrir un autre usage des branches : via (merci Yoann M.)

Conclusion

Nous avons vu comment cloner un projet, faire des modifications sur l'un ou l'autre des clones (voire sur les deux) et propager ces modifications en tenant compte de la nature décentralisée de git. Cela illustre trois des intérêts majeurs des gestionnaires de versions : permettre à plusieurs personnes de partager une base commune, la modifier selon leurs besoins tout en en faisant profiter les autres, et enfin de détecter les conflits qui peuvent survenir lors de modifications concurrentes.

Enfin, nous avons rapidement abordé l'aspect communautaire que permettent les gestionnaires de versions. Même si il y avait emphase sur git, c'est la même chose pour l'ensemble des gestionnaires de versions : puisque le partage de code est simple et structuré, il devient aisé de contribuer à un projet sans bouger de son pc. Et avec le principe des forks et des pull requests, vous avez les bases pour vous lancer dans la contribution effrénée et dans les tutoriaux plus avancés : il est bien sûr possible de faire des choses beaucoup plus raffinées. Ces deux articles présentent les principes de base permettant de se sortir de la plupart des situations pas trop tordues. Nous sommes convaincus qu'une utilisation même limitée d'un gestionnaire de versions vaut mieux que pas de gestionnaire de versions du tout.

Remerciements

Mer­ci à Estel, nal­lias, hed­jour et Yoann M pour les com­men­taires et dis­cus­sions lors de l'édition de cet article.

À propos de cet article

Cet article et le pré­cé­dent ont étés adap­tés à par­tir d'un cours don­né par Lucas Bour­neuf.

Il a été rédi­gé par Lucas Bour­neuf et Oli­vier Dame­ron.



Pour continuer la lecture :


Commentaires

5 réponses à “Git : cloner un projet, travailler à plusieurs et créer des branches”

  1. Avatar de Pierre

    Je m'insurge devant la poli­tique pro-vim de ce billet il n'a été fait nul­le­ment men­tion d'emacs et des mer­veilleux module pour jouer avec git dans emacs. Tout est là http://​emacs​wi​ki​.org/​e​m​a​c​s​/​Git ça va du plus simple et user friend­ly au plus com­plexe.

    1. Avatar de Olivier
      Olivier

      C'est parce que c'est un article sérieux, Môs­sieur, et quitte à expli­quer autant ne pas par­ler des édi­teurs en plas­tique

    2. Avatar de lucas

      Ce n'est pas si pro-vim que ça : on aborde nano dans la par­tie pré­cé­dente… (c'est l'éditeur par défaut de git !)

Laisser un commentaire