Pular para o conteúdo principal
Este conjunto de dados contém todos os commits e as alterações do repositório ClickHouse. Ele pode ser gerado usando a ferramenta git-import nativa fornecida com o ClickHouse. Os dados gerados incluem um arquivo tsv para cada uma das seguintes tabelas:
  • commits - commits com estatísticas.
  • file_changes - arquivos alterados em cada commit, com informações sobre a alteração e estatísticas.
  • line_changes - cada linha alterada em cada arquivo alterado em cada commit, com informações completas sobre a linha e sobre a alteração anterior dessa linha.
Em 8 de novembro de 2022, cada TSV tinha aproximadamente o seguinte tamanho e número de linhas:
  • commits - 7.8M - 266,051 linhas
  • file_changes - 53M - 266,051 linhas
  • line_changes - 2.7G - 7,535,157 linhas

Gerando os dados

Isto é opcional. Disponibilizamos os dados gratuitamente — consulte Baixando e inserindo os dados.
Isso levará cerca de 3 minutos para ser concluído (em 8 de novembro de 2022, em um MacBook Pro 2021) no repositório do ClickHouse. Uma lista completa das opções disponíveis pode ser obtida na ajuda interna das ferramentas.
Esta ajuda também fornece o DDL de cada uma das tabelas acima, por exemplo.
Estas consultas devem funcionar em qualquer repositório. Fique à vontade para explorar e relatar suas descobertas Algumas diretrizes sobre os tempos de execução (em novembro de 2022):
  • Linux - ~/clickhouse git-import - 160 min

Baixando e inserindo os dados

Os dados a seguir podem ser usados para reproduzir um ambiente funcional. Como alternativa, esse conjunto de dados está disponível em play.clickhouse.com - consulte Consultas para mais detalhes. Os arquivos gerados para os repositórios a seguir podem ser encontrados abaixo: Para inserir esses dados, prepare o banco de dados executando as consultas a seguir:
Insira os dados usando INSERT INTO SELECT e a função s3. Por exemplo, a seguir, inserimos os arquivos do ClickHouse em suas respectivas tabelas: commits
file_changes
line_changes

Consultas

A ferramenta sugere várias consultas por meio da saída de ajuda. Respondemos a elas, além de incluir algumas perguntas suplementares de interesse. Essas consultas seguem uma complexidade aproximadamente crescente, em contraste com a ordem arbitrária da ferramenta. Esse conjunto de dados está disponível em play.clickhouse.com no banco de dados git_clickhouse. Fornecemos um link para esse ambiente em todas as consultas, adaptando o nome do banco de dados conforme necessário. Observe que os resultados no play podem variar em relação aos apresentados aqui devido a diferenças no momento da coleta dos dados.

Histórico de um único arquivo

A consulta mais simples de todas. Aqui, vemos todas as mensagens de commit de StorageReplicatedMergeTree.cpp. Como essas mensagens provavelmente são mais interessantes, ordenamos da mais recente para a mais antiga. play
Também podemos revisar as alterações linha a linha, excluindo renomeações — ou seja, não mostraremos alterações anteriores a um evento de renomeação quando o arquivo existia com outro nome: play
Note que existe uma variante mais complexa desta consulta, na qual é possível encontrar o histórico de commits linha por linha de um arquivo, considerando renomeações.

Encontre os arquivos ativos atuais

Isso é importante para análises posteriores, quando quisermos considerar apenas os arquivos atuais no repositório. Estimamos esse conjunto como os arquivos que não foram renomeados nem excluídos (e depois readicionados/re-renomeados). Observe que parece ter havido uma quebra no histórico de commit em relação aos arquivos nos diretórios dbms, libs, tests/testflows/ durante suas renomeações. Portanto, também os excluímos. play
Observe que isso permite que arquivos sejam renomeados e depois renomeados de volta para seus nomes originais. Primeiro, agregamos old_path para obter uma lista dos arquivos excluídos como resultado da renomeação. Em seguida, unimos isso à última operação de cada path. Por fim, filtramos essa lista para manter apenas os casos em que o evento final não é um Delete. play
Observe que ignoramos a importação de vários diretórios durante esse processo, ou seja, --skip-paths 'generated\.cpp|^(contrib|docs?|website|libs/(libcityhash|liblz4|libdivide|libvectorclass|libdouble-conversion|libcpuid|libzstd|libfarmhash|libmetrohash|libpoco|libwidechar_width))/' Ao aplicar esse padrão a git list-files, são reportados 18155.
Nossa solução atual é, portanto, uma estimativa dos arquivos em uso no momento A diferença aqui é causada por alguns fatores:
  • Uma renomeação pode ocorrer junto com outras modificações no arquivo. Elas são listadas como eventos separados em file_changes, mas com o mesmo horário. A função argMax não tem como distinguir isso — ela escolhe o primeiro valor. A ordenação natural das inserções (a única forma de saber a ordem correta) não é preservada na união, então eventos de modificação podem acabar sendo selecionados. Por exemplo, abaixo, o arquivo src/Functions/geometryFromColumn.h passa por várias modificações antes de ser renomeado para src/Functions/geometryConverters.h. Nossa solução atual pode selecionar um evento Modify como a alteração mais recente, fazendo com que src/Functions/geometryFromColumn.h seja mantido.
