Passer au contenu principal
Ce jeu de données contient l’ensemble des commits et des modifications du dépôt ClickHouse. Il peut être généré à l’aide de l’outil git-import natif distribué avec ClickHouse. Les données générées fournissent un fichier tsv pour chacune des tables suivantes :
  • commits - commits avec statistiques.
  • file_changes - fichiers modifiés dans chaque commit, avec des informations sur la modification et des statistiques.
  • line_changes - chaque ligne modifiée dans chaque fichier modifié de chaque commit, avec toutes les informations sur la ligne ainsi que les informations sur la modification précédente de cette ligne.
Au 8 novembre 2022, chaque TSV présente approximativement la taille et le nombre de lignes suivants :
  • commits - 7.8M - 266 051 lignes
  • file_changes - 53M - 266 051 lignes
  • line_changes - 2.7G - 7 535 157 lignes

Génération des données

Cette étape est facultative. Les données sont mises à disposition gratuitement ; voir Téléchargement et insertion des données.
Cette opération prendra environ 3 minutes (au 8 novembre 2022, sur un MacBook Pro 2021) pour le dépôt ClickHouse. La liste complète des options disponibles est accessible via l’aide native de l’outil.
Cette aide fournit également le DDL de chacune des tables ci-dessus, par exemple.
Ces requêtes devraient fonctionner sur n’importe quel dépôt. N’hésitez pas à les essayer et à faire part de vos observations Quelques repères concernant les temps d’exécution (en novembre 2022) :
  • Linux - ~/clickhouse git-import - 160 mins

Téléchargement et insertion des données

Les données suivantes peuvent être utilisées pour reproduire un environnement fonctionnel. Vous pouvez également accéder à ce jeu de données sur play.clickhouse.com - voir Requêtes pour plus de détails. Les fichiers générés pour les dépôts suivants sont disponibles ci-dessous : Pour insérer ces données, préparez la base de données en exécutant les requêtes suivantes :
Insérez les données à l’aide de INSERT INTO SELECT et de la fonction s3. Par exemple, ci-dessous, nous insérons les fichiers ClickHouse dans chacune des tables correspondantes : commits
file_changes
line_changes

Requêtes

L’outil suggère plusieurs requêtes dans sa sortie d’aide. Nous y avons répondu, ainsi qu’à quelques autres questions d’intérêt. Ces requêtes sont présentées dans un ordre de complexité approximativement croissant, plutôt que dans l’ordre arbitraire de l’outil. Ce jeu de données est disponible sur play.clickhouse.com, dans la base de données git_clickhouse. Nous fournissons un lien vers cet environnement pour toutes les requêtes, en adaptant le nom de la base de données selon les besoins. Notez que les résultats sur play peuvent différer de ceux présentés ici en raison du moment de la collecte des données.

Historique d’un seul fichier

La plus simple des requêtes. Ici, nous affichons tous les messages de commit de StorageReplicatedMergeTree.cpp. Comme les plus récents sont probablement les plus intéressants, nous les affichons d’abord. play
Nous pouvons également examiner les modifications apportées aux lignes, en excluant les renommages, c’est-à-dire que nous n’afficherons pas les modifications antérieures à un événement de renommage lorsque le fichier existait sous un autre nom : play
Notez qu’il existe une variante plus complexe de cette requête, dans laquelle nous trouvons l’historique des commits ligne par ligne d’un fichier en tenant compte des renommages.

Trouver les fichiers actifs actuels

