Git - Comment afficher l'historique des modifications d'une méthode / fonction?

91

J'ai donc trouvé la question de savoir comment afficher l'historique des modifications d'un fichier, mais l'historique des modifications de ce fichier particulier est énorme et je ne m'intéresse vraiment qu'aux modifications d'une méthode particulière. Alors, serait-il possible de voir l'historique des modifications pour cette méthode en particulier?

Je sais que cela nécessiterait git d'analyser le code et que l'analyse serait différente pour différentes langues, mais les déclarations de méthode / fonction semblent très similaires dans la plupart des langues, alors j'ai pensé que quelqu'un a peut-être implémenté cette fonctionnalité.

Le langage avec lequel je travaille actuellement est Objective-C et le SCM que j'utilise actuellement est git, mais je serais intéressé de savoir si cette fonctionnalité existe pour n'importe quel SCM / langage.

Erik B
la source
1
J'ai vu une telle fonctionnalité dans la proposition Git GSoG.
Vi.
Est-ce la proposition dont vous parliez? lists-archives.org/git/…
Erik B
@lpapp la question ici a été posée 10 mois plus tôt, l'autre question devrait être marquée comme dupe de celle-ci (si ce sont des dupes du tout).
Dirty-flow
2
@lpapp Ce sont deux questions complètement différentes. Vous pouvez éventuellement écrire un script qui résout un nom de fonction en une plage de lignes, puis utiliser la technique de cette question pour obtenir l'historique de ces lignes, mais en soi, il ne répond pas à cette question.
Erik B

Réponses:

97

Les versions récentes de git logappris une forme spéciale du -Lparamètre:

-L: <nomfonc>: ​​<fichier>

Tracez l'évolution de la plage de lignes donnée par "<start>,<end>"(ou le nom de la fonction regex <funcname>) dans le <file>. Vous ne pouvez pas donner de limiteurs de pathspec. Ceci est actuellement limité à une marche à partir d'une seule révision, c'est-à-dire que vous ne pouvez donner que zéro ou un argument de révision positif. Vous pouvez spécifier cette option plusieurs fois.
...
Si “:<funcname>”est donné à la place de <start>et <end>, c'est une expression régulière qui désigne la plage allant de la première ligne funcname qui correspond <funcname>, jusqu'à la ligne funcname suivante. “:<funcname>”recherche à partir de la fin de la -Lplage précédente , le cas échéant, sinon depuis le début du fichier. “^:<funcname>”recherche depuis le début du fichier.

En d'autres termes: si vous demandez à Git de le faire git log -L :myfunction:path/to/myfile.c, il affichera désormais avec plaisir l'historique des modifications de cette fonction.

eckes
la source
15
cela peut fonctionner pour objective-c hors de la boîte, mais si vous le faites pour d'autres langages (par exemple Python, Ruby, etc.), vous devrez peut-être ajouter la configuration appropriée dans un fichier .gitattributes afin que git reconnaisse la fonction / méthode déclarations dans cette langue. Pour python, utilisez * .py diff = python, pour ruby, utilisez * .rb diff = ruby
samaspin
1
Comment git trace-t-il la fonction?
nn0p
@ nn0p Je suppose en ayant une connaissance syntaxique de plusieurs langages et en sachant ainsi isoler une fonction et tracer ses changements.
JasonGenX
4
En prolongeant le commentaire de @ samaspin, pour d'autres langues, vous pouvez vous référer à la documentation ici: git-scm.com/docs/gitattributes#_generating_diff_text
edhgoose
Cela ne fonctionne pas pour les langages comme Scala et Java et même C qui utilisent des accolades imbriquées (les expressions régulières ne peuvent pas gérer cela en général), et même le formulaire de début, de fin ne fonctionne pas lorsque la fonction est déplacée dans le fichier et modifié dans le même commit.
Robin Green
16

L'utilisation git gui blameest difficile à utiliser dans les scripts, et bien git log -Gque git log --pickaxechacun puisse vous montrer quand la définition de la méthode est apparue ou a disparu, je n'ai trouvé aucun moyen de leur faire lister toutes les modifications apportées au corps de votre méthode.

