Archives du blog

Bitbucket vs Github

Vous savez peut-être que Bitbucket vous permet depuis fin 2011 d’héberger vos projets Git, comme Github, après avoir été un temple de Mercurial. Est-ce pour autant une bonne alternative ? Passons en revue leurs particularités !

Repository

Les interfaces du site sont extrêmement similaires, même si les designers de Github sont probablement légèrement meilleurs.

Chacun permet de créer des repositories publics et privés (mais pratique des prix différents, voir plus bas). Une fois le repository créé, on obtient une URL de remote, sur laquelle il est possible de pousser du code.

La navigation dans les sources se fait de façon identique sur le site, en parcourant les répertoires ou en utilisant les raccourcis claviers (par exemple `t` pour rechercher un fichier dans Github et `f` dans Bitbucket). Une fois le fichier trouvé, il est possible de le visionner, de faire un `blame`, de voir son historique ou de le modifier depuis le navigateur dans les deux cas.

Il est également possible de voir l’activité du projet, et les deux concurrents utilisent le fichier README de façon similaire pour décrire le projet.

L’historique des commits est un peu plus fonctionnel dans Bitbucket avec l’affichage possible de toutes les branches. La visualisation des branches est aussi intéressante, plus graphique que celle proposée par Github.

Fork/Pull Request/Code review

On trouve également le même mécanisme de fork (copie d’un repository dans votre espace utilisateur sur lequel vous avez tous les droits), de pull requests (demande d’intégration d’une fonctionnalité que vous avez codé dans un repo qui ne vous appartient pas) et de code reviews (possibilité de voir les différences introduites et de commenter le code).

Bitbucket ajoute quelques features « nice to have » : il est possible d’afficher un diff avec le fichier dans son ancienne version et dans sa nouvelle version côte à côte, d’affecter les code reviews à certains collaborateurs pour approbation et de ne pouvoir merger que si la pull request a été approuvée un certain nombre de fois. Ce petit workflow d’intégration n’est pas sans intérêt, même s’il est souvent pratiqué informellement sur Github. Autre petit avantage : lorsqu’une pull request ne peut être mergée pour cause de conflit, Bitbucket affiche clairement quels sont les fichiers et lignes en cause.

Administration

Peu de différence là encore : possibilité de créer des équipes et de leur affecter certains droits sur un repository. Bitbucket innove avec la possibilité de donner certains droits sur une branche spécifique.

Bug tracking

Chacun des concurrents propose un bug tracker intégré. Les fonctionnalités sont à peu près identiques :
– création d’anomalie avec assignation possible et version cible de correction.
– description au format markdown, avec images jointes.
– recherche des anomalies.
– lien possible entre anomalies (mais pas d’autocomplétion sur Bitbucket…).
– surveiller les anomalies.
– résolution automatique par commit.

Bitbucket propose en plus une criticité, une priorité et une gestion du workflow intégrée alors que Github compense en permettant la création dynamique de labels comme vous l’entendez. Le système de Github est flexible mais demande un peu plus de travail.

Une autre fonctionnalité intéressante réside dans le vote sur les issues. Là où Github ne permet toujours pas de voter, et où les commentaires ‘+1’ sont le seul moyen de manifester son intérêt, Bitbucket intègre directement le vote sur les issues, ce qui permet de jauger l’intérêt de la communauté pour une feature particulière.

La synchronisation avec d’autres bug trackers est généralement possible. L’intégration de Bitbucket avec Jira est mise en avant mais le même connecteur est utilisé pour Github et Bitbucket dans JIRA, les fonctionnalités sont donc possiblement équivalentes (mais je n’ai pas testé).

Wiki

Un wiki minimaliste est disponible pour les deux sites, avec syntaxe markdown, code highlighting et téléchargement possible (avec Git bien sûr) pour consultation offline.

Money, Money

Bitbucket mise sur un bon argument pour attirer les développeurs : les repositories privés. Alors que sur Github, le plan gratuit ne vous donne accès qu’à des repositories publics, Bitbucket autorise la création gratuite et illimitée de repositories privés. La restriction, car il faut bien une incitation à passer à la version payante, concerne le nombre maximum d’utilisateurs d’un repository privé : 5. Vous ne pouvez donc donner les droits d’accès à ces repo privés qu’à 5 de vos collègues : au-delà, il faudra mettre la main au portefeuille.