C’est important pour l’analyse ultérieure, lorsque nous voulons ne prendre en compte que les fichiers actuellement présents dans le dépôt. Nous estimons que cet ensemble correspond aux fichiers qui n’ont pas été renommés ni supprimés (puis rajoutés/re-renommés). Notez qu’il semble y avoir une rupture dans l’historique des commits concernant les fichiers des répertoires dbms, libs, tests/testflows/ lors de leur renommage. Nous les excluons donc également. play
Notez que cela permet de renommer des fichiers, puis de leur redonner leur nom d’origine. Nous agrégeons d’abord old_path pour dresser la liste des fichiers supprimés à la suite d’un renommage. Nous faisons ensuite l’union avec la dernière opération pour chaque path. Enfin, nous filtrons cette liste pour ne conserver que les éléments dont l’événement final n’est pas un Delete. play
Notez que nous avons ignoré l’importation de plusieurs répertoires lors de l’importation, à savoir : --skip-paths 'generated\.cpp|^(contrib|docs?|website|libs/(libcityhash|liblz4|libdivide|libvectorclass|libdouble-conversion|libcpuid|libzstd|libfarmhash|libmetrohash|libpoco|libwidechar_width))/' L’application de ce motif à git list-files renvoie 18155.
Notre solution actuelle constitue donc une estimation des fichiers actuellement actifs La différence ici s’explique par plusieurs facteurs :
  • Un renommage peut intervenir en même temps que d’autres modifications du fichier. Celles-ci sont répertoriées comme des événements distincts dans file_changes, mais avec le même horodatage. La fonction argMax n’a aucun moyen de les distinguer : elle choisit la première valeur. L’ordre naturel des insertions (le seul moyen de connaître le bon ordre) n’est pas conservé après l’union, si bien que des événements de modification peuvent être sélectionnés. Par exemple, ci-dessous, le fichier src/Functions/geometryFromColumn.h a subi plusieurs modifications avant d’être renommé en src/Functions/geometryConverters.h. Notre solution actuelle peut sélectionner un événement Modify comme dernière modification, ce qui conduit à conserver src/Functions/geometryFromColumn.h.
play
  • Historique des commits incomplet - événements de suppression manquants. Origine et cause à déterminer.
Ces différences ne devraient pas avoir d’incidence notable sur notre analyse. N’hésitez pas à proposer une version améliorée de cette requête.

Lister les fichiers ayant le plus de modifications

En se limitant aux fichiers actuels, nous considérons que le nombre de modifications correspond à la somme des suppressions et des ajouts. play

Quel jour de la semaine les commits sont-ils généralement effectués ?

play
Cela correspond bien à une certaine baisse de productivité le vendredi. C’est formidable de voir des gens faire des commits le week-end ! Un grand merci à nos contributeurs !

Historique du sous-répertoire/fichier - nombre de lignes, de commits et de contributeurs au fil du temps

Cela produirait un résultat de query volumineux, peu réaliste à afficher ou à visualiser sans filter. Nous permettons donc de filtrer un fichier ou un sous-répertoire dans l’Example suivant. Ici, nous regroupons par semaine à l’aide de la fonction toStartOfWeek — adaptez selon vos besoins. play
Ces données se prêtent bien à la visualisation. Ci-dessous, nous utilisons Superset. Pour les lignes ajoutées et supprimées : Pour les commits et les auteurs :

Lister les fichiers avec le plus grand nombre d’auteurs

Se limiter aux fichiers actuels uniquement. essayer

Lignes de code les plus anciennes du dépôt

Uniquement pour les fichiers actuels. essayer

Fichiers à l’historique le plus long

Limité aux fichiers actuels. essayer
Notre structure de données centrale, le Merge Tree, est évidemment en constante évolution et a déjà connu de nombreuses modifications !

Répartition des contributions à la documentation et au code au cours du mois

Lors de la capture des données, les modifications du dossier docs/ ont été exclues en raison d’un historique de commits très bruité. Les résultats de cette requête ne sont donc pas exacts. Écrivons-nous plus de documentation à certains moments du mois, par exemple autour des dates de release ? Nous pouvons utiliser la fonction countIf pour calculer un ratio simple, et visualiser le résultat à l’aide de la fonction bar. essayer
Peut-être un peu plus vers la fin du mois, mais dans l’ensemble, nous conservons une répartition assez uniforme. Là encore, ces résultats ne sont pas fiables en raison du filtrage appliqué par le filtre docs lors de l’insertion des données.

Auteurs à l’impact le plus varié

Ici, nous considérons la diversité comme le nombre de fichiers uniques auxquels un auteur a contribué. essayer
Voyons qui présente la plus grande diversité dans ses commits récents. Au lieu de limiter par date, nous nous limiterons aux N derniers commits d’un auteur (ici, nous avons utilisé 3, mais n’hésitez pas à modifier cette valeur) : essayer

Fichiers favoris d’un auteur

