Contribuer à un projet open-source avec GitHub

Cette page rassemble les commandes et pratiques utiles pour contribuer à un projet open-source comme PeerTube. En particulier, nous verrons ce qu’il est nécessaire de faire pour travailler à plusieurs sur les mêmes issues, ce qui est souvent le cas au début d’un projet de type TX.

Le repository (repo) principal est sur le compte GitHub de celui qu’on appelle le mainteneur du projet. Pour nous, ce repo est ici. Pour contribuer au code du projet, l’idée est d’abord de copier le projet sur son propre GitHub. On peut ensuite travailler dessus, donc sans que cela n’impacte le repo du mainteneur, qui contient le code utilisé par l’application en production. Une fois qu’on est satisfait d’un bug corrigé ou d’une nouvelle fonctionnalité développée, on peut soumettre sa proposition au mainteneur (et aux autres contributeurs par la même occasion).

Il suffit de se rendre sur le repository du mainteneur, et de cliquer sur le bouton de fork, en haut à droite de l’écran :

La première chose à faire est de récupérer le projet en local. On clone le repository :

$ git clone git@github.com:Chocobozzz/PeerTube.git

Il s’agit maintenant de trouver une nouvelle fonctionnalité à développer ou un bug à corriger. Pour ce faire, il existe la section “Issues” sur le repo du mainteneur, notamment les “Good First Issues1) pour commencer. Une fois sa tâche trouvée, il peut être bon de vérifier que personne ne travaille déjà dessus, et le cas échéant de signaler dans la réponse à la description de l’issue que l’on compte travailler dessus. Ce sera aussi éventuellement le moment de se mettre d’accord avec le mainteneur du projet et les autres contributeurs sur la démarche à suivre.

On peut alors créer une branche de travail :

$ git checkout -b "addNewFeature"

On travaille sur cette branche, on ajoute des commits, etc. Une fois qu’on est satisfait, la Pull-Request (PR) se fait depuis son propre GitHub. On fournit un message de description, la Pull-Request va être reviewed par les autres contributeurs. Rester attentif aux retours qui pourront être faits sur le code dans les commentaires de la PR 2).

Pendant qu’on est en train de développer sur une branche de son fork, le projet distant continue à avancer avec les autres contributeurs. Il est donc nécessaire de garder son fork à jour.

On doit d’abord configurer son projet en local pour qu’il soit directement relié au repo du mainteneur :

$ git remote add upstream https://github.com/Chocobozzz/PeerTube
$ git remote -v
origin	git@github.com:clementbrizard/PeerTube.git (fetch)
origin  git@github.com:clementbrizard/PeerTube.git (push)
upstream  https://github.com/Chocobozzz/PeerTube (fetch)
upstream  https://github.com/Chocobozzz/PeerTube (push)

On affecte ici le label upstream au repository du mainteneur. On rappelle que l’on a forké ce repository dans nos projets Github personnels, puis que l’on a cloné ce fork sur notre machine. origin quant à lui, désigne notre fork. Ce pourrait être d’autres noms, de notre choix, mais c’est généralement ceux-ci qu’on utilise.

On peut ensuite mettre à jour son projet en local avec le repo distant :

$ git fetch upstream
$ git checkout master
$ git merge upstream/master

La première commande récupère toutes les branches du repo distant, la deuxième fait se déplacer sur la branche master locale, et la troisième effectue un merge entre les deux branches master locale et distante. Il ne reste plus qu’à pusher ces modifications sur son GitHub.

Ayant mis à jour son master local avec le master distant, il est nécessaire de mettre à jour les branches locales issues du master local :

$ git checkout branch_name
$ git rebase master
$ git push -f

Le rebase récupère les commits effectués sur master depuis la dernière mise à jour entre les deux branches, et réécrit les commits effectués sur la branche tout en haut de l’historique des commits. D’où la nécessité de forcer le push ensuite parce que l’historique des commits de la branche locale aura divergé avec celui de la branche sur son GitHub.

Lorsque nos modifications sont appliquées, il est possible que nos modifications entrent en conflit avec les modifications que l’on vient de récupérer. Voir la section dédiée.

En début de TX, il est intéressant de commencer par travailler ensemble sur les premières issues. Il est alors nécessaire de partager les mêmes branches de travail, ce qui permet de se faire des PR (Pull Request) mutuelles, des reviews de code, avant de soumettre une PR unique et collective au mainteneur.

La solution consiste à ce que tous clonent le fork d’un des membres du groupe :

  1. Celui qui va partager son fork ajoute les autres dans les collaborateurs de son fork, via l’onglet “Settings”. Il doit leur donner les droits pour le push
  2. Les autres doivent accepter l’invitation reçue par mail ou dans les notifications GitHub
  3. Les autres clonent ensuite le repo et peuvent commencer à pusher dessus

Quand on a terminé de travailler sur une PR, qu’elle est prête à être reviewed par les autres contributeurs, il est préférable que le travail réalisé soit bien lisible. Il arrive en effet souvent, par exemple, qu’on modifie d’abord un bout de code dans un premier commit, avant d’effacer cette modification dans un commit postérieur. On aimerait qu’au moment de la relecture, ce rétropédalage n’apparaisse pas. Pour ce faire, on peut réécrire l’historique des commits.

Pour être sûr que notre travail n’entre pas en conflit avec les PR mergées sur notre branche de base depuis que l’on a commencé notre PR, on peut effectuer un rebase. On se place d’abord sur notre branche de base, c’est-à-dire celle d’où on a fait partir notre branche de travail :