Les stratégies sont donc différentes en terme de marketing :
Github limite le nombre de repositories privés en fonction du prix (0 en gratuit, puis 5 pour 7$, 10 pour 12$…), le nombre de collaborateurs étant illimité.
Bitbucket permet de créer un nombre illimité de repositories privés mais limite le nombre de collaborateurs (5 en gratuit, puis 10 pour 10$, 25 pour 25$…).

Bitbucket a donc un argument intéressant pour une petite équipe créant un projet privé. A noter également la possibilité d’héberger vous même un Github Enterprise ou la suite professionelle de Bitbucket, nommée Stash, si la perspective d’avoir vos sources sur des serveurs américains vous trouble (mais franchement on ne voit pas pourquoi…). Ces outils vous donnent toutes les fonctionnalités de base plus la possibilité de s’intégrer avec votre système d’authentification interne.

Les prix sont tout de suite plus … entreprise! Github Enterprise démarre à 5000$ par an pour 20 utilisateurs, et est à peu près linéaire avec 250$ par utilisateur (100 utilisateurs donnent donc 25000$ par an, aïe). Bitbucket utilise là aussi une stratégie incitative avec une offre à seulement 10$ par mois pour 10 utilisateurs. La pente est ensuite plus raide mais les prix restent beaucoup plus abordables que ceux de Github avec 100 utilisateurs à 6000$ par an. A noter que Stash offre quelques fonctionnalités intéressantes comme une intégration poussée avec Jira (le bugtracker de la même société), ou les merges automatiques en cascade (un bugfix sur une ancienne release peut être automatiquement mergé sur les releases plus récentes).

Extras

Tous deux proposent une très bonne API REST, et des « hooks » qui permettent de s’intégrer avec tout ce que votre écosystème comporte d’important (les intégrations continues, dashboards, issue trackers…).
Bitbucket ne pose aucune limite sur la taille des fichiers, là où Github restreint à 100Mb par fichier.
Dans les petits bonus de Github, il ne faut pas oublier Github Pages, un support de nouveaux formats (fichier STL 3D, fichier GeoJSON) et une application mobile (même si c’est un peu anecdotique).

Communauté

Difficile de concurrencer Github, leader historique, dans le domaine. Avec près de 5 millions d’utilisateurs contre 1.5 million pour Bitbucket, la marge est encore grande. Les projets OSS phares hébergés sur Github sont très connus : Twitter Bootstrap, Node.js, Rails, JQuery, Angular.js, MongoDB, Linux Kernel. Bitbucket de son coté héberge les projets Atlassian, quelques projets de l’écosystème Python/Django et… pas grand chose d’autre de renommé. Mais surtout très difficile de trouver l’information, qui n’est pas mise en avant. A croire donc que les projets open source boudent le produit.

Les deux sites ont un petit aspect social, avec la possibilité de suivre des utilisateurs, de voir leur flux d’activité public, de mettre en favoris certains projets…

TL; DR;

Bitbucket a bien rattrapé son retard et ne souffre d’aucune lacune flagrante, au-delà de sa communauté moins nombreuse. Il possède même quelques fonctionnalités que l’on retrouverait avec plaisir sur Github.

Pour résumer :
– vous avez un projet open source ? Github sans réfléchir. L’exposition sera un ordre de magnitude supérieure.
– vous avez beaucoup de repositories privés, une petite équipe et peu d’argent ? Bitbucket est la solution économique. Vous pouvez même envisager Stash, leur solution pro.
– vous avez peu de repo privés et/ou de grandes équipes ? Github a un pricing plus intéressant.
– vous voulez héberger la solution chez vous ? Stash est beaucoup moins cher et ajoute quelques fonctionnalités intéressantes. Mais vous pouvez également regarder du côté des projets open source gratuits comme Gitlab par exemple.

Git config – les options indispensables