Ici, nous prenons comme exemple notre fondateur Alexey Milovidov et limitons notre analyse aux fichiers actuels. essayer
C’est logique, puisque c’est Alexey qui est chargé de maintenir le Change log. Mais si l’on utilisait le nom de base du fichier pour identifier ses fichiers les plus populaires, cela permettrait de prendre en compte les renommages et de mieux se concentrer sur les contributions au code. essayer
Cela reflète peut-être davantage ses centres d’intérêt.

Fichiers les plus volumineux avec le moins d’auteurs

Pour cela, nous devons d’abord identifier les fichiers les plus volumineux. L’estimer en reconstruisant intégralement chaque fichier à partir de l’historique des commits serait très coûteux ! Pour obtenir une estimation, en supposant que nous nous limitions aux fichiers actuels, nous additionnons les ajouts de lignes et soustrayons les suppressions. Nous pouvons ensuite calculer un ratio entre la taille et le nombre d’auteurs. essayer
Les dictionnaires de texte ne sont peut-être pas très pertinents, alors limitons-nous au code à l’aide d’un filtre sur l’extension de fichier ! essayer
Il y a ici un certain biais de récence : les fichiers plus récents ont eu moins d’occasions de faire l’objet de commits. Et si nous nous limitions aux fichiers âgés d’au moins 1 an ? essayer

Commits et répartition des lignes de code au fil du temps ; par jour de la semaine, par auteur ; pour des sous-répertoires spécifiques

Nous interprétons cela comme le nombre de lignes ajoutées et supprimées par jour de la semaine. Dans ce cas, nous nous concentrons sur le répertoire Functions essayer
Et selon l’heure de la journée, essayer
Cette répartition est cohérente, étant donné que la majeure partie de notre équipe de développement se trouve à Amsterdam. Les fonctions bar nous aident à visualiser ces répartitions : essayer

Matrice des auteurs montrant quels auteurs ont tendance à réécrire le code d’autres auteurs

Le sign = -1 indique une suppression de code. Nous excluons la ponctuation ainsi que l’insertion de lignes vides. essayer
Un diagramme de Sankey (SuperSet) permet de visualiser cela de façon claire. Notez que nous portons LIMIT BY à 3 afin d’obtenir, pour chaque auteur, les 3 personnes qui suppriment le plus de code, ce qui rend la visualisation plus variée. Alexey aime manifestement supprimer le code des autres. Excluons-le pour obtenir une vue plus équilibrée de la suppression de code.

Quel contributeur a le pourcentage le plus élevé pour chaque jour de la semaine ?

Si l’on considère uniquement le nombre de commits : Essayer
D’accord, notre contributeur de plus longue date — notre fondateur Alexey — semble avoir ici quelques avantages. Limitons notre analyse à l’année écoulée. essayer
C’est encore un peu simpliste et cela ne reflète pas le travail réel des personnes. Une meilleure métrique consisterait à identifier, pour chaque jour, le principal contributeur en proportion du travail total effectué au cours de l’année écoulée. Notez que nous accordons le même poids à la suppression et à l’ajout de code. essayer

Répartition de l’ancienneté du code dans l’ensemble du dépôt

Nous limitons l’analyse aux fichiers actuels. Pour rester concis, nous limitons les résultats à une profondeur de 2, avec 5 fichiers par dossier racine. Ajustez selon vos besoins. essayer

Quel pourcentage du code d’un auteur a été supprimé par d’autres auteurs ?

Pour répondre à cette question, nous devons diviser le nombre de lignes écrites par un auteur par le nombre total de lignes de cet auteur supprimées par un autre contributeur. essayer

Quels fichiers ont été réécrits le plus grand nombre de fois ?

