web-dev-qa-db-fra.com

Pourquoi git blame ne suit pas les renommages?

$ pwd
/data/mdi2/classes

$ git blame -L22,+1 -- utils.js
99b7a802 mdi2/utils.js (user 2015-03-26 21:54:57 +0200 22)  #comment

$ git blame -L22,+1 99b7a802^ -- utils.js
fatal: no such path mdi2/classes/utils.js in 99b7a802^

Comme vous l'avez remarqué, le fichier se trouvait dans un répertoire différent de ce commit

$ git blame -L22,+1 99b7a802^ -- ../utils.js
c5105267 (user 2007-04-10 08:00:20 +0000 22)    #comment 2

Malgré le doc

The Origin of lines is automatically followed across whole-file renames (currently there is no option to turn
       the rename-following off)

le blâme ne suit pas les renommages. Pourquoi?

MISE À JOUR: Réponse courte

git blame suivre les renommages mais pas pour git blame COMMIT^ -- <filename>

Mais il est trop difficile de suivre manuellement les renommages de fichiers à travers une masse de renommages et des tonnes d'historique. Je pense que ce comportement doit être corrigé pour suivre silencieusement les renommages de git blame COMMIT^ -- <filename>. Ou au moins, --follow doit être implémenté, donc je peux: git blame --follow COMMIT^ -- <filename>

PDATE2: C'est impossible. Lire ci-dessous.

RÉPONSE DE MAILLIST par Junio ​​C Hamano

git blame suivre les renommages mais pas pour git blame COMMIT^ -- <filename>

Supposons que vous ayez le fichier A et le fichier B dans votre version v1.0.

Six mois plus tard, le code a été beaucoup remanié et vous n'avez pas besoin du contenu de ces deux fichiers séparément. Vous avez supprimé A et B et une grande partie de ce qu'ils avaient se trouve maintenant dans le fichier C. C'est l'état actuel.

git blame -C HEAD -- C

peut suivre le contenu des deux très bien, mais si vous étiez autorisé à dire

git blame v1.0 -- C

qu'est-ce que cela signifie même? C n'existait pas du tout v1.0. Demandez-vous de suivre le contenu de A à l'époque, ou B? Comment avez-vous dit que vous vouliez dire A et non B lorsque vous lui avez dit C dans cette commande?

"git blame" suit les mouvements de contenu, et ne traite jamais les "renames" d'une manière spéciale, car il est stupide de penser qu'un changement de nom est en quelque sorte spécial ;-)

La façon dont vous dites à partir de quel contenu commencer à creuser à la commande à partir de sa ligne de commande est de donner le point de départ commit (par défaut HEAD mais vous pouvez donner COMMIT ^ comme exemple) et le chemin dans ce point de départ. Comme cela n'a aucun sens de dire C à Git et de faire comme par magie supposer que vous vouliez dire A dans certains cas et B dans d'autres. Si v1.0 n'avait pas C, la seule chose sensée à faire est pour quitter au lieu de faire une supposition (et sans dire à l'utilisateur comment il a deviné).

41
Eugen Konkov

git blame le fait suivre les renommages (tout comme git log si vous le donnez --follow). Le problème réside dans le chemin il suit les renommages, ce qui est un hack peu approfondi: en reculant d'un commit à la fois (de chaque enfant à chaque parent), il fait un diff — le même type de diff que vous pouvez créer manuellement avec:

git diff -M SHA1^ SHA1

—Et vérifie si ce diff a détecté un renommage.1

C'est très bien pour autant, mais cela signifie que pour git blame pour détecter un renommage, (a) git diff -M doit être capable pour le détecter (heureusement, c'est le cas ici) et - voici ce qui vous cause des problèmes - il doit traverser le changement de nom.

Par exemple, supposons que le graphe de validation ressemble un peu à ceci:

A <-- B <-- ... Q <-- R <-- S <-- T

où chaque lettre majuscule représente un commit. Supposons en outre qu'un fichier a été renommé dans commit R, de sorte que dans les validations R à T il porte le nom newname tandis que dans les validations A à Q il a le nom oldname.

Si vous exécutez git blame -- newname, la séquence commence à T, compare S et T, compare R et S, et compare Q et R. Quand il compare Q et R, git blame découvre le changement de nom et commence à rechercher oldname dans les validations Q et les versions antérieures, donc quand il compare P et Q il compare les fichiers oldname et oldname dans ces deux validations.

Si, en revanche, vous exécutez git blame R^ -- newname (ou git blame Q -- newname) pour que la séquence commence à commit Q, il n'y a pas de fichier newname dans ce commit, et il n'y a pas de renommage lors de la comparaison de P et Q, et git blame abandonne tout simplement.

L'astuce est que si vous partez d'un commit dans lequel le fichier avait le nom précédent, vous devez donner à git l'ancien nom:

git blame R^ -- oldname

et puis tout fonctionne à nouveau.


1Dans le git diff documentation , vous verrez qu'il y a un -M option qui contrôle commentgit diff détecte les renommages. Le code blame le modifie un peu (et fait en fait deux passes, une avec -M éteint et une seconde avec -M activé) et utilise son propre (différent) -M option à des fins quelque peu différentes, mais finalement, elle utilise ce même code.


