Lorsque je travaille avec git dans une équipe utilisant des branches de fonctionnalités, j'ai souvent du mal à comprendre la structure des branches dans l'histoire.
Exemple:
Disons qu'il y avait une branche de fonctionnalité feature/make-coffee, et la correction de bugs s'est poursuivie sur master en parallèle à la branche de fonctionnalité.
L'histoire pourrait ressembler à ceci:
* merge feature/make-coffee
|\
| * small bugfix
| |
* | fix bug #1234
| |
| * add milk and sugar
| |
* | improve comments
| |
* | fix bug #9434
| |
| * make coffe (without milk or sugar)
| |
* | improve comments
|/
*
Problème
À première vue, je trouve difficile de dire de quel côté se trouve la branche de fonctionnalité. J'ai généralement besoin de parcourir plusieurs commentaires des deux côtés pour avoir une idée de qui est lequel. Cela devient plus compliqué s'il y a plusieurs branches d'entités en parallèle (en particulier si elles concernent des entités étroitement liées), ou s'il y a fusion dans les deux sens entre la branche d'entités et le maître.
En revanche, dans Subversion, c'est beaucoup plus facile car le nom de la branche fait partie de l'historique - je peux donc dire tout de suite qu'un commit a été initialement fait sur "feature/make-coffee".
Git pourrait faciliter cela en incluant le nom de la branche actuelle dans les métadonnées de commit lors de la création d'un commit (avec l'auteur, la date, etc.). Cependant, git ne le fait pas.
Y a-t-il une raison fondamentale pour laquelle cela n'est pas fait? Ou est-ce juste que personne ne voulait la fonctionnalité? Si c'est le dernier, y a-t-il d'autres façons de comprendre le but des branches historiques sans voir le nom?
Probablement parce que les noms de branche n'ont de sens que dans un seul référentiel. Si je suis sur le make-coffee
équipe et soumettre toutes mes modifications via un contrôleur d'accès, ma branche master
pourrait être attirée vers le contrôleur d'accès make-coffee-gui
branche, qu'il fusionnera avec son make-coffee-backend
branche, avant de rebaser en make-coffee
branche qui est fusionnée dans la branche centrale master
.
Même au sein d'un même référentiel, les noms de branche peuvent changer et changent à mesure que votre flux de travail évolue. master
pourrait changer plus tard pour être appelé development
, par exemple.
Comme CodeGnome l'a fait allusion, git a une philosophie de conception forte de ne pas incorporer quelque chose dans les données s'il n'est pas nécessaire. Les utilisateurs sont censés utiliser des options dans git log
et git diff
pour afficher les données à leur convenance après coup. Je recommande d'en essayer jusqu'à ce que vous trouviez un format qui vous convient mieux. git log --first-parent
, par exemple, n'affichera pas les validations d'une branche en cours de fusion.
--no-ff
Dans Git, un commit a des ancêtres, mais une "branche" n'est vraiment que la tête actuelle d'une ligne de développement. En d'autres termes, une validation est un instantané de l'arborescence de travail à un moment donné et peut appartenir à n'importe quel nombre de branches à la fois. Cela fait partie de ce qui rend la branche Git si légère par rapport aux autres DVCS.
Les validations Git ne contiennent pas les informations de branche car elles ne sont pas nécessaires pour l'historique de Git. Cependant, les fusions qui ne sont pas rapides peuvent certainement contenir des informations supplémentaires sur la branche qui a été fusionnée. Vous pouvez vous assurer que votre historique contient ces informations en passant toujours le --no-ff
signaler vos fusions. git-merge (1) dit:
--no-ff
Create a merge commit even when the merge resolves as a
fast-forward. This is the default behaviour when merging an
annotated (and possibly signed) tag.
Cela créera généralement un message de fusion similaire à Merge branch 'foo'
, vous pouvez donc généralement trouver des informations sur les "branches ancêtres" avec une ligne semblable à la suivante:
$ git log --regexp-ignore-case --grep 'merge branch'
Pourquoi les validations git ne contiennent pas le nom de la branche sur laquelle elles ont été créées?
Juste une décision de conception. Si vous avez besoin de ces informations, vous pouvez ajouter un hook prepare-commit-msg ou commit-msg pour l'imposer sur un seul référentiel.
Après la catastrophe de BitKeeper Linus Torvalds a développé git pour correspondre au processus de développement du noyau Linux. Là, jusqu'à ce qu'un patch soit accepté, il est révisé, édité, poli plusieurs fois (tout au long de Mail), signé (= éditer un commit), choisi par les cerises, errant vers les branches du lieutenant peut-être souvent rebasé, plus de modifications, cerise -picks et ainsi de suite jusqu'à ce qu'ils soient finalement fusionnés en amont par Linus.
Dans un tel flux de travail, il peut n'y avoir aucune origine spécifique d'un commit, et souvent un commit est juste créé dans une branche de débogage temporaire dans un référentiel aléatoire privé sans importance avec des noms de branche drôles, car git est distribué.
Peut-être que cette décision de conception est arrivée par hasard, mais elle correspond exactement au processus de développement du noyau Linux.
La réponse la plus simple est que les noms des branches sont éphémères. Et si vous deviez, par exemple, renommer une branche (git branch -m <oldname> <newname>
)? Qu'arriverait-il à tous les commits contre cette branche? Ou que se passe-t-il si deux personnes ont des succursales portant le même nom sur différents référentiels locaux?
La seule chose qui a un sens dans git est la somme de contrôle du commit lui-même. Il s'agit de l'unité de base du suivi.
L'information est là, vous ne la demandez tout simplement pas, et ce n'est pas exactement là.
Git stocke la somme de contrôle de la tête de chaque branche dans .git/refs/heads
. Par exemple:
... /. git/refs/heads $ ls test - rw-rw-r-- 1 michealt michealt 41 sept. 30 11:50 test .../.git/refs/heads $ cat test 87111111111111111111111111111111111111d4
(oui, je fais trembler mes sommes de contrôle git, ce n'est pas spécial, mais ce sont des référentiels privés que je regarde au moment où il n'y a pas besoin de fuite accidentelle).
En regardant cela et en retraçant chaque commit qui a ceci comme parent, ou le parent des parents ou le parent des parents ... vous pouvez découvrir dans quelles branches se trouve un commit donné. C'est juste un gros graphique à rendre.
Stocker où spécifiquement le nom d'une branche (qui peut changer comme mentionné ci-dessus) un commit est dans le commit lui-même est une surcharge supplémentaire sur le commit qui ne l'aide pas. Stockez le (s) parent (s) du commit et son bien.
Vous pouvez voir les branches en exécutant la commande git log avec l'indicateur approprié.
git log --branches --source --pretty = oneline --graph git log --all --source --pretty = oneline --graph
Il existe de nombreuses façons de choisir ce que vous voulez pour cela - regardez la documentation git log - le -all
section et les quelques options qui suivent.
* 87111111111111111111111111111111111111d4 test Fusionner la branche 'test1' dans le test |\ | * 42111111111111111111111111111111111111e8 mise à jour test1: ajouter des éléments * | dd1111111111111111111111111111111111111159 test Fusionner la branche 'test3' dans le test |\\
Ce "test", "test1" et "test2" sont les branches sur lesquelles ils se sont engagés.
Notez que la documentation du journal git est énorme et il est tout à fait possible d'en tirer presque tout ce que vous voulez.
Parce que dans Git, les commits comme "faire du café" sont considérés comme faisant partie des branches both lorsque la branche de fonctionnalité est fusionnée. Il y a même une commande pour vérifier que:
% git branch --contains @
feature/make-coffee
master
De plus, les succursales sont bon marché, locales et éthérées; ils peuvent être facilement ajoutés, supprimés, renommés, poussés et supprimés du serveur.
Git suit le principe que tout peut être fait, c'est pourquoi vous pouvez facilement supprimer une branche d'un serveur distant, et vous pouvez facilement réécrire l'historique.
Disons que j'étais dans la branche 'master', et j'ai fait deux commits: 'faire du café' et 'ajouter du lait et du sucre', puis j'ai décidé d'utiliser une nouvelle branche pour ça, donc j'ai réinitialisé 'master' sur 'Origin' /Maître'. Ce n'est pas un problème, toute l'histoire est toujours propre: "faire du café" et "ajouter du lait et du sucre" ne font pas partie du maître, seulement une fonction/faire du café.
Mercurial est le contraire; il ne veut pas changer les choses. Les branches sont permanentes, l'historique de réécriture est mal vu, vous ne pouvez pas simplement supprimer une branche du serveur.
En bref: Git vous permet de tout faire, Mercurial veut rendre les choses faciles (et rend vraiment difficile de vous laisser faire ce que vous voulez).