Cependant, vous pouvez utiliser gitattributeset letextconv propriété pour reconstituer une solution qui fait exactement cela. Bien que ces fonctionnalités aient été initialement destinées à vous aider à travailler avec des fichiers binaires, elles fonctionnent tout aussi bien ici.

La clé est que Git supprime du fichier toutes les lignes sauf celles qui vous intéressent avant d'effectuer des opérations de comparaison. Alors git log,git diff etc. ne verront que la zone que vous intéresse.

Voici les grandes lignes de ce que je fais dans une autre langue; vous pouvez le modifier selon vos propres besoins.

  • Écrivez un court script shell (ou un autre programme) qui prend un argument - le nom d'un fichier source - et ne produit que la partie intéressante de ce fichier (ou rien si rien de tout cela n'est intéressant). Par exemple, vous pouvez utiliser sedcomme suit:

    #!/bin/sh
    sed -n -e '/^int my_func(/,/^}/ p' "$1"
  • Définir un Git textconv filtre pour votre nouveau script. (Voir la gitattributespage de manuel pour plus de détails.) Le nom du filtre et l'emplacement de la commande peuvent être tout ce que vous voulez.

    $ git config diff.my_filter.textconv /path/to/my_script
  • Dites à Git d'utiliser ce filtre avant de calculer les différences pour le fichier en question.

    $ echo "my_file diff=my_filter" >> .gitattributes
  • Maintenant, si vous utilisez -G.(notez le .) pour lister tous les commits qui produisent des changements visibles lorsque votre filtre est appliqué, vous aurez exactement ces commits qui vous intéressent. Toutes les autres options qui utilisent les routines de diff de Git, telles que--patch , obtenez également cette vue restreinte.

    $ git log -G. --patch my_file
  • Voilà!

Une amélioration utile que vous voudrez peut-être apporter est que votre script de filtrage prenne un nom de méthode comme premier argument (et le fichier comme deuxième). Cela vous permet de spécifier une nouvelle méthode intéressante simplement en appelantgit config , plutôt que d'avoir à modifier votre script. Par exemple, vous pourriez dire:

$ git config diff.my_filter.textconv "/path/to/my_command other_func"

Bien sûr, le script de filtrage peut faire tout ce que vous voulez, prendre plus d'arguments ou autre: il y a beaucoup de flexibilité au-delà de ce que j'ai montré ici.

Paul Whittaker
la source
1
L'argument prendre plus d'un n'a pas fonctionné pour moi, mais mettre le nom de la fonction en dur fonctionne bien et c'est vraiment génial!
qwertzguy
Génial, même si je me demande à quel point il est (in) pratique de basculer entre de nombreuses méthodes différentes. Aussi, connaissez-vous un programme qui peut extraire une fonction entière de type C?
nafg
12

git log a une option '-G' qui peut être utilisée pour trouver toutes les différences.

-G Recherchez les différences dont la ligne ajoutée ou supprimée correspond à celle donnée <regex>.

Donnez-lui simplement une expression régulière du nom de la fonction qui vous intéresse. Par exemple,

$ git log --oneline -G'^int commit_tree'
40d52ff make commit_tree a library function
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
7b9c0a6 git-commit-tree: make it usable from other builtins
lht
la source
5
Je n'ai pas exécuté la commande, mais il me semble que cette commande n'afficherait que les commits qui touchent la ligne qui correspond à l'expression régulière, ce qui n'est pas toute la méthode / fonction.
Erik B
si vous voulez plus de contexte, vous pouvez le remplacer --onelinepar-p
lfender6445
3
Mais que se passerait-il si une modification était apportée à 20 lignes dans la méthode?
nafg
+1. Pour moi, la meilleure réponse a fonctionné mais n'a montré que le commit le plus récent. Peut-être en raison de rebases ou de la fonction se déplaçant plusieurs fois, je ne sais pas. En recherchant la ligne de code réelle au lieu de la fonction dans laquelle elle se trouvait (bien que sur une seule ligne), cette réponse m'a permis de repérer facilement le commit que je recherchais. Heureusement, j'écris généralement des messages de commit utiles!
Dave S
11