Si vous utilisez Git, vous connaissez probablement la commande ‘git config’ qui permet de paramétrer Git. Vous savez peut être qu’il existe 3 niveaux possibles de stockage de ces paramètres :
– system (tous les utilisateurs)
– global (pour votre utilisateur)
– local (pour votre projet courant)

Si un paramètre est défini localement et globalement avec des valeurs différentes, ce sera la valeur locale qui sera utilisée (le niveau le plus bas surcharge les niveaux supérieurs).

Il est possible d’afficher sa configuration avec :

    git config --list

On peut ajouter une valeur très simplement, par exemple mon user.name (utilisé pour l’auteur des commits) :

    git config --global user.name cexbrayat

Et l’on peut supprimer une valeur avec –unset :

    git config --unset user.name

Savez vous que l’on peut également définir des alias de commande? Par exemple, j’utilise souvent l’alias ‘lg’ pour une version de ‘log’ améliorée :

    git config alias.lg “log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)%Creset' --abbrev-commit”

Mais revenons à nos paramétrages. La doc de Git est très riche et il n’est pas simple de savoir quelles options peuvent être utiles. Le fichier ‘.gitconfig’ dans votre ‘home’ contient tous vos paramétrages (lorsque vous les avez ajouté en global). Ce fichier regroupe les clés par section, par exemple user.name et user.email sont regroupées dans une section [user]. Vous allez voir, rien de compliqué!

Voici les paramètres que j’aime utiliser :

    [user]
        email = cedric@ninja-squad.com
        name = cexbrayat

Ces deux paramètres sont nécessaires pour faire un commit et apparaîtront dans le log de vos collègues, à compléter dès l’installation donc!

    [core]
        editor = vim
        pager = less
        excludesfile = ~/.gitignore_global

La section core contient beaucoup d’options possibles. Parmi elles, ‘editor’ vous permet de choisir quel éditeur de texte sera utilisé (j’aime bien vim, si si je vous assure, mais vous pouvez très bien mettre SublimeText par exemple, avec ‘subl -w’), idem pour le pager utilisé par Git (pour afficher le log par exemple). Il faut savoir que Git pagine dès que l’affichage ne rentre pas dans votre écran : certains détestent ça et préfèrent utiliser ‘cat’ plutôt.

L’option `excludesfile` permet d’ignorer de façon globale certains fichiers en les précisant dans un `.gitignore` global. Le mien est inspiré de celui de Github.

    [alias]
        lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)%Creset' --abbrev-commit
        wdiff = diff --word-diff

Cette section contient tous les alias que vous ajouterez. Comme j’utilise oh-my-zsh et son plugin Git, j’ai déjà beaucoup d’alias disponibles (par exemple ‘gc’ pour ‘git commit’). On retrouve donc ‘lg’ pour un meilleur log et ‘wdiff’ pour un diff en mode ‘mot’ et pas ‘ligne’. Exemple avec ‘git diff’ :

    -:author:    Ninja Squad
    +:author:    Ninja Squad Team

Un seul mot ajouté, mais Git considère que la ligne entière est changée. Alors qu’avec ‘git wdiff’ :

    :author:    Ninja Squad{+ Team+}

Il reconnaît bien le seul ajout de mot sur la ligne!

    [color]
        ui = auto

Git est tout de suite plus convivial en ligne de commande avec un peu de couleur. Il est quasiment possible de définir pour chaque commande quelle couleur vous voulez, mais le plus simple est d’utiliser la clé ‘color.ui’ qui donne une valeur par défaut à toutes les commandes. A vous les branches en couleur, le log en couleur, le diff en couleur…

    [credential]
        helper = osxkeychain

Si vous utilisez un repo distant protégé par mot de passe (à tout hasard Github), il est possible de stocker votre mot de passe pour ne pas avoir à le saisir à chaque fois. Sur MacOS, le keychain se charge de le stocker pour vous.

    [clean]
        requireForce = false

Git possède une commande ‘clean’ pour supprimer les fichiers non suivis. Mettre requireForce à false permet d’éviter d’avoir à ajouter le flag ‘-f’.

    [diff]
        mnemonicprefix = true

Par défaut lorsque vous faites un ‘diff’, Git vous affiche par exemple :

    --- a/Git/git.asc
    +++ b/Git/git.asc