L’approche la plus simple pour répondre à cette question consiste probablement à compter le plus grand nombre de modifications de lignes par chemin, en se limitant aux fichiers actuels, par exemple :
Cela ne rend toutefois pas compte de la notion de « réécriture », où une grande partie du fichier change dans un commit donné. Cela nécessite une requête plus complexe. Si l’on considère qu’il y a réécriture lorsque plus de 50 % du fichier est supprimé et 50 % ajouté, vous pouvez adapter la requête à votre propre définition de ce qui constitue une réécriture. La requête se limite aux fichiers actuels. Nous listons toutes les modifications de fichiers en regroupant par path et commit_hash, puis en renvoyant le nombre de lignes ajoutées et supprimées. À l’aide d’une fonction de fenêtre, nous estimons la taille totale du fichier à chaque instant en effectuant une somme cumulative, et nous estimons l’impact de chaque modification sur la taille du fichier par lines added - lines removed. À partir de cette mesure, nous pouvons calculer le pourcentage du fichier ajouté ou supprimé pour chaque modification. Enfin, nous comptons, pour chaque fichier, le nombre de modifications qui constituent une réécriture, c.-à-d. (percent_add >= 0.5) AND (percent_delete >= 0.5) AND current_size > 50. Notez que nous exigeons des fichiers de plus de 50 lignes afin d’éviter que les premières contributions à un fichier soient comptées comme des réécritures. Cela évite également un biais en faveur des très petits fichiers, qui sont plus susceptibles d’être réécrits. essayer

Quel jour de la semaine le code a-t-il le plus de chances de rester dans le dépôt ?

Pour cela, nous devons identifier une ligne de code de manière unique. Nous l’estimons ainsi (car une même ligne peut apparaître plusieurs fois dans un fichier) à l’aide du chemin et du contenu de la ligne. Nous exécutons une requête sur les lignes ajoutées, que nous joignons aux lignes supprimées, en filtrant les cas où ces dernières sont survenues plus récemment que les premières. Cela nous donne les lignes supprimées à partir desquelles nous pouvons calculer le temps écoulé entre ces deux événements. Enfin, nous agrégeons cet ensemble de données afin de calculer le nombre moyen de jours pendant lesquels les lignes restent dans le dépôt selon le jour de la semaine. essayer

Fichiers triés par ancienneté moyenne du code

Cette requête repose sur le même principe que Quel jour de la semaine le code a-t-il le plus de chances de rester dans le dépôt : identifier de manière unique une ligne de code à partir du chemin et du contenu de la ligne. Cela nous permet de mesurer le temps écoulé entre l’ajout et la suppression d’une ligne. Nous nous limitons toutefois aux fichiers actuels et au code uniquement, puis nous calculons la durée moyenne pour chaque fichier à partir de l’ensemble de ses lignes. essayer

Qui a tendance à écrire davantage de tests / de code CPP / de commentaires ?

Il existe plusieurs façons d’aborder cette question. En se concentrant sur le ratio code/tests, cette requête est relativement simple : compter le nombre de contributions dans les dossiers contenant tests et calculer le ratio par rapport au nombre total de contributions. Ici, nous limitons la requête aux utilisateurs ayant effectué plus de 20 modifications afin de nous concentrer sur les contributeurs réguliers et d’éviter un biais lié aux contributions ponctuelles. essayer
On peut représenter cette distribution sous forme d’histogramme. essayer
La plupart des contributeurs écrivent plus de code que de tests, comme on peut s’y attendre. Mais qui ajoute le plus de commentaires au code ? essayer
Notez que le tri est effectué selon les contributions au code. Le pourcentage est étonnamment élevé chez tous nos principaux contributeurs, et c’est en partie ce qui rend notre code si lisible.

Comment les commits d’un auteur évoluent-ils au fil du temps selon le pourcentage de code et de commentaires ?

Le calcul par auteur est trivial,
Idéalement, nous voulons toutefois voir comment cela évolue globalement pour l’ensemble des auteurs à partir du premier jour où ils commencent à commit. Réduisent-ils progressivement le nombre de commentaires qu’ils écrivent ? Pour calculer cela, nous déterminons d’abord le ratio de commentaires de chaque auteur au fil du temps, comme dans Qui a tendance à écrire davantage de tests / code CPP / commentaires ?. Nous le joignons ensuite à la date de début de chaque auteur, ce qui nous permet de calculer le ratio de commentaires par décalage hebdomadaire. Après avoir calculé la moyenne par décalage hebdomadaire pour l’ensemble des auteurs, nous échantillonnons ces résultats en sélectionnant une semaine sur 10. play
Fait encourageant, notre pourcentage de commentaires reste assez constant et ne se dégrade pas à mesure que les auteurs contribuent au fil du temps.

Quel est le délai moyen avant qu’un code ne soit réécrit, et quelle est la médiane (demi-vie de l’obsolescence du code) ?

