Como realmente mostrar logs de arquivos renomeados com git?

132

Eu sou relativamente novo no git, usei o Subversion antes.

Notei que a maioria dos front-ends gráficos do git e dos plugins IDE não parecem capazes de exibir o histórico de um arquivo se o arquivo tiver sido renomeado. Quando eu uso

git log --follow

na linha de comando, posso ver o log inteiro entre os renomeados.

De acordo com Linus Torvalds, o switch --follow é um prazer "SVN noob", usuários sérios do git não o usam:

--follow é um hack total, destinado apenas a satisfazer ex-usuários do SVN que nunca souberam nada sobre coisas como paternidade ou bons gráficos de revisão.

Não é totalmente fundamental, mas a implementação atual do "--follow" é realmente um processo rápido de pré-processamento, aparafusado na lógica de revisão, em vez de ser algo realmente integral.

Ele foi literalmente projetado como um agradador de "SVN noob", não como uma coisa de "funcionalidade real do git". A idéia era que você se afastaria da mentalidade (quebrada) de pensar que renomeia a matéria no quadro geral.

Minha pergunta : Como os usuários hardcore de git entre vocês obtêm o histórico de um arquivo quando ele foi renomeado? Qual é a maneira 'real' de fazer isso?

Mike
fonte
17
@ David Hall: git mv oldfile newfilenão provoca a mudança de nome a ser gravado em tudo - é apenas o mesmo que excluir um arquivo e adicionar outro. O git apenas renomeia e copia o estado da árvore em cada commit após o fato.
precisa saber é o seguinte
20
@ David Hall: Se você renomear o arquivo com outra ferramenta fora do git (por exemplo /bin/mv oldfile newfile), mas o fizer git add newfile; git rm oldfile, o resultado será indistinguível do de git mv oldfile newfile.
precisa saber é o seguinte
1
Essa ideologia se desfaz se você mover um arquivo para um novo repositório; nesse caso, a incapacidade de mover todo o histórico pode ser um problema importante. Embora, é claro, haja limites para a quantidade real de história que realmente pode vir com um arquivo em um projeto complexo.
Roman Starkov
1
Nota: git log --followmelhora um pouco com o git 2.9 (junho de 2016): veja minha resposta abaixo
VonC 14/04/16
1
A partir da v2.15, convém experimentar --color-movedquando você diff.
Michael - Onde está Clay Shirky

Respostas:

71

Eu acho que o ponto principal por trás do argumento Linus é que - e leve isso com uma pitada de sal - os usuários hardcore de git nunca se importam com a história de um "arquivo". Você coloca o conteúdo em um repositório git porque o conteúdo como um todo tem um histórico significativo.

A renomeação de arquivo é um pequeno caso especial de "conteúdo" que se move entre caminhos. Você pode ter uma função que se move entre os arquivos que um usuário do git pode rastrear com "picareta" funcionalmente (por exemplo log -S).

Outras mudanças no "caminho" incluem combinar e dividir arquivos; O git realmente não se importa com o arquivo que você considera renomeado e qual arquivo você considera copiado (ou renomeado e excluído), apenas acompanha o conteúdo completo da sua árvore.

O git incentiva o pensamento de "árvore inteira", onde muitos sistemas de controle de versão são muito centrados em arquivos. É por isso que o git se refere a "caminhos" com mais frequência do que a "nomes de arquivos".

