web-dev-qa-db-fra.com

Git: découvrez quels commits ont jamais touché une gamme de lignes

Je n'arrive pas à comprendre comment utiliser git blame pour obtenir l'ensemble des commits qui ever ont touché une plage de lignes donnée. Il y a des questions similaires comme celle-ci mais la réponse acceptée ne m'amène pas beaucoup plus loin. 

Disons que j'ai une définition qui commence à la ligne 1000 de foo.rb. Cela ne fait que 5 lignes, mais le nombre de commits qui ont changé ces lignes est énorme. Si je fais

git blame foo.rb -L 1000,+5

Je reçois des références à (au plus) cinq commits distincts qui ont changé ces lignes, mais je m'intéresse également aux commits "derrière eux". 

De même,

git rev-list HEAD -- foo.rb | xargs git log --oneline

est presque ce que je veux, mais je ne peux pas spécifier de plages de lignes à git rev-list

Puis-je passer un indicateur à git blame pour obtenir la liste des commits ayant jamais touché ces cinq lignes, ou quel est le moyen le plus rapide de créer un script permettant d'extraire ces informations? Ignorons pour le moment la possibilité que la définition ait déjà eu plus ou moins de 5 lignes. 

57
Joao Tavora

Depuis Git 1.8.4 , git log a -L pour afficher l’évolution d’une plage de lignes.

Par exemple, supposons que vous regardiez la sortie de git blame

((aa27064...))[mlm@macbook:~/w/mlm/git]
$ git blame -L150,+11 -- git-web--browse.sh
a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 150)            die "The browser $browser is not
a180055a git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:36 +0100 151)    fi
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 152) fi
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 153) 
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 154) case "$browser" in
81f42f11 git-web--browse.sh (Giuseppe Bilotta 2010-12-03 17:47:38 +0100 155) firefox|iceweasel|seamonkey|iceape)
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 156)    # Check version because firefox < 2.0 do
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 157)    vers=$(expr "$($browser_path -version)" 
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 158)    NEWTAB='-new-tab'
5d6491c7 git-browse-help.sh (Christian Couder 2007-12-02 06:07:55 +0100 159)    test "$vers" -lt 2 && NEWTAB=''
a0685a4f git-web--browse.sh (Dmitry Potapov   2008-02-09 23:22:22 -0800 160)    "$browser_path" $NEWTAB "$@" &

Et vous voulez connaître l'historique de ce qui est maintenant la ligne 155.

Ensuite:

((aa27064...))[mlm@macbook:~/w/mlm/git]
$ git log --topo-order --graph -u -L 155,155:git-web--browse.sh
* commit 81f42f11496b9117273939c98d270af273c8a463
| Author: Giuseppe Bilotta <[email protected]>
| Date:   Fri Dec 3 17:47:38 2010 +0100
| 
|     web--browse: support opera, seamonkey and elinks
|     
|     The list of supported browsers is also updated in the documentation.
|     
|     Signed-off-by: Giuseppe Bilotta <[email protected]>
|     Signed-off-by: Junio C Hamano <[email protected]>
| 
| diff --git a/git-web--browse.sh b/git-web--browse.sh
| --- a/git-web--browse.sh
| +++ b/git-web--browse.sh
| @@ -143,1 +143,1 @@
| -firefox|iceweasel)
| +firefox|iceweasel|seamonkey|iceape)
|  
* commit a180055a47c6793eaaba6289f623cff32644215b
| Author: Giuseppe Bilotta <[email protected]>
| Date:   Fri Dec 3 17:47:36 2010 +0100
| 
|     web--browse: coding style
|     
|     Retab and deindent choices in case statements.
|     
|     Signed-off-by: Giuseppe Bilotta <[email protected]>
|     Signed-off-by: Junio C Hamano <[email protected]>
| 
| diff --git a/git-web--browse.sh b/git-web--browse.sh
| --- a/git-web--browse.sh
| +++ b/git-web--browse.sh
| @@ -142,1 +142,1 @@
| -    firefox|iceweasel)
| +firefox|iceweasel)
|  
* commit 5884f1fe96b33d9666a78e660042b1e3e5f9f4d9
  Author: Christian Couder <[email protected]>
  Date:   Sat Feb 2 07:32:53 2008 +0100

      Rename 'git-help--browse.sh' to 'git-web--browse.sh'.

      Signed-off-by: Christian Couder <[email protected]>
      Signed-off-by: Junio C Hamano <[email protected]>

  diff --git a/git-web--browse.sh b/git-web--browse.sh
  --- /dev/null
  +++ b/git-web--browse.sh
  @@ -0,0 +127,1 @@
  +    firefox|iceweasel)

Si vous utilisez fréquemment cette fonctionnalité, vous trouverez peut-être un alias git utile. Pour ce faire, insérez votre ~/.gitconfig:

[alias]
    # Follow evolution of certain lines in a file
    # arg1=file, arg2=first line, arg3=last line or blank for just the first line
    follow = "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" -

Et maintenant, vous pouvez simplement faire git follow git-web--browse.sh 155.