Nous pouvons utiliser le même principe que dans Lister les fichiers qui ont été réécrits le plus grand nombre de fois ou par le plus grand nombre d’auteurs pour identifier les réécritures, mais en tenant compte de tous les fichiers. Une fonction de fenêtre est utilisée pour calculer le délai entre les réécritures pour chaque fichier. À partir de là, nous pouvons calculer une moyenne et une médiane sur l’ensemble des fichiers. play

Quel est le pire moment pour écrire du code, c’est-à-dire celui où il a le plus de chances d’être réécrit ?

Comme pour Quel est le délai moyen avant qu’un code soit réécrit et quelle est la médiane (demi-vie de l’obsolescence du code) ? et Lister les fichiers qui ont été réécrits le plus grand nombre de fois ou par le plus grand nombre d’auteurs, sauf qu’ici, nous agrégeons par jour de la semaine. Adaptez selon vos besoins, par exemple par mois de l’année. play

Quels auteurs ont le code le plus persistant ?

Nous définissons ici le caractère « persistant » par le temps pendant lequel le code d’un auteur reste en place avant d’être réécrit. Comme pour la question précédente Quel est le temps moyen avant que le code ne soit réécrit et la médiane (demi-vie de l’obsolescence du code) ?, nous utilisons la même mesure des réécritures, à savoir 50 % d’ajouts et 50 % de suppressions dans le fichier. Nous calculons le temps moyen de réécriture par auteur et ne prenons en compte que les contributeurs ayant plus de deux fichiers. play

Plus grand nombre de jours de commits consécutifs par auteur

Cette requête commence par calculer les jours où un auteur a effectué un commit. À l’aide d’une fonction de fenêtre, avec un partitionnement par auteur, nous pouvons calculer le nombre de jours entre ses commits. Pour chaque commit, si le temps écoulé depuis le commit précédent est d’un jour, nous le marquons comme consécutif (1), sinon 0, en stockant ce résultat dans consecutive_day. Les fonctions de tableau suivantes calculent ensuite, pour chaque auteur, sa plus longue séquence de 1 consécutifs. Tout d’abord, la fonction groupArray est utilisée pour regrouper toutes les valeurs consecutive_day d’un auteur. Ce tableau de 1 et de 0 est ensuite découpé sur les valeurs 0 en sous-tableaux. Enfin, nous calculons le sous-tableau le plus long. play

Historique des commits d’un fichier, ligne par ligne

Les fichiers peuvent être renommés. Dans ce cas, un événement de renommage est enregistré : la colonne path contient le nouveau chemin du fichier, tandis que old_path indique son emplacement précédent, p. ex. play
Cela complique la consultation de l’historique complet d’un fichier, car nous n’avons pas de valeur unique reliant toutes les modifications de ligne ou de fichier. Pour y remédier, nous pouvons utiliser des User Defined Functions (UDFs). Celles-ci ne peuvent pas, à l’heure actuelle, être récursives. Pour reconstituer l’historique d’un fichier, nous devons donc définir une série d’UDFs qui s’appellent explicitement les unes les autres. Cela signifie que nous ne pouvons suivre les renommages que jusqu’à une profondeur maximale : dans l’exemple ci-dessous, elle est de 5. Il est peu probable qu’un fichier soit renommé plus de fois que cela ; pour le moment, c’est donc suffisant.
En appelant file_path_history('src/Storages/StorageReplicatedMergeTree.cpp'), nous parcourons récursivement l’historique des renommages, chaque fonction appelant le niveau suivant avec old_path. Les résultats sont ensuite concaténés à l’aide de arrayConcat. Par exemple,
Nous pouvons maintenant utiliser cette capacité pour reconstituer l’ensemble des commits de tout l’historique d’un fichier. Dans cet exemple, nous présentons un commit pour chacune des valeurs de path.

Questions en suspens

Git blame

Il est particulièrement difficile d’obtenir un résultat exact, car il n’est actuellement pas possible de conserver un état dans les array functions. Cela sera possible avec arrayFold ou arrayReduce, qui permettent de conserver un état à chaque itération. Une solution approximative, suffisante pour une analyse à haut niveau, pourrait ressembler à ceci :
Les solutions exactes et améliorées sont les bienvenues ici.
Dernière modification le 2 juillet 2026