play
  • Histórico de commits inconsistente - eventos de exclusão ausentes. Origem e causa ainda a definir.
Essas diferenças não devem afetar significativamente nossa análise. Sugestões de melhorias para esta consulta são bem-vindas.

Listar os arquivos com mais modificações

Considerando apenas os arquivos atuais, o número de modificações é a soma de exclusões e adições. play

Em que dia da semana os commits costumam ocorrer?

play
Isso faz sentido, com uma certa queda de produtividade nas sextas-feiras. É ótimo ver gente fazendo commit no código nos fins de semana! Muito obrigado aos nossos colaboradores!

Histórico de subdiretório/arquivo — número de linhas, commits e colaboradores ao longo do tempo

Isso produziria um resultado de consulta muito grande, pouco prático de exibir ou visualizar sem filtro. Por isso, no exemplo a seguir, permitimos filtrar um arquivo ou subdiretório. Aqui, agrupamos por semana usando a função toStartOfWeek — adapte conforme necessário. play
Esses dados podem ser visualizados com facilidade. Abaixo, usamos o Superset. Para linhas adicionadas e removidas: Para commits e autores:

Liste os arquivos com o maior número de autores

Limite aos arquivos atuais. play

Linhas de código mais antigas do repositório

Limitado apenas aos arquivos atuais. play

Arquivos com o histórico mais longo

Limitado aos arquivos atuais. play
Nossa principal estrutura de dados, a Merge Tree, está obviamente em constante evolução, com um longo histórico de alterações!

Distribuição de colaboradores entre documentação e código ao longo do mês

Durante a captura dos dados, as alterações na pasta docs/ foram filtradas devido a um histórico de commits muito desorganizado. Portanto, os resultados desta consulta não são exatos. Escrevemos mais documentação em determinados períodos do mês, por exemplo, perto das datas de lançamento? Podemos usar a função countIf para calcular uma proporção simples e visualizar o resultado com a função bar. play
Talvez haja um pouco mais no fim do mês, mas, no geral, mantemos uma distribuição bem uniforme. Novamente, isso não é confiável devido ao filtro docs aplicado durante a inserção de dados.

Autores com o impacto mais diversificado

Consideramos diversidade aqui como o número de arquivos únicos para os quais um autor contribuiu. play
Vamos ver quem tem os commits mais variados no trabalho mais recente. Em vez de limitar por data, vamos considerar os últimos N commits de um autor (neste caso, usamos 3, mas fique à vontade para alterar): play

Arquivos favoritos de um autor

Aqui selecionamos nosso fundador Alexey Milovidov e limitamos a análise aos arquivos existentes atualmente. play
Isso faz sentido porque Alexey é o responsável por manter o changelog. Mas e se usarmos o nome-base do arquivo para identificar seus arquivos mais populares? Isso permite renomeações e deve destacar as contribuições de código. play
Isso talvez reflita mais as áreas de interesse dele.

Maiores arquivos com menos autores

Para isso, primeiro precisamos identificar os maiores arquivos. Estimar isso por meio de uma reconstrução completa de cada arquivo, com base no histórico de commits, seria muito custoso! Para fazer essa estimativa, supondo que nos restrinjamos aos arquivos atuais, somamos as adições de linhas e subtraímos as exclusões. Em seguida, podemos calcular uma proporção entre o tamanho e o número de autores. play
Talvez dicionários de texto não sejam muito realistas, então vamos nos restringir apenas ao código usando um filtro por extensão de arquivo! play
Há um certo viés de recência aqui: arquivos mais novos tiveram menos oportunidades de receber commits. E se restringirmos aos arquivos com pelo menos 1 ano? play

Distribuição de commits e linhas de código ao longo do tempo; por dia da semana, por autor; para subdiretórios específicos

Interpretamos isso como o número de linhas adicionadas e removidas por dia da semana. Neste caso, focamos no diretório Functions play
E por hora do dia, abrir
Essa distribuição faz sentido, já que a maior parte da nossa equipe de desenvolvimento está em Amsterdã. As funções bar nos ajudam a visualizar essas distribuições: play

Matriz de autores que mostra quais autores tendem a reescrever o código de outros autores