$ git checkout branche_de_base 

On met ensuite à jour cette branche de base avec son équivalent sur le repository du mainteneur. Voir la section dédiée.

Il ne reste ensuite plus qu’à faire un rebase de notre branche de travail sur sa branche de base que l’on vient de mettre à jour. Un rebase consiste à récupérer les changements effectués sur la branche de base, puis à appliquer nos changements (effectués sur la branche de travail) sur les changements récupérés. On parle de rebase au sens où on re-base littéralement nos modifications à la suite des derniers changements effectués sur la branche d’où est partie notre branche de travail :

$ git checkout branche_de_travail
$ git rebase branche_de_base

On va maintenant pouvoir savoir combien de commits sont concernés par notre réécriture. Il s’agit du nombre de commits de la PR. On se trouve déjà sur la branche de la PR

On peut afficher l’historique des commits :

$ git log

On peut alors compter le nombre de commits que l’on a réalisés sur notre PR. Après avoir réalisé l’étape précédente pour éviter les conflits, nos commits seront forcément tout en haut de l’historique. Notons ce nombre n.

En rassemblant tous les commits en un seul, on va ensuite pouvoir le séparer en autant de commits qu’on le souhaite. On passe pour ce faire par un rebase interactif :

$ git rebase -i HEAD~n

S’affiche alors la liste de nos n derniers commits. On peut alors indiquer une action à réaliser pour chaque commit. On laisse pick devant premier commit, celui placé tout en haut. Cela signifie qu’on garde les changements faits dans ce commit. Pour tous les commits suivants, on remplace pick par squash (ou s). Ainsi, les changements du deuxième commit vont être intégrés dans le premier, puis ceux du troisième et ainsi de suite jusqu’au n-ième commit. On sauvegarde ces changements et on quitte l’éditeur (en général vim ou nano). Ces squash successifs vont permettre de ne garder que la dernière version de nos modifs. En particulier, si une ligne ajoutée à un commit i a été retirée à un commit i+1, cette ligne n’apparaîtra pas dans les changements conservés dans l’unique commit.

Il ne reste plus qu’à partager l’unique commit obtenu en autant qu’on le souhaite.

On va d’abord annuler l’unique commit et récupérer tous les changements qu’il contient :

 git reset HEAD^

On peut voir que le commit n’apparaît plus dans l’historique :

$ git log 

On peut aussi voir qur tous les changements sont prêts à être ajoutés dans un commit, comme si on venait de tous les faire :

 git status 

On peut alors créer nos commits les uns après les autres :

$ git add file1 file2
$ git commit -m "1st step of pull request"
$ git add all_files_in_this_folder/
$ git commit -m "2nd step of pull request"
$ ...

Si on ne souhaite pas ajouter toutes les modifs effectuées dans un fichier dans un même commit, on peut utiliser la commande suivante :

$ git add -p 

Git va alors nous proposer chaque modification effectué dans les fichiers non encore ajoutés pour le prochain commit. On tape y pour ajouter le changement (nommé “hunk”), n pour le garder pour un autre commit, et s pour que Git sépare le changement courant en plusieurs.

La branche est propre en local, il faut maintenant qu’elle le soit aussi pour les contributeurs. On va devoir forcer le push, car on a réécrit l’historique :

$ git push -f 

Plus qu’à rédiger un joli message en Markdown pour résumer la PR aux autres contributeurs.

À plusieurs moments d’un projet géré avec Github, on peut faire face à des conflits entre les modifications réalisées par nous-mêmes, en local, et des modifications réalisées par les autres contributeurs. Ce problème intervient quand une même ligne de code a été modifiée. Il peut notamment intervenir quand on met à jour notre branche locale avec la branche distante sur le repository du mainteneur, ou plus simplement lors d’un pull.

Git va alors indiquer un message semblable au suivant :

$ git pull
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

On va alors ouvrir le fichier concerné dans un éditeur, vim par exemple :

vi index.html

Le conflit va être indiqué dans le fichier de la façon suivante :

<<<<<<< HEAD:index.html
<body id="home" lang="en">
=======
<body id="home" lang="fr">
>>>>>>> dev:index.html

On voit les deux versions en conflit, à nous de choisir celle que l’on souhaite garder en éditant le fichier :

<body id="home" lang="fr">

On enregistre ensuite ces modifications dans l’éditeur, ce qui nous faire revenir à Git dans le terminal. On ajoute la modification effectuée et on en fait un commit

$ git add index.html
$ git commit -m "Ajouts de lang="fr" sur l'élément body."

Ce point n’est pas directement lié à Github mais peut éviter de devoir amender ses commits juste pour un problème de syntaxe.

Dans de nombreux projets informatiques, un module de vérification de la syntaxe est présent et lancé lorsqu’un commit est push. C’est le cas sur PeerTube : le module Travis CI effectue une batterie de tests parmi lesquels la syntaxe. Plutôt que d’attendre d’envoyer son code sur Github pour savoir s’il passe les tests, il est possible de les lancer directement depuis son terminal. Dans le cas du test lint de PerTube par exemple, il suffit de lancer la commande suivante :

$ npm run travis -- lint

1)
Référencées sous ce nom sur le repository de PeerTube, nom également utilisé par d’autres projets.
2)
Pull Request est le terme utilisé sur Github, Gitlab utilise Merge Request, qui est plus juste puisqu’en réalité, notre branche de travail va bien être mergée dans la branche principale.
  • txs/contrib/peertube-a18/github_tips.txt
  • de 127.0.0.1