60
Matt McClure

Je pense que c'est ce que vous voulez: 

git rev-list HEAD -- foo.rb | ( 
    while read rev; do
        git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1
    done;
) | awk '{ if (!h[$0]) { print $0; h[$0]=1 } }'

Cela affichera le numéro de révision de chaque commit ayant une édition sur les lignes que vous avez choisies. 

Voici les étapes: 

  1. La première partie git rev-list HEAD -- foo.rb affiche toutes les révisions dans lesquelles le fichier choisi est édité. 

  2. Chacune de ces révisions passe ensuite à la deuxième partie, qui prend chacune d’elles et la place dans git blame -l -L 1000,+5 $rev -- foo.rb | cut -d ' ' -f 1. C'est une commande en deux parties. 

    1. git blame -l -L 1000,+5 $rev -- foo.rb renvoie le blâme pour les lignes choisies. En lui donnant le numéro de révision, nous lui disons de partir de ce commit et de partir de là, plutôt que de commencer par la tête.
    2. Puisque blame génère une foule d’informations dont nous n’avons pas besoin, cut -d ' ' -f 1 nous donne la première colonne (le numéro de révision) de la sortie de blâme. 
  3. awk '{ if (!h[$0]) { print $0; h[$0]=1 } }' supprime les lignes en double non adjacentes tout en conservant l'ordre dans lequel elles apparaissent. Voir http://jeetworks.org/node/94 pour plus d'informations sur cette commande. 

Vous pouvez ajouter une dernière étape ici pour obtenir une sortie plus jolie. Transférez tout dans xargs -L 1 git log --oneline -1 et obtenez le message de validation correspondant pour la liste des révisions. J'ai eu un problème étrange en utilisant cette dernière étape où je devais continuer à appuyer chaque fois sur quelques révisions qui étaient sorties. Je ne suis pas sûr de savoir pourquoi, et c'est pourquoi je ne l'ai pas inclus dans ma solution. 

19
Jonathan Wren

Vous n'êtes pas sûr de ce que vous voulez faire, mais peut-être que git log -S peut faire l'affaire pour vous:

-S<string>
    Look for differences that introduce or remove an instance of <string>. 
    Note that this is different than the string simply appearing
    in diff output; see the pickaxe entry in gitdiffcore(7) for more
    details.

Vous pouvez mettre en chaîne le changement (ou une partie du changement) que vous essayez de suivre et cela listera les commits qui ont jamais touché ce changement.

11
FerCa

J'ai aimé ce casse-tête, il a ses subtilités. Sourcez ce fichier, dites init foo.rb 1000,1005 et suivez les instructions. Une fois que vous avez terminé, le fichier @changes aura la liste correcte des commits par ordre topologique et @blames aura le résultat réel de chaque blame. 

Ceci est considérablement plus complexe que la solution acceptée ci-dessus . Il produit des sorties qui seront parfois plus utiles et difficiles à reproduire, et c'était amusant de coder.

Si vous essayez de suivre automatiquement les plages de numéros de ligne tout en remontant dans l’historique, c’est si un membre modifié change de frontière, vous ne pouvez pas déterminer automatiquement où se trouve la nouvelle limite, et vous devrez soit: pour inclure une grande plage d'ajouts importants et ainsi accumuler (parfois un grand nombre) de modifications non pertinentes, ou passer en mode manuel pour vous assurer que c'est correct (ce qui vous ramène bien sûr ici), ou accepter des pertes extrêmes par moments. 

Si vous voulez que votre sortie soit exacte, utilisez la réponse ci-dessus avec des plages de regex dignes de confiance, telles que `/ ^ type function (/,/^}/', ou utilisez ceci, ce qui n'est pas si grave en réalité, quelques secondes par pas en arrière à l'heure.

En échange de la complexité supplémentaire, il produit la liste de réponses en séquence topologique et essaie au moins (avec succès) d’atténuer la douleur à chaque étape. Par exemple, il n’exerce jamais de faute redondante et les plages de mise à jour facilitent l’ajustement des numéros de ligne. Et bien sûr, il y a la fiabilité d'avoir à regarder individuellement les mecs ... :-P

Pour exécuter ceci en mode automatique, dites { init foo.rb /^class foo/,/^end/; auto; } 2>&-

 ### functions here create random @-prefix files in the current directory ###
#
# git blame history for a range, finding every change to that range
# throughout the available history.  It's somewhat, ahh, "intended for
# customization", is that enough of a warning?  It works as advertised
# but drops @-prefix temporary files in your current directory and
# defines new commands
#
# Source this file in a subshell, it defines functions for your use.
# If you have @-prefix files you care about, change all @ in this file
# to something you don't have and source it again.
#
#    init path/to/file [<start>,<end>]  # range optional
#    update-ranges           # check range boundaries for the next step
#    cycle [<start>,<end>]   # range unchanged if not supplied
#    prettyblame             # pretty colors, 
#       blue="child commit doesn't have this line"
#       green="parent commit doesn't have this line"
#           brown=both
#    shhh # silence the pre-cycle blurb
#
# For regex ranges, you can _usually_ source this file and say `init
# path/to/file /startpattern/,/endpattern/` and then cycle until it says 0
# commits remain in the checklist
#
# for line-number ranges, or regex ranges you think might be unworthy, you
# need to check and possibly update the range before each cycle.  File
# @next is the next blame start-point revision text; and command
# update-ranges will bring up vim with the current range V-selected.  If
# that looks good, `@M` is set up to quit even while selecting, so `@M` and
# cycle.  If it doesn't look good, 'o' and the arrow keys will make getting
# good line numbers easy, or you can find better regex's.  Either way, `@M`
# out and say `cycle <start>,<end>` to update the ranges.