CB Bailey
fonte
Olá Charles, Obrigado pela sua resposta. Parece que eu uso o git da mesma maneira que usei o SVN. Embora eu entenda que o git é muito diferente de outros sistemas de controle de versão, muitos conceitos do git ainda me parecem estranhos ... Eu provavelmente deveria terminar o livro do git que comprei recentemente.
21711 Mike
24
O argumento de Linus era que um gui "adequado" seria capaz de rastrear pedaços de código entre arquivos, e ele esperava que já tivéssemos essas ferramentas. Infelizmente, ainda não temos esse luxo e --follow ainda é útil.
Michael Parker
11
O git realmente oferece uma solução diferente --followpara isso?
Griwes
13
Eu argumentaria que o pensamento de "árvore inteira" é aprimorado por --followser o padrão. O que quero dizer é que quando quero ver o histórico do código em um arquivo, geralmente não me importo se o arquivo foi renomeado ou não, apenas quero ver o histórico do código, independentemente de renomear. Então, na minha opinião, faz sentido --followser o padrão, porque não me importo com arquivos individuais; --followme ajuda a ignorar as renomeações de arquivos individuais, que geralmente são bastante irrelevantes.
Eddified
2
Então ... se eu decidir me importar com o "conteúdo" em vez do arquivo, como imprimo os commits relevantes para o conteúdo atualmente neste arquivo? Eu ficaria encantado se o git rastreasse tudo em arquivos diferentes para mim e relatasse um log de todas as alterações - simplesmente não vejo como obtê-lo.
Ed Avis
36

Eu tenho exatamente o mesmo problema que você está enfrentando. Embora eu não possa responder, acredito que você possa ler este e-mail que Linus escreveu em 2005, é muito pertinente e pode lhe dar uma dica sobre como lidar com o problema:

... Estou afirmando que qualquer SCM que tenta rastrear renomeações é fundamentalmente quebrado, a menos que faça isso por razões internas (ou seja, para permitir deltas eficientes), exatamente porque as renomeações não importam. Eles não o ajudam e, de qualquer maneira , não são o que você estava interessado .

O que importa é descobrir "de onde isso veio", e a arquitetura git faz isso muito bem mesmo - muito melhor do que qualquer outra coisa lá fora. ...

Eu o encontrei referenciado por este post do blog, o que também pode ser útil para você encontrar uma solução viável:

Na mensagem, Linus descreveu como um sistema ideal de rastreamento de conteúdo pode permitir que você descubra como um bloco de código entrou na forma atual. Você começaria a partir do bloco de código atual em um arquivo, voltaria no histórico para encontrar a confirmação que alterou o arquivo. Em seguida, você inspeciona a alteração do commit para ver se o bloco de código no qual você está interessado é modificado por ele, pois um commit que altera o arquivo pode não tocar no bloco de código no qual você está interessado, mas apenas em algumas outras partes do Arquivo.

Quando você descobre que antes da confirmação o bloco de código não existia no arquivo, você inspeciona a confirmação mais profundamente. Você pode achar que é uma das muitas situações possíveis, incluindo:

  1. O commit realmente introduziu o bloco de código. O autor do commit foi o inventor desse recurso interessante para o qual você estava caçando sua origem (ou a parte culpada que introduziu o bug); ou
  2. O bloco de código não existia no arquivo, mas cinco cópias idênticas existiam em arquivos diferentes, todas desaparecendo após a confirmação. O autor do commit refatorou o código duplicado introduzindo uma única função auxiliar; ou
  3. (como um caso especial) Antes da confirmação, o arquivo que atualmente contém o bloco do código no qual você está interessado não existia, mas existia outro arquivo com conteúdo quase idêntico e o bloco do código no qual você está interessado, juntamente com todos os outros conteúdos existentes no arquivo, existiam naquele outro arquivo. Foi embora após o commit. O autor da confirmação renomeou o arquivo enquanto fazia uma pequena modificação.

No git, a melhor ferramenta de rastreamento de conteúdo da Linus ainda não existe de maneira totalmente automatizada. Mas a maioria dos ingredientes importantes já está disponível.

Por favor, mantenha-nos informados sobre seu progresso nisso.