Ces préfixes a/ et b/ ne sont pas très parlants n’est ce pas? En passant ‘diff.mnemonicprefix’ à true, Git va afficher des préfixes plus logiques, par exemple ‘i’ pour index et ‘w’ pour le work tree (votre dossier de travail). Exemple :

    --- i/Git/git.asc
    +++ w/Git/git.asc

Pratique.

    [help]
        autocorrect = -1

L’une de mes fonctionnalités préférées! Si comme moi vous avez tendance à entrer ‘git rzset’ comme commande, vous avez déjà vu Git vous dire :

Did you mean this?
    reset

Mais ne rien faire pour autant! Et bien en activant l’autocorrection, git va remplacer votre commande par ‘git reset’ automatiquement. Il est possible de laisser un délai en donnant un entier positif comme valeur, ici, avec -1, la correction sera immédiate.

    [rerere]
        enabled = true

Si vous avez lu mon dernier article sur Git, vous savez déjà tout le bien que peut vous apporter ‘rerere’!

    [push]
        default = upstream

Actuellement (Git < 2.0), Git pousse toutes les branches modifiées qui existent sur le repo distant lors d’une commande ‘git push’ (valeur ‘matching’). Je trouve toujours ça un peu dangereux, car on a parfois oublié que l’on a des modifications sur une autre branche que celle en cours, et que l’on ne voudrait pas forcément partager immédiatement. A partir de Git 2.0, la nouvelle valeur sera ‘simple’ : seule la branche courante sera poussée si une branche du même nom existe. La valeur que je préfère est ‘upstream’, qui comme ‘simple’, permet de pousser seulement la branche locale, mais ce même si la branche distante ne possède pas le même nom.

    [rebase]
        autosquash = true
        autostash = true

Lorsqu’on effectue un rebase, Git propose la liste des commits concernés avec un verbe d’action pour chacun (en l’occurence ‘pick’). Ce verbe d’action peut être modifié pour devenir ‘reword’, ‘edit’, ‘squash’ ou ‘fixup’. Les deux derniers compressent deux commits en un seul. Parfois je sais dès que je commite qu’il devra fusionner avec le précédent. En préfixant le message de commit par ‘fixup!’, lors du rebase qui viendra, le commit sera automatiquement précédé par le verbe ‘fixup’ plutôt que ‘pick’!

L’option ‘rebase.autostash’ est toute récente (release 1.8.4.2 de Git) et permet d’automatiser une opération un peu pénible. Lorsque l’on lance un rebase, le répertoire de travail doit être sans modification en cours, sinon le rebase ne se lance pas. Il faut alors commiter ou stasher les modifications avant de recommencer le rebase. Avec cette option ‘rebase.autostash’, le rebase mettra dans automatiquement les modifications courantes dans le stash avant de faire le rebase, puis ré-appliquera les modifications ensuite.

    [pull]
        rebase = true

Par défaut, ‘pull’ effectue un ‘fetch’ suivi d’un ‘merge’. Avec l’option ‘rebase’, ‘pull’ effectuera un ‘fetch’ suivi d’un ‘rebase’.

Allez, pour votre culture, un petit tour des sections moins utiles mais parfois intéressantes.

    [advice]

Il est possible de désactiver tous les conseils que vous affiche Git en cas de problème. C’est un peu comme activer le mode difficile de Git, c’est vous qui voyez…

    [commit]

Il est possible de customiser le template de message de commit avec l’option ‘commmit.template’. Cela peut être pratique si vous avez des pratiques partagées par toute l’équipe (comme celles de l’équipe AngularJS).

Vous pouvez trouver mon ‘.gitconfig’ complet sur mon repo Github.

Et vous, quelles sont vos options préférées?

Git rerere – ma commande préférée

J’adore faire des rebases. Vraiment. C’est d’ailleurs une part très importante du workflow que nous vous conseillons. L’une des choses qui énerve parfois ceux qui font des rebases vient de la résolution de conflit répétitives qui peut survenir.

Pour rappel, un rebase va prendre tous les commits sur votre branche courante et les rejouer un à un sur une autre. Si vous avez une branche ‘topic1’ avec 5 commits et que vous effectuer un rebase sur la branche ‘master’ alors Git rejoue les 5 commits les uns après les autres sur la branche ‘master’.