La chose la plus proche que vous puissiez faire est de déterminer la position de votre fonction dans le fichier (par exemple, disons que votre fonction i_am_buggyest aux lignes 241 à 263 de foo/bar.c), puis exécutez quelque chose comme:

git log -p -L 200,300:foo/bar.c

Cela ouvrira moins (ou un téléavertisseur équivalent). Maintenant, vous pouvez taper/i_am_buggy (ou votre équivalent pager) et commencer à parcourir les modifications.

Cela peut même fonctionner, en fonction de votre style de code:

git log -p -L /int i_am_buggy\(/,+30:foo/bar.c

Cela limite la recherche à partir du premier hit de cette expression régulière (idéalement votre déclaration de fonction) à trente lignes après cela. L'argument de fin peut également être une expression rationnelle, bien que détecter cela avec les expressions rationnelles est une proposition plus incertaine.

badp
la source
Doux! Pour info, c'est nouveau dans Git v1.8.4. (Je suppose que je devrais mettre à jour.) Bien qu'une solution plus précise serait bien ... comme si quelqu'un écrit la réponse de Paul Whittaker.
Greg Price
@GregPrice Apparemment, les bords de la recherche peuvent même être des expressions régulières , donc vous pouvez au moins avoir un point de départ plus ou moins précis.
badp
Oh wow. En fait: au lieu d'écrire votre propre expression régulière, vous pouvez simplement dire -L ":int myfunc:foo/bar.c"et vous limiter à la fonction portant ce nom. C'est fantastique - merci pour le pointeur! Maintenant, si seulement la fonction de détection était un peu plus fiable ...
Greg Price
3

La bonne façon est d'utiliser git log -L :function:path/to/filecomme expliqué dans la réponse eckes .

Mais en plus, si votre fonction est très longue, vous voudrez peut-être voir uniquement les changements introduits par divers commit, et non les lignes de fonction entières, incluses non modifiées, pour chaque commit qui ne touche peut-être qu'une de ces lignes. Comme une normale diff.

Normalement, git logpeut afficher les différences avec -p, mais cela ne fonctionne pas avec -L. Vous devez donc grep git log -Lafficher uniquement les lignes impliquées et l'en-tête commits / files pour les contextualiser. L'astuce ici est de faire correspondre uniquement les lignes colorées du terminal, en ajoutant un --colorcommutateur, avec une expression régulière. Finalement:

git log -L :function:path/to/file --color | grep --color=never -E -e "^(^[\[[0-9;]*[a-zA-Z])+" -3

Notez que cela ^[doit être réel, littéral ^[. Vous pouvez les saisir en appuyant sur ^ V ^ [en bash, c'est-à-dire Ctrl+ V, Ctrl+ [. Référence ici .

Dernier -3interrupteur également, permet d'imprimer 3 lignes de contexte de sortie, avant et après chaque ligne correspondante. Vous voudrez peut-être l'ajuster à vos besoins.

Hazzard17
la source
2

git blame vous montre qui a changé en dernier chaque ligne du fichier; vous pouvez spécifier les lignes à examiner afin d'éviter de récupérer l'historique des lignes en dehors de votre fonction.

Volonté
la source
4
avec git gui blamevous pouvez parcourir les anciennes révisions.
Vi.
0
  1. Afficher l'historique des fonctions avec git log -L :<funcname>:<file>comme indiqué dans la réponse d'eckes et git doc

    S'il ne montre rien, reportez-vous à la section Définition d'un en-tête de morceau personnalisé pour ajouter quelque chose comme *.java diff=javaau .gitattributes fichier pour prendre en charge votre langue.

  2. Afficher l'historique des fonctions entre les validations avec git log commit1..commit2 -L :functionName:filePath

  3. Afficher l'historique des fonctions surchargées (il peut y avoir plusieurs fonctions avec le même nom, mais avec des paramètres différents) avec git log -L :sum\(double:filepath

Kris Roofe
la source