sign = -1 indica uma exclusão de código. Excluímos a pontuação e a inserção de linhas vazias. play
Um gráfico de Sankey (SuperSet) permite visualizar isso de forma bem clara. Observe que aumentamos nosso LIMIT BY para 3, para obter os 3 principais responsáveis pela remoção de código de cada autor e dar mais variedade à visualização. Alexey claramente gosta de remover código de outras pessoas. Vamos excluí-lo para ter uma visão mais equilibrada da remoção de código.

Quem tem a maior porcentagem de contribuição por dia da semana?

Se considerarmos apenas o número de commits: play
Certo, nosso fundador Alexey, por ser o colaborador mais antigo, pode ter alguma vantagem aqui. Vamos limitar nossa análise ao último ano. play
Isso ainda é um pouco simplista e não reflete o trabalho das pessoas. Uma métrica melhor talvez seja identificar quem foi o principal colaborador em cada dia, como fração do trabalho total realizado no último ano. Observe que tratamos a remoção e a adição de código da mesma forma. testar

Distribuição da idade do código em todo o repositório

Limitamos a análise aos arquivos atuais. Para ser breve, restringimos os resultados a uma profundidade de 2, com 5 arquivos por pasta raiz. Ajuste conforme necessário. play

Que porcentagem do código de um autor foi removida por outros autores?

Para esta pergunta, precisamos do número de linhas escritas por um autor dividido pelo número total de linhas que esse autor teve removidas por outro colaborador. abrir

Listar os arquivos que foram reescritos mais vezes?

A abordagem mais simples para responder a essa pergunta é contar o número de modificações de linha por caminho (limitando-se aos arquivos atuais), por exemplo:
Isso, no entanto, não capta a ideia de um “rewrite”, em que uma grande parte do arquivo muda em um commit. Isso exige uma consulta mais complexa. Se considerarmos um rewrite como um caso em que mais de 50% do arquivo é excluído e 50% é adicionado, você pode ajustar a consulta de acordo com a sua própria interpretação do que caracteriza isso. A consulta é limitada apenas aos arquivos atuais. Listamos todas as alterações de arquivos agrupando por path e commit_hash, retornando o número de linhas adicionadas e removidas. Usando uma função de janela, estimamos o tamanho total do arquivo em qualquer ponto no tempo fazendo uma soma cumulativa e estimando o impacto de cada alteração no tamanho do arquivo como lines added - lines removed. Com essa estatística, podemos calcular a porcentagem do arquivo que foi adicionada ou removida em cada alteração. Por fim, contamos o número de alterações de arquivo que constituem um rewrite por arquivo, ou seja, (percent_add >= 0.5) AND (percent_delete >= 0.5) AND current_size > 50. Observe que exigimos que os arquivos tenham mais de 50 linhas para evitar que contribuições iniciais em um arquivo sejam contadas como um rewrite. Isso também evita um viés em relação a arquivos muito pequenos, que podem ter maior probabilidade de serem reescritos. executar

Em que dia da semana o código tem mais chances de permanecer no repositório?

Para isso, precisamos identificar uma linha de código de forma única. Fazemos essa estimativa (já que a mesma linha pode aparecer várias vezes em um arquivo) usando o path e o conteúdo da linha. Fazemos uma consulta das linhas adicionadas, juntando-a com as linhas removidas e filtrando os casos em que estas últimas ocorreram mais recentemente do que as primeiras. Isso nos dá as linhas excluídas, a partir das quais podemos calcular o tempo entre esses dois eventos. Por fim, agregamos esse conjunto de dados para calcular o número médio de dias que as linhas permanecem no repositório por dia da semana. play

Arquivos ordenados por idade média do código

Esta consulta usa o mesmo princípio de Em que dia da semana o código tem a maior chance de permanecer no repositório — buscando identificar de forma única uma linha de código com base no caminho e no conteúdo da linha. Isso nos permite identificar quanto tempo se passa entre a adição e a remoção de uma linha. No entanto, filtramos apenas o código dos arquivos atuais e calculamos, para cada arquivo, a média desse tempo entre suas linhas. play

Quem tende a escrever mais testes / código em CPP / comentários?

Há algumas maneiras de abordar essa questão. Focando na relação entre código e testes, esta consulta é relativamente simples: contar o número de contribuições para pastas que contêm tests e calcular a proporção em relação ao total de contribuições. Aqui, limitamos a consulta a usuários com mais de 20 alterações para focar em contribuidores regulares e evitar viés de contribuições pontuais. play
Podemos representar essa distribuição em um histograma. abrir
A maioria dos colaboradores escreve mais código do que testes, como era de se esperar. Mas quem adiciona mais comentários ao contribuir com código? play
Note que ordenamos por contribuições de código. A porcentagem é surpreendentemente alta entre todos os nossos principais colaboradores e é parte do que torna nosso código tão legível.

