Upstreams et forks git : Guide pratique et astuce sympa

Nicola Paolucci
Nicola Paolucci
Retour à la liste

Il existe des tonnes de guides utiles expliquant comment garder vos forks à jour avec les dépôts upstream. Dans ce blog, nous vous présenterons quelques façons dont la duplication interagit avec les dépôts en amont : les bases, les pièges et une astuce utile. Et, cerise sur le gâteau, je vous rendrai ensuite très jaloux ou très impatient, c'est vous qui déciderez. Cela vous intéresse ? Poursuivez votre lecture.

Le workflow de base pour actualiser le contenu et contribuer

Je tiens tout d'abord à vous présenter une configuration courante ainsi qu'un workflow des plus basiques pour interagir avec les dépôts upstream.

Toute configuration standard comporte généralement un remote origin et upstream. Ce dernier est le gardien du projet ou la source d'informations fiable à laquelle vous souhaitez contribuer.

Premièrement, vérifiez que vous avez déjà créé un remote pour le dépôt upstream, avec en prime une branche origin :

git remote -v

origin  git@bitbucket.org:my-user/some-project.git (fetch)
origin  git@bitbucket.org:my-user/some-project.git (push)

Si vous n'avez pas de branche upstream, il vous suffit de l'ajouter en utilisant la commande remote :

git remote add upstream git@bitbucket.org:some-gatekeeper-maintainer/some-project.git

Vérifiez que la branche distante est correctement ajoutée :

git remote -v
origin git@bitbucket.org:my-user/some-project.git (fetch)
origin git@bitbucket.org:my-user/some-project.git (push)
upstream git@bitbucket.org:some-gatekeeper-maintainer/some-project.git (fetch)
upstream git@bitbucket.org:some-gatekeeper-maintainer/some-project.git (push)

À présent, vous pouvez collecter les derniers changements du dépôt upstream avec fetch (répétez cette action à chaque fois que vous voulez recevoir des mises à jour) :

git fetch upstream

(Si le projet dispose de tags qui n'ont pas été mergés à la branche master, vous devez également exécuter : git fetch upsteam --tags)

En règle générale, vous souhaitez que votre branche master locale soit un parfait miroir de la branche upstream master et vous voulez travailler dans des branches de fonctionnalité (qui pourraient bien devenir des pull requests).

Pour le moment, que vous utilisiez merge ou rebase n'a pas d'importance, puisque le résultat sera le même. Utilisons merge :

git checkout master
git merge upstream/master

Lorsque vous souhaitez partager votre travail avec les mainteneurs upstream, vous créez une branche à partir de master, puis une branche de fonctionnalité. Une fois que vous êtes satisfait, vous la pushez vers votre dépôt distant.

Vous pouvez également utiliser rebase, puis merge pour vous assurer que la branche upstream dispose d'un ensemble de commits nettoyé (idéalement, un seul) à évaluer :

git checkout -b feature-x

#du travail et des commits se produisent
#du temps passe

git fetch upstream
git rebase upstream/master

Si vous devez écraser quelques commits pour n'en obtenir qu'un seul, c'est le moment d'utiliser une commande géniale : rebase interactive.

Une fois que vous aurez effectué ces opérations, publiez votre travail dans votre fork distant simplement en utilisant push :

git push origin feature-x

Un petit problème se posera si vous devez mettre à jour votre branche distante feature-x après l'avoir publiée, en raison du feedback des mainteneurs upstream. Différentes solutions s'offrent à vous :

  • créer une nouvelle branche avec vos propres mises à jour et celles de upstream :
  • faire un merge des mises à jour issues de upstream dans votre branche locale, qui enregistrera un commit de merge. Cela va encombrer le dépôt upstream ;
  • Faites un rebase de votre branche locale au-dessus des mises à jour issues de upstream et réalisez un force push sur votre branche distante :
git push -f origin feature-x

Personnellement, je préfère conserver l'historique aussi propre que possible et utiliser la troisième option, mais les workflows varient en fonction des équipes. ALERTE ÉVIDENTE ! Ne procédez de la sorte que lorsque vous travaillez avec votre propre fork. Vous ne devez JAMAIS procéder à la réécriture de l'historique des dépôts et des branches partagés.

Astuce du jour : nombres de commits avant/après une branche dans l'invite

Après un fetch, git status vous montrera combien de commits se trouvent avant ou après la branche remote vers laquelle la synchronisation à lieu. Ne serait-ce pas merveilleux si vous pouviez consulter ces informations avec cette bonne vieille invite de commande ? C'est la raison pour laquelle j'ai concocté un petit quelque chose avec bash.

Une fois la configuration effectuée, voici à quoi ressemblera votre invite :

nick-macbook-air:~/dev/projects/stash[1|94]$

Voici ce que vous devez ajouter à votre .bashrc ou à un fichier équivalent, c'est-à-dire une seule fonction :

function ahead_behind {
    curr_branch=$(git rev-parse --abbrev-ref HEAD);
    curr_remote=$(git config branch.$curr_branch.remote);
    curr_merge_branch=$(git config branch.$curr_branch.merge | cut -d / -f 3);
    git rev-list --left-right --count $curr_branch...$curr_remote/$curr_merge_branch | tr -s '\t' '|';
}

Vous pouvez enrichir votre invite bash avec cette nouvelle fonction ahead_behind pour obtenir l'effet souhaité. Le lecteur pourra la personnaliser à sa guise (pensez à ne pas trop encombrer la pointe).

Exemple d'invite :

export PS1="\h:\w[\$(ahead_behind)]$"

Fonctionnement interne

Pour ceux qui souhaitent plus d'informations et d'explications, voici comment cela fonctionne :

Nous obtenons le nom symbolique du HEAD courant, c'est-à-dire, de la branche courante :

curr_branch=$(git rev-parse --abbrev-ref HEAD);

Nous obtenons la branche distante vers laquelle la branche courante pointe :

curr_remote=$(git config branch.$curr_branch.remote);

Nous obtenons la branche sur laquelle ce remote doit être mergé (avec un petit truc Unix pour tout ignorer, même la dernière barre oblique /) :

curr_merge_branch=$(git config branch.$curr_branch.merge | cut -d / -f 3);

À présent, nous avons ce dont nous avons besoin pour compter le nombre de commits qui se trouvent avant ou après la branche.

git rev-list --left-right --count $curr_branch...$curr_remote/$curr_merge_branch | tr -s '\t' '|';

Nous utilisons l'ancienne commande Unix tr pour convertir TAB en séparateur |.

Conclusions

J'espère que cette petite présentation de upstream aura été utile aux lecteurs peu familiers de ce processus. Bonnes nouvelles : Bitbucket Server inclut la synchronisation des forks, ce qui évite aux développeurs de devoir garder leurs forks à jour, et Bitbucket Cloud permet une synchronisation facile en une étape. À tester !

Suivez-moi (@durdn) et l'incroyable équipe @Bitbucket pour plus d'infos passionnantes sur DVCS.

Prêt à découvrir Git ?

Essayez ce tutoriel interactif.

Démarrez maintenant