{"thread":{"id":"3513","subject":"[PATCH 0/3] Annotate updates","startedAt":"2006-03-02T05:16:36Z","lastAt":"2006-03-02T05:20:33Z","messageCount":3,"participants":["Ryan Anderson"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"17012","messageId":"114127659642-git-send-email-ryan@michonline.com","threadId":"3513","inReplyTo":null,"subject":"[PATCH 0/3] Annotate updates","fromName":"Ryan Anderson","fromEmail":"ryan@michonline.com","sentAt":"2006-03-02T05:16:36Z","receivedAt":"2006-03-02T05:16:36Z","isPatch":true,"sender":{"key":"ryan@michonline.com","avatar":null},"body":"The following changes since commit 2b74cffa9179eed274be2a38c59b7e323c813737 are\nfound in the git repository at:\n\n  http://h4x0r5.com/~ryan/git/ryan.git annotate-upstream\n\nand will follow this email as replies in patch format.\n\nRyan Anderson:\n      Handle \\No newline at end of file.\n      annotate: Add a basic set of test cases.\n      annotate: --rev-file (-S) is not a boolean parameter\n\n git-annotate.perl   |    8 ++++-\n t/t8001-annotate.sh |   89 +++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 96 insertions(+), 1 deletions(-)\n create mode 100755 t/t8001-annotate.sh\n"},{"id":"17013","messageId":"11412765964127-git-send-email-ryan@michonline.com","threadId":"3513","inReplyTo":"114127659642-git-send-email-ryan@michonline.com","subject":"[PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Ryan Anderson","fromEmail":"ryan@michonline.com","sentAt":"2006-03-02T05:16:36Z","receivedAt":"2006-03-02T05:16:36Z","isPatch":true,"sender":{"key":"ryan@michonline.com","avatar":null},"body":"Signed-off-by: Ryan Anderson <ryan@michonline.com>\n\n---\n\n(Pull from http://h4x0r5.com/~ryan/git/ryan.git/ annotate-upstream )\n\nI'm pretty sure this version (finally) gets the edge cases correct.\n\nI would appreciate some other testing on this, as I can't find a case\nwhere it falls down, but the files with a lot of history tend to have a\nlot of lines, making them hard to spotcheck without having been an\nintimate part of that history.\n\nOh, this is the \"functional\" version, but it might not qualify as \"nice\nlooking\" yet, pleaes, feel free to complain.\n\n Makefile          |    1 \n git-annotate.perl |  321 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 322 insertions(+), 0 deletions(-)\n create mode 100755 git-annotate.perl\n\n107045e8abb674a66ee7c682dd85a3d303f26e3c\ndiff --git a/Makefile b/Makefile\nindex 317be3c..86ffcf4 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -119,6 +119,7 @@ SCRIPT_SH = \\\n SCRIPT_PERL = \\\n \tgit-archimport.perl git-cvsimport.perl git-relink.perl \\\n \tgit-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \\\n+\tgit-annotate.perl \\\n \tgit-svnimport.perl git-mv.perl git-cvsexportcommit.perl\n \n SCRIPT_PYTHON = \\\ndiff --git a/git-annotate.perl b/git-annotate.perl\nnew file mode 100755\nindex 0000000..8f98431\n--- /dev/null\n+++ b/git-annotate.perl\n@@ -0,0 +1,321 @@\n+#!/usr/bin/perl\n+# Copyright 2006, Ryan Anderson <ryan@michonline.com>\n+#\n+# GPL v2 (See COPYING)\n+#\n+# This file is licensed under the GPL v2, or a later version\n+# at the discretion of Linus Torvalds.\n+\n+use warnings;\n+use strict;\n+\n+my $filename = shift @ARGV;\n+\n+\n+my @stack = (\n+\t{\n+\t\t'rev' => \"HEAD\",\n+\t\t'filename' => $filename,\n+\t},\n+);\n+\n+our (@lineoffsets, @pendinglineoffsets);\n+our @filelines = ();\n+open(F,\"<\",$filename)\n+\tor die \"Failed to open filename: $!\";\n+\n+while(<F>) {\n+\tchomp;\n+\tpush @filelines, $_;\n+}\n+close(F);\n+our $leftover_lines = @filelines;\n+our %revs;\n+our @revqueue;\n+our $head;\n+\n+my $revsprocessed = 0;\n+while (my $bound = pop @stack) {\n+\tmy @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'});\n+\tforeach my $revinst (@revisions) {\n+\t\tmy ($rev, @parents) = @$revinst;\n+\t\t$head ||= $rev;\n+\n+\t\t$revs{$rev}{'filename'} = $bound->{'filename'};\n+\t\tif (scalar @parents > 0) {\n+\t\t\t$revs{$rev}{'parents'} = \\@parents;\n+\t\t\tnext;\n+\t\t}\n+\n+\t\tmy $newbound = find_parent_renames($rev, $bound->{'filename'});\n+\t\tif ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) {\n+\t\t\tpush @stack, $newbound;\n+\t\t\t$revs{$rev}{'parents'} = [$newbound->{'rev'}];\n+\t\t}\n+\t}\n+}\n+push @revqueue, $head;\n+init_claim($head);\n+$revs{$head}{'lineoffsets'} = {};\n+handle_rev();\n+\n+\n+my $i = 0;\n+foreach my $l (@filelines) {\n+\tmy ($output, $rev, $committer, $date);\n+\tif (ref $l eq 'ARRAY') {\n+\t\t($output, $rev, $committer, $date) = @$l;\n+\t\tif (length($rev) > 8) {\n+\t\t\t$rev = substr($rev,0,8);\n+\t\t}\n+\t} else {\n+\t\t$output = $l;\n+\t\t($rev, $committer, $date) = ('unknown', 'unknown', 'unknown');\n+\t}\n+\n+\tprintf(\"(%8s %10s %10s %d)%s\\n\", $rev, $committer, $date, $i++, $output);\n+}\n+\n+sub init_claim {\n+\tmy ($rev) = @_;\n+\tmy %revinfo = git_commit_info($rev);\n+\tfor (my $i = 0; $i < @filelines; $i++) {\n+\t\t$filelines[$i] = [ $filelines[$i], '', '', '', 1];\n+\t\t\t# line,\n+\t\t\t# rev,\n+\t\t\t# author,\n+\t\t\t# date,\n+\t\t\t# 1 <-- belongs to the original file.\n+\t}\n+\t$revs{$rev}{'lines'} = \\@filelines;\n+}\n+\n+\n+sub handle_rev {\n+\tmy $i = 0;\n+\twhile (my $rev = shift @revqueue) {\n+\n+\t\tmy %revinfo = git_commit_info($rev);\n+\n+\t\tforeach my $p (@{$revs{$rev}{'parents'}}) {\n+\n+\t\t\tgit_diff_parse($p, $rev, %revinfo);\n+\t\t\tpush @revqueue, $p;\n+\t\t}\n+\n+\n+\t\tif (scalar @{$revs{$rev}{parents}} == 0) {\n+\t\t\t# We must be at the initial rev here, so claim everything that is left.\n+\t\t\tfor (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {\n+\t\t\t\tif (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {\n+\t\t\t\t\tclaim_line($i, $rev, $revs{$rev}{lines}, %revinfo);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n+\n+\n+sub git_rev_list {\n+\tmy ($rev, $file) = @_;\n+\n+\topen(P,\"-|\",\"git-rev-list\",\"--parents\",\"--remove-empty\",$rev,\"--\",$file)\n+\t\tor die \"Failed to exec git-rev-list: $!\";\n+\n+\tmy @revs;\n+\twhile(my $line = <P>) {\n+\t\tchomp $line;\n+\t\tmy ($rev, @parents) = split /\\s+/, $line;\n+\t\tpush @revs, [ $rev, @parents ];\n+\t}\n+\tclose(P);\n+\n+\tprintf(\"0 revs found for rev %s (%s)\\n\", $rev, $file) if (@revs == 0);\n+\treturn @revs;\n+}\n+\n+sub find_parent_renames {\n+\tmy ($rev, $file) = @_;\n+\n+\topen(P,\"-|\",\"git-diff-tree\", \"-M50\", \"-r\",\"--name-status\", \"-z\",\"$rev\")\n+\t\tor die \"Failed to exec git-diff: $!\";\n+\n+\tlocal $/ = \"\\0\";\n+\tmy %bound;\n+\tmy $junk = <P>;\n+\twhile (my $change = <P>) {\n+\t\tchomp $change;\n+\t\tmy $filename = <P>;\n+\t\tchomp $filename;\n+\n+\t\tif ($change =~ m/^[AMD]$/ ) {\n+\t\t\tnext;\n+\t\t} elsif ($change =~ m/^R/ ) {\n+\t\t\tmy $oldfilename = $filename;\n+\t\t\t$filename = <P>;\n+\t\t\tchomp $filename;\n+\t\t\tif ( $file eq $filename ) {\n+\t\t\t\tmy $parent = git_find_parent($rev, $oldfilename);\n+\t\t\t\t@bound{'rev','filename'} = ($parent, $oldfilename);\n+\t\t\t\tlast;\n+\t\t\t}\n+\t\t}\n+\t}\n+\tclose(P);\n+\n+\treturn \\%bound;\n+}\n+\n+\n+sub git_find_parent {\n+\tmy ($rev, $filename) = @_;\n+\n+\topen(REVPARENT,\"-|\",\"git-rev-list\",\"--remove-empty\", \"--parents\",\"--max-count=1\",\"$rev\",\"--\",$filename)\n+\t\tor die \"Failed to open git-rev-list to find a single parent: $!\";\n+\n+\tmy $parentline = <REVPARENT>;\n+\tchomp $parentline;\n+\tmy ($revfound,$parent) = split m/\\s+/, $parentline;\n+\n+\tclose(REVPARENT);\n+\n+\treturn $parent;\n+}\n+\n+\n+# Get a diff between the current revision and a parent.\n+# Record the commit information that results.\n+sub git_diff_parse {\n+\tmy ($parent, $rev, %revinfo) = @_;\n+\n+\tmy ($ri, $pi) = (0,0);\n+\topen(DIFF,\"-|\",\"git-diff-tree\",\"-M\",\"-p\",$rev,$parent,\"--\",\n+\t\t\t$revs{$rev}{'filename'}, $revs{$parent}{'filename'})\n+\t\tor die \"Failed to call git-diff for annotation: $!\";\n+\n+\tmy $slines = $revs{$rev}{'lines'};\n+\tmy @plines;\n+\n+\tmy $gotheader = 0;\n+\tmy ($remstart, $remlength, $addstart, $addlength);\n+\tmy ($hunk_start, $hunk_index, $hunk_adds);\n+\twhile(<DIFF>) {\n+\t\tchomp;\n+\t\tif (m/^@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+)/) {\n+\t\t\t($remstart, $remlength, $addstart, $addlength) = ($1, $2, $3, $4);\n+\t\t\t# Adjust for 0-based arrays\n+\t\t\t$remstart--;\n+\t\t\t$addstart--;\n+\t\t\t# Reinit hunk tracking.\n+\t\t\t$hunk_start = $remstart;\n+\t\t\t$hunk_index = 0;\n+\t\t\t$gotheader = 1;\n+\n+\t\t\tfor (my $i = $ri; $i < $remstart; $i++) {\n+\t\t\t\t$plines[$pi++] = $slines->[$i];\n+\t\t\t\t$ri++;\n+\t\t\t}\n+\t\t\tnext;\n+\t\t} elsif (!$gotheader) {\n+\t\t\tnext;\n+\t\t}\n+\n+\t\tif (m/^\\+(.*)$/) {\n+\t\t\tmy $line = $1;\n+\t\t\t$plines[$pi++] = [ $line, '', '', '', 0 ];\n+\t\t\tnext;\n+\n+\t\t} elsif (m/^-(.*)$/) {\n+\t\t\tmy $line = $1;\n+\t\t\tif (get_line($slines, $ri) eq $line) {\n+\t\t\t\t# Found a match, claim\n+\t\t\t\tclaim_line($ri, $rev, $slines, %revinfo);\n+\t\t\t} else {\n+\t\t\t\tdie sprintf(\"Sync error: %d/%d\\n|%s\\n|%s\\n%s => %s\\n\",\n+\t\t\t\t\t\t$ri, $hunk_start + $hunk_index,\n+\t\t\t\t\t\t$line,\n+\t\t\t\t\t\tget_line($slines, $ri),\n+\t\t\t\t\t\t$rev, $parent);\n+\t\t\t}\n+\t\t\t$ri++;\n+\n+\t\t} else {\n+\t\t\tif (substr($_,1) ne get_line($slines,$ri) ) {\n+\t\t\t\tdie sprintf(\"Line %d (%d) does not match:\\n|%s\\n|%s\\n%s => %s\\n\",\n+\t\t\t\t\t\t$hunk_start + $hunk_index, $ri,\n+\t\t\t\t\t\tsubstr($_,1),\n+\t\t\t\t\t\tget_line($slines,$ri),\n+\t\t\t\t\t\t$rev, $parent);\n+\t\t\t}\n+\t\t\t$plines[$pi++] = $slines->[$ri++];\n+\t\t}\n+\t\t$hunk_index++;\n+\t}\n+\tclose(DIFF);\n+\tfor (my $i = $ri; $i < @{$slines} ; $i++) {\n+\t\tpush @plines, $slines->[$ri++];\n+\t}\n+\n+\t$revs{$parent}{lines} = \\@plines;\n+\treturn;\n+}\n+\n+sub get_line {\n+\tmy ($lines, $index) = @_;\n+\n+\treturn ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index];\n+}\n+\n+sub git_cat_file {\n+\tmy ($parent, $filename) = @_;\n+\treturn () unless defined $parent && defined $filename;\n+\tmy $blobline = `git-ls-tree $parent $filename`;\n+\tmy ($mode, $type, $blob, $tfilename) = split(/\\s+/, $blobline, 4);\n+\n+\topen(C,\"-|\",\"git-cat-file\", \"blob\", $blob)\n+\t\tor die \"Failed to git-cat-file blob $blob (rev $parent, file $filename): \" . $!;\n+\n+\tmy @lines;\n+\twhile(<C>) {\n+\t\tchomp;\n+\t\tpush @lines, $_;\n+\t}\n+\tclose(C);\n+\n+\treturn @lines;\n+}\n+\n+\n+sub claim_line {\n+\tmy ($floffset, $rev, $lines, %revinfo) = @_;\n+\tmy $oline = get_line($lines, $floffset);\n+\t@{$lines->[$floffset]} = ( $oline, $rev,\n+\t\t$revinfo{'author'}, $revinfo{'author_date'} );\n+\t#printf(\"Claiming line %d with rev %s: '%s'\\n\",\n+\t#\t\t$floffset, $rev, $oline) if 1;\n+}\n+\n+sub git_commit_info {\n+\tmy ($rev) = @_;\n+\topen(COMMIT, \"-|\",\"git-cat-file\", \"commit\", $rev)\n+\t\tor die \"Failed to call git-cat-file: $!\";\n+\n+\tmy %info;\n+\twhile(<COMMIT>) {\n+\t\tchomp;\n+\t\tlast if (length $_ == 0);\n+\n+\t\tif (m/^author (.*) <(.*)> (.*)$/) {\n+\t\t\t$info{'author'} = $1;\n+\t\t\t$info{'author_email'} = $2;\n+\t\t\t$info{'author_date'} = $3;\n+\t\t} elsif (m/^committer (.*) <(.*)> (.*)$/) {\n+\t\t\t$info{'committer'} = $1;\n+\t\t\t$info{'committer_email'} = $2;\n+\t\t\t$info{'committer_date'} = $3;\n+\t\t}\n+\t}\n+\tclose(COMMIT);\n+\n+\treturn %info;\n+}\n-- \n1.2.2.gb342\n"},{"id":"17014","messageId":"20060302052032.GA15638@mythryan2.michonline.com","threadId":"3513","inReplyTo":"11412765964127-git-send-email-ryan@michonline.com","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Ryan Anderson","fromEmail":"ryan@michonline.com","sentAt":"2006-03-02T05:20:33Z","receivedAt":"2006-03-02T05:20:33Z","isPatch":true,"sender":{"key":"ryan@michonline.com","avatar":null},"body":"Sorry about this - I was in the wrong directory (apparently) when\nrunning git-send-email.  Actual patches in a second.\n\n-- \n\nRyan Anderson\n  sometimes Pug Majere\n"}]}