GitHub a popularisé le principe de pull-request et tous les autres outils de gestion de dépôt Git s’y sont mis : Bitbucket Cloud, Bitbucket Server (anciennement Stash), GitLab (sous le terme de merge-request).
Dans le principe c’est simple : pour contribuer à un projet sur l’une de ces plateformes :
- Forker le projet
- Créer une branche et travailler dessus
- Publier la branche sur son fork
- Créer la pull-request
Mais dans les faits, ça peut être un peu plus compliqué…
Nous allons voir étape par étape comment cela fonctionne et comment s’en servir au mieux.
Nous allons nous concentrer sur la situation de workflow triangulaire, c’est à dire de travail avec 3 dépôts :
- Un dépôt de référence, conventionnellement appelé upstream
C’est le dépôt du projet auquel nous voulons contribuer.
Nous n’avons que les droits en lecture dessus. - Un dépôt de fork, conventionnellement référencé origin
C’est une copie du dépôt de référence.
Nous avons tous les droits dessus. - Un dépôt local
C’est notre dépôt de travail.
Depuis la version 2.5, Git simplifie le travail avec ce type de workflow avec l’introduction de la référence <branch>@{push}.
Il existe déjà la référence <branch>@{upstream} qui permet de déterminer la branche distante traquée par une branche. Celle-ci est automatiquement définie lorsque vous faites un git checkout -b branch upstream_branch ou peut être explicitement définie par un git branch --set-upstream-to upstream_branch branch. Cette référence permet de déterminer la branche avec laquelle fusionner/rebaser lors d’un git pull. Elle permet aussi, si votre configuration push.default est à upstream, de déterminer la branche vers laquelle publier par défaut lors d’un git push.
Avec l’apparition de la référence <branch>@{push}, il est maintenant possible de mieux contrôler la branche vers laquelle publier par défaut. Celle-ci est configurée par les options de configuration :
remote.pushDefault(oubranch.<name>.pushRemotepour une branche spécifique) : pour indiquer le dépôt par défaut sur lequel publierpush.default: une valeur àcurrentva provoquer une publication par défaut sur une branche portant le même nom que la branche courante dans le dépôt de publication
Il est possible de vérifier la valeur de ces références via les commandes suivantes (ici des valeurs dans un cas de workflow triangulaire) :
$ git rev-parse --symbolic-full-name --abbrev-ref @{upstream}
upstream/master
$ git rev-parse --symbolic-full-name --abbrev-ref @{push}
origin/current_branch
Dans notre exemple, nous allons apporter une contribution au logiciel ‘example’ publié sur http://git.example.com/org/example.
Ce dépôt contient 2 commits C1 et C2 et une branche par défaut master positionnée sur C2 :
Côté contributeur
Faire une contribution
Tout d’abord, il faut forker le projet.
Cette étape est simple : il suffit d’aller sur l’interface web du projet auquel nous voulons contribuer, en l’occurrence http://git.example.com/org/example, et de cliquer sur le bouton ‘Fork’ (c’est a priori le même sur GitHub, GitLab ou Bitbucket).
Cela va avoir pour effet de créer un clone du dépôt mais côté serveur.
Ce dépôt va alors être accessible à une URL du genre http://git.example.com/contributor/example.
Il nous appartient et nous avons tous les droits dessus.
Ensuite nous clonons localement notre dépôt :
git clone http://git.example.com/contributor/example
Comme indiqué en introduction, nous allons configurer un peu pour publier par défaut sur notre fork origin :
git config remote.pushdefault origin # Publier par défaut sur le dépôt 'origin' git config push.default current # Publier par défaut sur une branche portant le même nom que la branche courante dans le dépôt de publication
Enfin, nous allons ajouter en remote le dépôt de référence avec le nom conventionnel upstream :
git remote add upstream http://git.example.com/org/example git fetch upstream
Voilà, nous pouvons enfin vraiment travailler ! Enfin presque…
Une bonne pratique est de toujours travailler sur une branche spécifique, jamais sur la branche cible de notre développement.
git checkout -b contribution upstream/master
Ainsi nous allons travailler sur la branche contribution qui va automatiquement traquer la branche upstream/master.
Nous pouvons maintenant faire les modifications de code et commiter :
# hack, hack, hack git add X Y Z git commit # hack again git add A B C git commit
Publier et proposer une contribution
Avant publication, nous pouvons vérifier les commits que nous allons publier et où :
$ git rev-parse --symbolic-full-name --abbrev-ref @{push}
origin/contribution
$ git log --graph --oneline --decorate --date-order --full-history @{push}..HEAD
* C4 (HEAD -> contribution) Commit C4
* C3 Commit C3
Pour publier ces modifications, il nous suffit alors simplement de faire un :
git push
Ainsi, git va publier sur la branche configurée en publication ou @{push}, or, puisque nous avons défini les options remote.pushdefault=origin et push.default=current, c’est origin/contribution.
Enfin, il faut passer par l’interface web du projet pour créer la pull-request depuis votre branche contribution de votre fork vers la branche master du dépôt de référence.
Corriger une pull-request
Si vous avez des retours sur votre pull-request et des corrections à faire, il suffit de réitérer l’étape précédente.
Simplement corriger et commiter :
# fix, fix, fix git add --update git commit
$ git log --graph --oneline --decorate --date-order --full-history @{push}..
* C5 (HEAD -> contribution) Commit C5
$ git push
La pull-request sera automatiquement mise à jour avec le(s) nouveau(x) commit(s).
Mettre à jour une pull-request
Il peut arriver que le dépôt de référence évolue pendant le temps de validation de votre pull-request, il vous faut alors la mettre à jour.
Se pose ici la question du merge vs. rebase mais je vais laisser d’autres répondre à ce débat, et opter pour ma recommandation dans ce cas : le rebase.
Vérifions ce que l’on va récupérer :
$ git log --graph --oneline --date-order --full-history ..@{upstream}
* C7 Commit C7
* C6 Commit C6
Et intégrons-le :
git pull --rebase
Cela va faire un rebase sur la dernière version de la branche traquée par votre branche locale ou @{upstream}, qui est upstream/master comme expliqué plus haut.
Et maintenant republions tout simplement.
git push
Mais ça coince… Car la publication n’est pas fast-forward.
En effet, origin/contribution n’est pas un ancêtre de contribution.
Mais ce n’est pas grave, nous voulons justement écraser avec notre nouvelle version, donc nous allons forcer un peu, mais en s’assurant quand même que nous n’allons pas écraser quelque chose que nous ne connaîtrions pas (ce qui est peu probable dans le cas de notre fork personnel) :
git push --force-with-lease
La pull-request sera encore une fois automatiquement mise à jour.
Côté mainteneur
Voyons maintenant comment gérer les pull-requests qui nous sont soumises.
Vérifier une pull-request
Si la pull-request n’est pas en conflit et que nous pouvons nous contenter de lire la pull-request en ligne et que nous avons des tests automatisés, tant mieux.
Par contre, si vous avez besoin de la retravailler en local, les différents outils proposent des références spéciales pour récupérer les pull-requests :
# GitHub
git fetch upstream refs/pull/{PR_NUMBER}/from:pull/{PR_NUMBER}
# GitLab
git fetch upstream refs/merge-requests/{PR_NUMBER}/head:pull/{PR_NUMBER}
# BitBucket Server
git fetch upstream refs/pull-requests/{PR_NUMBER}/from:pull/{PR_NUMBER}
# Bitbucket Cloud (obligé ici de taper directement sur le fork du contributeur)
git fetch http://git.example.com/{PR_CONTRIBUTOR}/example {PR_SOURCE_BRANCH}:pull/{PR_NUMBER}
Nous nous retrouvons alors avec une branche locale pull/{PR_NUMBER} sur laquelle nous pouvons travailler classiquement.
Intégrer une pull-request
Si tout se passe bien et que la pull-request peut être intégrée via l’interface web, parfait : simplement cliquer sur ‘fusionner’.
Mais parfois, si la branche cible a évolué de manière conflictuelle avec la pull-request (et que le contributeur ne peut la mettre à jour comme montré précédemment) ou que vous préférez simplement faire les choses vous-même, voici comment procéder.
Tout d’abord, récupérer la branche de la pull-request comme présenté dans le point précédent, ensuite, selon vos préférences, fusionner avec ou sans fast-forward, avec ou sans squashing :
git merge --no-ff pull/{PR_NUMBER}
Il ne reste plus qu’à publier :
git push upstream
La pull-request sera alors automatiquement fermée.
Conclusion
J’espère que vous comprenez un peu mieux comment fonctionne une pull-request et comment la manipuler de manière plus fine.