[ Modifier pour ajouter une réponse au commentaire (ne correspondait pas à un commentaire lui-même)]:

Est-ce que n'importe quel outil peut me montrer les noms de fichiers comme: git renames <filename> SHA date oldname-> newname

Pas exactement, mais git diff -M se rapproche et peut être suffisamment proche.

Je ne sais pas ce que vous entendez par "date SHA" ici, mais git diff -M vous permet de fournir deux SHA-1 et compare gauche-droite. Ajouter --name-status pour obtenir uniquement les noms et les dispositions des fichiers. Par conséquent git diff -M --name-status HEAD oldsha1 mai signaler que pour convertir de HEAD à oldsha1, git pense que vous devez Rnommer un fichier et signalera l'ancien nom comme le "nouveau" nom. Par exemple, dans le référentiel git lui-même, il existe actuellement un fichier nommé Documentation/giteveryday.txt qui avait un nom légèrement différent:

$ git diff -M --name-status HEAD 992cb206
M       .gitignore
M       .mailmap
[...snip...]
M       Documentation/diff-options.txt
R097    Documentation/giteveryday.txt   Documentation/everyday.txt
D       Documentation/everyday.txto
[...]

Si c'est le fichier qui vous intéresse, vous êtes bon. Les deux problèmes ici sont:

  • trouver un SHA1: où est-ce que 992cb206 viens de? Si vous avez déjà un SHA-1, c'est facile; si non, git rev-list est l'outil de recherche SHA1; lire sa documentation;
  • et le fait que suite à une série de renommages à travers chaque commit, un commit à la fois, comme git blame le fait, peut produire des réponses très différentes de la comparaison d'un commit beaucoup plus récent (HEAD) avec un commit beaucoup plus tôt (992cb206 ou peu importe). Dans ce cas, le résultat est le même, mais l '"indice de similitude" est ici de 97 sur 100. S'il devait être modifié beaucoup plus dans certaines des étapes intermédiaires, cet indice de similitude pourrait tomber en dessous de 50% ... pourtant, si nous devions comparer une révision juste un peu après 992cb206 à 992cb206 (comme git blame would), l'indice de similitude entre ces deux fichiers pourrait être plus élevé.

Ce qui est nécessaire (et manquant) est pour git rev-list lui-même pour implémenter --follow, de sorte que toutes les commandes utilisant git rev-list en interne, c'est-à-dire la plupart des commandes qui fonctionnent sur plusieurs révisions, peuvent faire l'affaire. En cours de route, ce serait bien si cela fonctionnait dans l'autre sens (actuellement --follow est du plus récent au plus ancien, c'est-à-dire qu'il fonctionne bien avec git blame et fonctionne bien avec git log tant que vous ne demandez pas d'abord la plus ancienne histoire avec --reverse).

32
torek

Le dernier git a une commande intéressante. Ajoutez à côté de votre configuration:

[alias]
    follow= "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" -

Maintenant vous pouvez:

$git follow <filename> <linefrom> [<lineto>]

Et vous verrez chaque commit qui change les lignes spécifiées dans <filename>.

Vous pouvez également être intéressé par --follow option de git log commande:

Continuez à répertorier l'historique d'un fichier au-delà des renommages (ne fonctionne que pour un seul fichier).

Si vous êtes intéressé par la détection de copie, utilisez -C:

Détectez les copies et renommez. Voir aussi --find-copies-harder. Si n est spécifié, il a la même signification que pour -M.

-C recherchera différents fichiers dans le même commit. Si vous souhaitez détecter que le code a été extrait d'un fichier différent qui n'a pas été modifié dans cette validation. Ensuite, vous devez fournir --find-copies-harder option.

Pour des raisons de performances, par défaut, l'option -C ne trouve des copies que si le fichier d'origine de la copie a été modifié dans le même ensemble de modifications. Cet indicateur oblige la commande à inspecter les fichiers non modifiés comme candidats à la source de la copie. C'est une opération très coûteuse pour les grands projets, alors utilisez-la avec prudence. Donner plus d'une option -C a le même effet.

UPD
J'améliore cet alias:

[alias]
    follow = "!bash -c '                                                 \
        if [[ $1 == \"/\"* ]]; then                                      \
            FILE=$1;                                                     \
        else                                                             \
            FILE=${GIT_PREFIX}$1;                                        \
        fi;                                                              \
        echo \"git log --topo-order -u -L $2,${3:-$2}:\\\"$FILE\\\"\";   \
        git log --topo-order -u -L $2,${3:-$2}:\"$FILE\";                \
    ' --"

Vous pouvez maintenant suivre la façon dont la plage de lignes spécifiée est modifiée:

git follow file_name.c 30 35

AVIS: git ne prend malheureusement pas en compte les changements dans le répertoire de travail. Ainsi, si vous apportez des modifications locales au fichier, vous devez le ranger avant de pouvoir follow modifications

1
Eugen Konkov