Alberto Bacchelli
fonte
Obrigado por postar esses artigos. Foi só quando os li que compreendi completamente a ideia da história do conteúdo! .Estive pensando sobre isso da maneira errada!
DavidG
Esse e-mail da Linus é ótimo, obrigado por postar isso.
precisa saber é o seguinte
Curiosamente--color-moved , o Git v2.15 adiciona uma mudança para esse "sistema de rastreamento ideal". Eu estava brincando com ele para ver as linhas movidas dentro de um arquivo, mas percebi acidentalmente que elas rastreavam as linhas movidas em todo o diff.
Michael - Onde está Clay Shirky
2
Linus explica uma situação complexa. Mas aqui temos uma situação simples: um arquivo foi renomeado (ou movido para outro diretório). Portanto, deve haver uma solução simples. Eu acho que a questão vem do fato de que, ao contrário do Subversion, o usuário não pode instruir o Git no momento da confirmação de onde um arquivo vem, e isso --followpode estar errado (por exemplo, se 2 arquivos tiverem o mesmo conteúdo ou se houver uma modificação além da movimentação do arquivo).
21719 vincent
13

Notei que a maioria dos front-ends gráficos do git e dos plugins IDE não parecem capazes de exibir o histórico de um arquivo se o arquivo tiver sido renomeado

Você ficará feliz em saber que algumas ferramentas populares da interface do usuário do Git agora suportam isso. Existem dezenas de ferramentas de interface do usuário do Git disponíveis, portanto não listarei todas, mas, por exemplo:

  • O SourceTree, ao visualizar um log de arquivo, possui uma caixa de seleção "Seguir arquivos renomeados" no canto inferior esquerdo
  • O TortoiseGit possui uma caixa de seleção "seguir renomear" na janela de registro no canto inferior esquerdo.

Mais informações sobre as ferramentas de interface do usuário do Git:

Michael Parker
fonte
A fonte funciona muito bem quando, ao renomear uma vez, ao renomear duas vezes, os detalhes da alteração não estão disponíveis para confirmações antes da renomeação. Relatei
Intel
O gitk funciona muito bem mesmo quando um arquivo é renomeado duas vezes no histórico. O comando se parece com este "gitk --follow path / to / file"
Intel
6

Nota: o git 2.9 (junho de 2016) melhorará bastante a natureza de "buggy" de git log --follow:

Veja commit ca4e3ca (30 de março de 2016) por SZEDER Gábor ( szeder) .
(Mesclado por Junio ​​C Hamano - gitster- na commit 26effb8 , 13 de abril de 2016)

diffcore: corrige a ordem de iteração de arquivos idênticos durante a detecção de renomeação

Se os dois caminhos ' dir/A/file' e ' dir/B/file' tiverem conteúdo idêntico e o diretório pai for renomeado, por exemplo, ' git mv dir other-dir', os diffcorerelatórios serão os seguintes:

renamed:    dir/B/file -> other-dir/A/file
renamed:    dir/A/file -> other-dir/B/file

(observe a inversão aqui:, B/file -> A/filee A/file -> B/file)

Embora tecnicamente não esteja errado, isso é confuso não apenas para o usuário, mas também para comandos git que tomam decisões com base em informações de renomeação, por exemplo, ' git log --follow other-dir/A/file' segue ' dir/B/file' após a renomeação.

Esse comportamento é um efeito colateral da confirmação v2.0.0-rc4 ~ 8 ^ 2 ~ 14 ( diffcore-rename.c: simplifique a localização de renomeações exatas, 14/11/2013): as fontes de armazenamento de hashmap retornam entradas do mesmo bucket, ou seja, fontes que correspondem ao destino atual , na ordem LIFO.
Assim, a iteração examina primeiro ' other-dir/A/file' e ' dir/B/file' e, ao encontrar conteúdo e nome de base idênticos, informa uma renomeação exata.

VonC
fonte
2

No Linux, verifiquei que o SmartGit e o GitEye são capazes de seguir os nomes renomeados ao seguir o histórico de um arquivo específico. No entanto, diferentemente do gitk e do GitEye, o SmartGit mostra uma visualização de arquivo e repositório separadas (que contém a estrutura de diretórios, mas não a lista de arquivos contidos)

prusswan
fonte