Como os commits de um autor variam ao longo do tempo em relação ao percentual de código/comentários?

Calcular isso por autor é trivial,
Idealmente, porém, queremos ver como isso muda de forma agregada, considerando todos os autores desde o dia do primeiro commit. Eles reduzem lentamente o número de comentários que escrevem? Para calcular isso, primeiro determinamos a proporção de comentários de cada autor ao longo do tempo — de forma semelhante a Quem tende a escrever mais testes / código CPP / comentários?. Em seguida, relacionamos isso à data de início de cada autor, o que nos permite calcular a proporção de comentários por deslocamento em semanas. Depois de calcular a média por deslocamento em semanas para todos os autores, amostramos esses resultados selecionando cada 10ª semana. play
De forma animadora, nossa porcentagem de comentários se mantém bastante constante e não diminui à medida que os autores contribuem por mais tempo.

Qual é o tempo médio até que o código seja reescrito e a mediana (meia-vida da degradação do código)?

Podemos usar o mesmo princípio de Listar os arquivos que foram reescritos mais vezes ou pelo maior número de autores para identificar reescritas, mas considerando todos os arquivos. Uma função de janela é usada para calcular o tempo entre reescritas de cada arquivo. A partir disso, podemos calcular a média e a mediana entre todos os arquivos. play

Qual é o pior momento para escrever código, no sentido de que ele tem a maior chance de ser reescrito?

Semelhante a Qual é o tempo médio até que o código seja reescrito e a mediana (meia-vida da degradação do código)? e Liste os arquivos que foram reescritos o maior número de vezes ou pelo maior número de autores, exceto que aqui agregamos por dia da semana. Ajuste conforme necessário, por exemplo, para o mês do ano. play

De quais autores o código é mais duradouro?

Definimos “duradouro” como o tempo que o código de um autor permanece antes de ser reescrito. Semelhante à pergunta anterior Qual é o tempo médio até que o código seja reescrito e a mediana (meia-vida da degradação do código)? — usando a mesma métrica para reescritas, isto é, 50% de adições e 50% de exclusões no arquivo. Calculamos o tempo médio de reescrita por autor e consideramos apenas colaboradores com mais de dois arquivos. play

Maior sequência de dias consecutivos com commits por autor

Esta consulta exige primeiro que calculemos os dias em que um autor fez commit. Usando uma função de janela, com particionamento por autor, podemos calcular o intervalo em dias entre seus commits. Para cada commit, se o tempo desde o commit anterior foi de 1 dia, marcamos como consecutivo (1); caso contrário, 0 — armazenando esse resultado em consecutive_day. Em seguida, as funções de array calculam a maior sequência de 1s consecutivos de cada autor. Primeiro, a função groupArray é usada para reunir todos os valores de consecutive_day de um autor. Esse array de 1s e 0s é então dividido nos valores 0 em subarrays. Por fim, calculamos o subarray mais longo. play

Histórico de commits de um arquivo, linha por linha

Arquivos podem ser renomeados. Quando isso acontece, temos um evento de renomeação, em que a coluna path recebe o novo caminho do arquivo, e old_path representa o caminho anterior, por exemplo. play
Isso dificulta a visualização de todo o histórico de um arquivo, já que não temos um valor único que conecte todas as alterações em linhas ou arquivos. Para resolver isso, podemos usar funções definidas pelo usuário (UDFs). No momento, elas não podem ser recursivas, então, para identificar o histórico de um arquivo, precisamos definir uma série de UDFs que chamem umas às outras explicitamente. Isso significa que só podemos rastrear renomeações até uma profundidade máxima — no exemplo abaixo, são 5 níveis. É improvável que um arquivo seja renomeado mais vezes do que isso, então, por enquanto, isso é suficiente.
Ao chamar file_path_history('src/Storages/StorageReplicatedMergeTree.cpp'), percorremos recursivamente o histórico de renomeações, com cada função chamando o nível seguinte com old_path. Os resultados são combinados usando arrayConcat. Por exemplo,
Podemos usar esse recurso para reunir os commits de todo o histórico de um arquivo. Neste exemplo, mostramos um commit para cada um dos valores de path.

Questões em aberto

Git blame

É particularmente difícil obter um resultado exato devido à impossibilidade, no momento, de manter estado em funções de array. Isso será possível com arrayFold ou arrayReduce, que permitem manter o estado a cada iteração. Uma solução aproximada, suficiente para uma análise de alto nível, pode ser algo assim:
Soluções exatas e aprimoradas são bem-vindas aqui.
Última modificação em 2 de julho de 2026