Parfois, ces commits ont un conflit et le rebase s’arrête, le temps de vous laisser corriger. Mais il arrive que dès que Git passe au commit suivant, vous ayez à nouveau le conflit! Ce qui est très frustrant et pousse parfois à l’abandon du rebase. Cette situation arrive aussi lorsque l’on doit merger une modification en série sur des branches plus récentes. Par exemple, un bugfix sur la version 1.1, qui doit être mergé sur les branches 1.2 et 2.0. Si un conflit de merge apparaît lors du merge sur 1.2, il est frustrant de l’avoir à nouveau sur la branche 2.0 une fois celui-ci résolu.

Mais, vous vous en doutez, je vous parle de tout ça car Git propose une option à activer une seule fois pour être débarrassé de ces soucis.

Cette commande a le nom le plus improbable de toutes les commandes. rerere signifie reuse recorded resolution : cette commande permet à Git de se rappeler de quelle façon un conflit a été résolu et le résoudre automatiquement de la même façon la prochaine que ce conflit se présente.

Pour activer rerere, la seule chose à faire est de l’indiquer en configuration :

$ git config --global rerere.enabled true

Une fois activé, Git va se souvenir de la façon dont vous résolvez les conflits, sans votre intervention. Par exemple, avec un fichier nommé bonjour contenant sur master :

hello ninjas

Une branche french est créée pour la version française :

bonjour ninjas

Alors que sur master, une modification est appliquée

hello ninjas!

Si la branche french est mergée, alors un conflit survient :

Auto-merging bonjour
CONFLICT (content): Merge conflict in bonjour
Recorded preimage for 'bonjour'
Automatic merge failed; fix conflicts and then commit the result.

Si l’on édite le fichier, on a bien un conflit :

<<<<<<< HEAD
hello ninjas!
=======
bonjour ninjas
>>>>>>> french

Vous pouvez voir les fichiers en conflit surveillés par rerere :

$ git rerere status
bonjour

Vous corrigez le conflit, pour conserver :

bonjour ninjas!

Vous pouvez voir ce que rerere retient de votre résolution avec :

$ git rerere diff
--- a/bonjour
+++ b/bonjour
@@ -1,5 +1 @@
-<<<<<<<
-bonjour ninjas
-=======
-hello ninjas!
->>>>>>>
+bonjour ninjas!

Une fois terminée la résolution du conflit (add et commit), vous pouvez voir la présence d’un nouveau répertoire dans le dossier .git, nommé rr-cache, qui contient maintenant un dossier correspondant à notre résolution dans lequel un fichier conserve le conflit (preimage) et la résolution (postimage).

Maintenant, vous vous rendez compte que vous vous préféreriez un rebase plutôt qu’un merge. Pas de problème, on reset le dernier merge :

$ git reset --hard HEAD~1

On se place sur la branche french et on rebase.

$ git checkout french
$ git rebase master
...
Falling back to patching base and 3-way merge...
Auto-merging bonjour
CONFLICT (content): Merge conflict in bonjour
Resolved 'bonjour' using previous resolution.
Failed to merge in the changes.
Patch failed at 0001 bonjour ninjas!

Nous avons le même conflit que précédemment, mais cette fois on peut voir « Resolved bonjour using previous resolution. ». Et si nous ouvrons le fichier bonjour, le conflit a été résolu automatiquement!

Par défaut, rerere n’ajoute pas le fichier à l’index, vous laissant le soin de vérifier la résolution et de continuer le rebase. Il est possible avec l’option ‘rerere.autoupdate’ de faire cet ajout automatiquement à l’index (je préfère personnellement laisser cette option à ‘false’ et vérifier moi-même)!

A noter qu’il serait possible de remettre le fichier avec son conflit (si la résolution automatique ne vous convenait pas) :

$ git checkout --conflict=merge bonjour

Le fichier est alors à nouveau en conflit :

<<<<<<< HEAD
hello ninjas!
=======
bonjour ninjas
>>>>>>> french

Vous pouvez re-résoudre automatiquement le conflit avec :

$ git rerere

Magique!