====== 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. ===== Pour contribuer ===== Le //repository// (repo) principal est sur le compte GitHub de celui qu'on appelle le mainteneur du projet. Pour nous, ce repo est [[https://github.com/Chocobozzz/PeerTube|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). ==== Copier le repo du mainteneur sur son propre GitHub : le fork ==== Il suffit de se rendre sur le //repository// du mainteneur, et de cliquer sur le bouton de //fork//, en haut à droite de l'écran : {{ :txs:peertube-a18:capture_du_2018-10-24_02-10-50.png?400 |}} ==== Travailler sur le code et soumettre une proposition : la Pull-Request ==== 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 Issues//" ((Référencées sous ce nom sur le //repository// de PeerTube, nom également utilisé par d'autres projets.)) 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-Reques//t va être //reviewed// par les autres contributeurs. Rester attentif aux retours qui pourront être faits sur le code dans les commentaires de la PR ((//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.)). ==== Synchroniser son fork avec le projet du mainteneur ==== 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. ==== Mettre à jour une branche locale avec sa branche locale d'origine ==== 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. [[txs:contrib:peertube-a18:github_tips#Résoudre des conflits|Voir la section dédiée.]] ===== Travailler à plusieurs sur une même issue ===== 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 : - 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// - Les autres doivent accepter l'invitation reçue par mail ou dans les notifications GitHub - Les autres clonent ensuite le repo et peuvent commencer à //pusher// dessus ===== Réécrire l'historique des commits ===== 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. ==== Préambule : éviter les conflits ==== 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. [[txs:contrib:peertube-a18:github_tips#Synchroniser son fork avec le projet 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 ==== Connaître le nombre de commits à réécrire ==== 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''. ==== Rassembler tous les commits en un seul ==== 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//. ==== Séparer l'unique commit en plusieurs ==== 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. ==== Publier les changements ==== 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. ===== Résoudre des conflits ===== À 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 ======= >>>>>>> dev:index.html On voit les deux versions en conflit, à nous de choisir celle que l'on souhaite garder en éditant le fichier : 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." ===== Tester le linter en local ===== 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 [[https://travis-ci.org/|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