init () { 
    file=$1;
    range="$2"
    rm -f @changes
    git rev-list --topo-order HEAD -- "$file" \
    | tee @checklist \
    | cat -n | sort -k2 > @sequence
    git blame "-ln${range:+L$range}" -- "$file" > @latest || echo >@checklist
    check-cycle
    cp @latest @blames
}

update-latest-checklist() {
    # update $latest with the latest sha that actually touched our range,
    # and delete that and everything later than that from the checklist.
    latest=$(
        sed s,^^,, @latest \
        | sort -uk1,1 \
        | join -1 2 -o1.1,1.2 @sequence - \
        | sort -unk1,1 \
        | sed 1q \
        | cut -d" " -f2
    )
    sed -i 1,/^$latest/d @checklist
}
shhh () { shhh=1; }

check-cycle () {
    update-latest-checklist
    sed -n q1 @checklist || git log $latest~..$latest --format=%H\ %s | tee -a @changes
    next=`sed 1q @checklist`
    git cat-file -p `git rev-parse $next:"$file"` > @next
    test -z "$shh$shhh$shhhh" && {
        echo "A blame from the (next-)most recent alteration (id `git rev-parse --short $latest`) to '$file'"
        echo is in file @latest, save its contents where you like
        echo 
        echo you will need to look in file @next to determine the correct next range,
        echo and say '`cycle its-start-line,its-end-line`' to continue
        echo the "update-ranges" function starts you out with the range selected
    } >&2
    ncommits=`wc -l @checklist | cut -d\  -f1`
    echo  $ncommits commits remain in the checklist >&2
    return $((ncommits==0))
}

update-ranges () {
    start="${range%,*}"
    end="${range#*,}"
    case "$start" in
    */*)    startcmd="1G$start"$'\n' ;;
    *)      startcmd="${start}G" ;;
    esac
    case "$end" in
    */*)    endcmd="$end"$'\n' ;;
    [0-9]*) endcmd="${end}G" ;;
    +[0-9]*) endcmd="${end}j" ;;
    *) endcmd="echohl Search|echo "can\'t" get to '${end}'\"|echohl None" ;;
    esac
    vim -c 'set buftype=nofile|let @m=":|q'$'\n"' -c "norm!${startcmd}V${endcmd}z.o" @next
}

cycle () {
    sed -n q1 @checklist && { echo "No more commits to check"; return 1; }
    range="${1:-$range}"
    git blame "-ln${range:+L$range}" $next -- "$file" >@latest || echo >@checklist
    echo >>@blames
    cat @latest >>@blames
    check-cycle
}

auto () {
    while cycle; do true; done
}

prettyblames () {
cat >@pretty <<-\EOD
BEGIN {
    RS=""
    colors[0]="\033[0;30m"
    colors[1]="\033[0;34m"
    colors[2]="\033[0;32m"
    colors[3]="\033[0;33m"
    getline commits < "@changes"
    split(commits,commit,/\n/)
}
NR!=1 { print "" }
{
    thiscommit=gensub(/ .*/,"",1,commit[NR])
    printf "%s\n","\033[0;31m"commit[NR]"\033[0m"
    split($0,line,/\n/)
    for ( n=1; n<=length(line); ++n ) {
        color=0
        split(line[n],key,/[1-9][0-9]*)/)
        if ( NR!=1 && !seen[key[1]] ) color+=1
        seen[key[1]]=1;
        linecommit = gensub(/ .*/,"",1,line[n])
        if (linecommit==thiscommit) color+=2
        printf "%s%s\033[0m\n",colors[color],line[n]
    }
}
EOD
awk -f @pretty @blames | less -R
}
1
jthill

Veuillez vous reporter à la réponse affichée ici Répertoriez tous les commits pour un fichier spécifique . C'est exactement ce dont vous avez besoin.

1
jitendrapurohit

Quelques réflexions ..

Cela ressemble à cet article , et il semble que vous pourriez vous en approcher avec quelque chose comme ceci:

git blame -L '/variable_name *= */',+1

Tant que vous connaissez la définition à comparer (pour l'expression régulière).

Il y a un fil discussion ici , sur l'utilisation de tig et git gui (qui pourrait apparemment gérer cela). Je n'ai pas encore essayé moi-même, donc je ne peux pas le vérifier (j'essaierai plus tard).

0
Jack