{"thread":{"id":"3404","subject":"[PATCH] Add git-annotate, a tool for assigning blame.","startedAt":"2006-02-20T10:46:09Z","lastAt":"2006-02-28T09:08:05Z","messageCount":13,"participants":["Ryan Anderson","Junio C Hamano","Fredrik Kuivinen","Johannes Schindelin","Andreas Ericsson"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"16449","messageId":"11404323692193-git-send-email-ryan@michonline.com","threadId":"3404","inReplyTo":null,"subject":"[PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Ryan Anderson","fromEmail":"ryan@michonline.com","sentAt":"2006-02-20T10:46:09Z","receivedAt":"2006-02-20T10:46:09Z","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":"16480","messageId":"7virr98wvc.fsf@assigned-by-dhcp.cox.net","threadId":"3404","inReplyTo":"11404323692193-git-send-email-ryan@michonline.com","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-02-20T22:54:15Z","receivedAt":"2006-02-20T22:54:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ryan Anderson <ryan@michonline.com> writes:\n\n> I would appreciate some other testing on this, as I can't find a case\n> where it falls down, but the files with a lot of history tend to have a\n> lot of lines, making them hard to spotcheck without having been an\n> intimate part of that history.\n\nI looked at a couple of files, including pack-objects.c,\nrev-list.c (in \"pu\"), and svnimport.perl; what I saw made sense.\nI think however we would want to check things with a lot of\nmerges, so git repository may not be a good guinea pig.\n\n> Oh, this is the \"functional\" version, but it might not qualify as \"nice\n> looking\" yet, pleaes, feel free to complain.\n\nNice.\n\nTwo design glitches and an implementation:\n\n - You seem to rely on the working tree file to be clean relative to\n   HEAD.\n\n - You do not take anything other than HEAD as the starting point.\n\nMaybe an additional -r<this-version> option which defaults to\nHEAD, plus reading the blob always from that tree as the\nstarting point would be helpful.\n\n> +\topen(P,\"-|\",\"git-rev-list\",\"--parents\",\"--remove-empty\",$rev,\"--\",$file)\n\nJohannes noticed I slipped this form which Perl 5.6 does not\ngrok in another program.  Eric pointed out he uses a backward\ncompatible idiom in his code.\n\nWe would need to make this one safe; something like this, perhaps:\n\n\tsub open_read_pipe {\n        \tmy ($fh, @cmd) = @_;\n                my $pid = open($fh, '-|');\n\t\tdie \"$!\" unless defined($pid);\n\t\tif (!$pid) {\n\t\t\texec(@cmd) or die \"$!\";\n\t\t}\n\t\treturn $fh;\n\t}\n\t...\n        open_read_pipe(\\*P, qw(git-rev-list --parents --remove-empty),\n\t\t\t$rev, '--', $file);\n\n\n> +\topen(P,\"-|\",\"git-diff-tree\", \"-M50\", \"-r\",\"--name-status\", \"-z\",\"$rev\")\n> +\t\tor die \"Failed to exec git-diff: $!\";\n\nIf you do not mean \"I want -M50\" but you meant \"I want whatever\nis default\", I'd leave that 50 out.\n\nIt is probably premature to talk about issues for UI that can be\nbuilt on top of this, but what I found interesting was this.\nWhile looking at the annotate output, whenever I got curious\nabout one line (\"Oh, this is an ancient change and by somebody\nwho does not feed patches to git regularly -- what was the\nchange about???\"), I grabbed the SHA1 of the commit on the line\nand threw it at \"git show\".  It was a good way to see how the\nchange to the line was done in context.  Maybe a two-pane UI\nthat shows annotate on the top pane, and activating one line\nfrom it shows \"git show\" output on the bottom pane to show the\ncommit log plus changes the commit introduced to the file and\nother files.\n"},{"id":"16482","messageId":"20060220234054.GA7903@c165.ib.student.liu.se","threadId":"3404","inReplyTo":"11404323692193-git-send-email-ryan@michonline.com","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Fredrik Kuivinen","fromEmail":"freku045@student.liu.se","sentAt":"2006-02-20T23:40:54Z","receivedAt":"2006-02-20T23:40:54Z","isPatch":true,"sender":{"key":"frekui@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13770967?v=4"},"body":"On Mon, Feb 20, 2006 at 05:46:09AM -0500, Ryan Anderson wrote:\n> Signed-off-by: Ryan Anderson <ryan@michonline.com>\n> \n> ---\n> \n> (Pull from http://h4x0r5.com/~ryan/git/ryan.git/ annotate-upstream )\n> \n> I'm pretty sure this version (finally) gets the edge cases correct.\n> \n> I would appreciate some other testing on this, as I can't find a case\n> where it falls down, but the files with a lot of history tend to have a\n> lot of lines, making them hard to spotcheck without having been an\n> intimate part of that history.\n> \n> Oh, this is the \"functional\" version, but it might not qualify as \"nice\n> looking\" yet, pleaes, feel free to complain.\n> \n\nNice work!\n\n\nI have also been working on a blame program. The algorithm is pretty\nmuch the one described by Junio in his blame.perl. My variant doesn't\nhandle renames, but it shouldn't be too hard to add that. The output\nis minimal, just the line number followed by the commit SHA1.\n\nAn interesting observation is that the output from my git-blame and\nyour git-annotate doesn't match on all files in the git\nrepository. One example where several lines differ is read-cache.c. I\nhaven't investigated it further to find out which one is correct.\n\n\nThe code should be considered as a work in progress. It certainly has\na couple of rough edges. The output looks fairly sane on the few files\nI have tested it on, but it wouldn't be too surprising if it gets some\ncases wrong.\n\n\nSigned-off-by: Fredrik Kuivinen <freku045@student.liu.se>\n\n\n---\n\n Makefile |    2 \n blame.c  |  444 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 445 insertions(+), 1 deletions(-)\n create mode 100644 blame.c\n\nafe1466a4f94bc72b42dce5fd1fed480c5aa4c49\ndiff --git a/Makefile b/Makefile\nindex 317be3c..4a3ffe9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -153,7 +153,7 @@ PROGRAMS = \\\n \tgit-upload-pack$X git-verify-pack$X git-write-tree$X \\\n \tgit-update-ref$X git-symbolic-ref$X git-check-ref-format$X \\\n \tgit-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \\\n-\tgit-describe$X\n+\tgit-describe$X git-blame$X\n \n # what 'all' will build and 'install' will install, in gitexecdir\n ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)\ndiff --git a/blame.c b/blame.c\nnew file mode 100644\nindex 0000000..d4a2fad\n--- /dev/null\n+++ b/blame.c\n@@ -0,0 +1,444 @@\n+#include <assert.h>\n+\n+#include \"cache.h\"\n+#include \"refs.h\"\n+#include \"tag.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"blob.h\"\n+#include \"epoch.h\"\n+#include \"diff.h\"\n+\n+#define DEBUG 0\n+\n+struct commit** blame_lines;\n+int num_blame_lines;\n+\n+struct util_info\n+{\n+    int* line_map;\n+    int num_lines;\n+    unsigned char sha1[20]; /* blob sha, not commit! */\n+    char* buf;\n+    unsigned long size;\n+//    const char* path;\n+};\n+\n+struct chunk\n+{\n+    int off1, len1; // ---\n+    int off2, len2; // +++\n+};\n+\n+struct patch\n+{\n+    struct chunk* chunks;\n+    int num;\n+};\n+\n+static void get_blob(struct commit* commit);\n+\n+int num_get_patch = 0;\n+int num_commits = 0;\n+\n+struct patch* get_patch(struct commit* commit, struct commit* other)\n+{\n+    struct patch* ret = xmalloc(sizeof(struct patch));\n+    ret->chunks = NULL;\n+    ret->num = 0;\n+\n+    struct util_info* info_c = (struct util_info*) commit->object.util;\n+    struct util_info* info_o = (struct util_info*) other->object.util;\n+\n+    if(!memcmp(info_c->sha1, info_o->sha1, 20))\n+        return ret;\n+\n+    get_blob(commit);\n+    get_blob(other);\n+\n+    FILE* fout = fopen(\"/tmp/git-blame-tmp1\", \"w\");\n+    if(!fout)\n+        die(\"fopen tmp1 failed: %s\", strerror(errno));\n+\n+    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)\n+        die(\"fwrite 1 failed: %s\", strerror(errno));\n+    fclose(fout);\n+\n+    fout = fopen(\"/tmp/git-blame-tmp2\", \"w\");\n+    if(!fout)\n+        die(\"fopen tmp2 failed: %s\", strerror(errno));\n+\n+    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)\n+        die(\"fwrite 2 failed: %s\", strerror(errno));\n+    fclose(fout);\n+\n+    FILE* fin = popen(\"diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2\", \"r\");\n+    if(!fin)\n+        die(\"popen failed: %s\", strerror(errno));\n+\n+    \n+    char buf[1024];\n+    while(fgets(buf, sizeof(buf), fin)) {\n+        if(buf[0] != '@' || buf[1] != '@')\n+            continue;\n+\n+        if(DEBUG)\n+            printf(\"chunk line: %s\", buf);\n+        ret->num++;\n+        ret->chunks = xrealloc(ret->chunks, sizeof(struct chunk)*ret->num);\n+        struct chunk* chunk = &ret->chunks[ret->num-1];\n+        \n+        assert(!strncmp(buf, \"@@ -\", 4));\n+\n+        char* start = buf+4;\n+        char* sp = index(start, ' ');\n+        *sp = '\\0';\n+        if(index(start, ',')) {\n+            int ret = sscanf(start, \"%d,%d\", &chunk->off1, &chunk->len1);\n+            assert(ret == 2);  \n+        } else {\n+            int ret = sscanf(start, \"%d\", &chunk->off1);\n+            assert(ret == 1);\n+            chunk->len1 = 1;\n+        }\n+        *sp = ' ';\n+\n+        start = sp+1;\n+        sp = index(start, ' ');\n+        *sp = '\\0';\n+        if(index(start, ',')) {\n+            int ret = sscanf(start, \"%d,%d\", &chunk->off2, &chunk->len2);\n+            assert(ret == 2);\n+        } else {\n+            int ret = sscanf(start, \"%d\", &chunk->off2);\n+            assert(ret == 1);\n+            chunk->len2 = 1;\n+        }\n+        *sp = ' ';\n+\n+        if(chunk->off1 > 0)\n+            chunk->off1 -= 1;\n+        if(chunk->off2 > 0)\n+            chunk->off2 -= 1;\n+\n+        assert(chunk->off1 >= 0);\n+        assert(chunk->off2 >= 0);\n+    }\n+    fclose(fin);\n+\n+    num_get_patch++;\n+    return ret;\n+}\n+\n+void free_patch(struct patch* p)\n+{\n+    free(p->chunks);\n+    free(p);\n+}\n+\n+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,\n+                                  const char *pathname, unsigned mode, int stage);\n+\n+\n+static unsigned char blob_sha1[20];\n+static int get_blob_sha1(struct tree* t, const char* pathname, unsigned char* sha1)\n+{\n+    const char *pathspec[2];\n+    pathspec[0] = pathname;\n+    pathspec[1] = NULL;\n+    memset(blob_sha1, 0, sizeof(blob_sha1));\n+    read_tree_recursive(t, \"\", 0, 0, pathspec, get_blob_sha1_internal);\n+\n+    int i;\n+    for(i = 0; i < 20; i++) {\n+        if(blob_sha1[i] != 0)\n+            break;\n+    }\n+\n+    if(i == 20)\n+        return -1;\n+    \n+    memcpy(sha1, blob_sha1, 20);\n+    return 0;\n+}\n+\n+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,\n+                                  const char *pathname, unsigned mode, int stage)\n+{\n+//    printf(\"Got blob: %s base: '%s' baselen: %d pathname: '%s' mode: %o stage: %d\\n\",\n+//           sha1_to_hex(sha1), base, baselen, pathname, mode, stage);\n+\n+    if(S_ISDIR(mode))\n+        return READ_TREE_RECURSIVE;\n+    \n+    memcpy(blob_sha1, sha1, 20);\n+    return -1;\n+}\n+\n+static void get_blob(struct commit* commit)\n+{\n+    struct util_info* info = commit->object.util;\n+    char type[20];    \n+\n+    if(info->buf)\n+        return;\n+    \n+    info->buf = read_sha1_file(info->sha1, type, &info->size);\n+    assert(!strcmp(type, \"blob\"));\n+}\n+\n+void print_patch(struct patch* p)\n+{\n+    printf(\"Num chunks: %d\\n\", p->num);\n+    int i;\n+    for(i = 0; i < p->num; i++) {\n+        printf(\"%d,%d %d,%d\\n\", p->chunks[i].off1, p->chunks[i].len1, p->chunks[i].off2, p->chunks[i].len2);\n+    }\n+}\n+\n+\n+// p is a patch from commit to other.\n+void fill_line_map(struct commit* commit, struct commit* other, struct patch* p)\n+{\n+    int num_lines = ((struct util_info*) commit->object.util)->num_lines;\n+    int* line_map = ((struct util_info*) commit->object.util)->line_map;\n+    int num_lines2 = ((struct util_info*) other->object.util)->num_lines;\n+    int* line_map2 = ((struct util_info*) other->object.util)->line_map;\n+    int cur_chunk = 0;\n+    int i1, i2;\n+\n+    if(p->num && DEBUG)\n+        print_patch(p);\n+    \n+    for(i1 = 0; i1 < num_lines; i1++)\n+        line_map[i1] = -1;\n+\n+    if(DEBUG)\n+        printf(\"num lines 1: %d num lines 2: %d\\n\", num_lines, num_lines2);\n+    \n+    for(i1 = 0, i2 = 0; i1 < num_lines; i1++, i2++) {\n+        if(DEBUG > 1)\n+            printf(\"%d %d\\n\", i1, i2);\n+\n+        if(i2 >= num_lines2)\n+            break;\n+        \n+        line_map[i1] = line_map2[i2];\n+\n+        struct chunk* chunk = NULL;\n+        if(cur_chunk < p->num)\n+            chunk = &p->chunks[cur_chunk];\n+        \n+        if(chunk && chunk->off1 == i1) {\n+            i2 = chunk->off2;\n+\n+            if(chunk->len1 > 0)\n+                i1 += chunk->len1-1;\n+            if(chunk->len2 > 0)\n+                i2 += chunk->len2-1;\n+            cur_chunk++;\n+        }\n+    }\n+}\n+\n+int map_line(struct commit* commit, int line)\n+{\n+    struct util_info* info = commit->object.util;\n+    assert(line >= 0 && line < info->num_lines);\n+    return info->line_map[line];\n+}\n+\n+int fill_util_info(struct commit* commit, const char* path)\n+{\n+    if(commit->object.util)\n+        return 0;\n+    \n+    struct util_info* util = xmalloc(sizeof(struct util_info));\n+    util->buf = NULL;\n+    util->size = 0;\n+    util->num_lines = -1;\n+    util->line_map = NULL;\n+\n+    commit->object.util = util;\n+    \n+    if(get_blob_sha1(commit->tree, path, util->sha1))\n+        return -1;\n+\n+    return 0;\n+}\n+\n+void alloc_line_map(struct commit* commit)\n+{\n+    struct util_info* util = commit->object.util;\n+\n+    if(util->line_map)\n+        return;\n+\n+    get_blob(commit);\n+    \n+    int i;\n+    util->num_lines = 0;\n+    for(i = 0; i < util->size; i++) {\n+        if(util->buf[i] == '\\n')\n+            util->num_lines++;\n+    }\n+    util->line_map = xmalloc(sizeof(int)*util->num_lines);\n+}\n+\n+void copy_line_map(struct commit* dst, struct commit* src)\n+{\n+    struct util_info* u_dst = dst->object.util;\n+    struct util_info* u_src = src->object.util;\n+\n+    u_dst->line_map = u_src->line_map;\n+    u_dst->num_lines = u_src->num_lines;\n+    u_dst->buf = u_src->buf;\n+    u_dst->size = u_src->size;\n+}\n+    \n+void process_commits(struct commit_list* list, const char* path)\n+{\n+    int i;\n+    \n+    while(list) {\n+        struct commit* commit = pop_commit(&list);\n+        struct commit_list* parents;\n+        struct util_info* info;\n+\n+        info = commit->object.util;\n+        num_commits++;\n+        if(DEBUG)\n+            printf(\"\\nProcessing commit: %d %s\\n\", num_commits, sha1_to_hex(commit->object.sha1));\n+        for(parents = commit->parents;\n+            parents != NULL; parents = parents->next) {\n+            struct commit* parent = parents->item;\n+            \n+            if(parse_commit(parent) < 0)\n+                die(\"parse_commit error\");\n+\n+            if(DEBUG)\n+                printf(\"parent: %s\\n\", sha1_to_hex(parent->object.sha1));\n+\n+            if(fill_util_info(parent, path))\n+                continue;\n+\n+            // Temporarily assign everything to the parent.\n+            int num_blame = 0;\n+            for(i = 0; i < num_blame_lines; i++) {\n+                if(blame_lines[i] == commit) {\n+                    num_blame++;\n+                    blame_lines[i] = parent;\n+                }\n+            }\n+\n+            if(num_blame == 0)\n+                continue;\n+            \n+            struct patch* patch = get_patch(parent, commit);\n+            if(patch->num == 0) {\n+                copy_line_map(parent, commit);\n+            } else {\n+                alloc_line_map(parent);\n+                fill_line_map(parent, commit, patch);\n+            }\n+\n+            for(i = 0; i < patch->num; i++) {\n+                int l;\n+                for(l = 0; l < patch->chunks[i].len2; l++) {\n+                    int mapped_line = map_line(commit, patch->chunks[i].off2 + l);\n+                    if(mapped_line != -1 && blame_lines[mapped_line] == parent)\n+                        blame_lines[mapped_line] = commit;\n+                }\n+            }\n+            free_patch(patch);\n+        }\n+    }\n+}\n+\n+#define SEEN 1\n+struct commit_list* get_commit_list(struct commit* commit, const char* pathname)\n+{\n+    struct commit_list* ret = NULL;\n+    struct commit_list* process = NULL;\n+    unsigned char sha1[20];\n+    \n+    commit_list_insert(commit, &process);\n+\n+    while(process) {\n+        struct commit* com = pop_commit(&process);\n+        if(com->object.flags & SEEN)\n+            continue;\n+\n+        com->object.flags |= SEEN;\n+        commit_list_insert(com, &ret);\n+        struct commit_list* parents;\n+\n+        parse_commit(com);\n+        \n+        for(parents = com->parents;\n+            parents != NULL; parents = parents->next) {\n+            struct commit* parent = parents->item;\n+\n+            parse_commit(parent);\n+            \n+            if(!get_blob_sha1(parent->tree, pathname, sha1))\n+                commit_list_insert(parent, &process);\n+        }\n+    }\n+    \n+    return ret;\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+    unsigned char sha1[20];\n+    struct commit *commit;\n+    const char* filename;\n+    int i;\n+    \n+    setup_git_directory();\n+\n+    if (argc != 3)\n+        die(\"Usage: blame commit-ish file\");\n+        \n+    if (get_sha1(argv[1], sha1))\n+        die(\"get_sha1 failed\");\n+\n+    commit = lookup_commit_reference(sha1);\n+\n+    filename = argv[2];\n+\n+    struct commit_list* list = get_commit_list(commit, filename);\n+    sort_in_topological_order(&list, 1);\n+\n+    if(fill_util_info(commit, filename)) {\n+        printf(\"%s not found in %s\\n\", filename, argv[1]);\n+        return 0;\n+    }\n+    alloc_line_map(commit);\n+\n+    struct util_info* util = commit->object.util;\n+    num_blame_lines = util->num_lines;\n+    blame_lines = xmalloc(sizeof(struct commit*)*num_blame_lines);\n+\n+\n+    for(i = 0; i < num_blame_lines; i++) {\n+        blame_lines[i] = commit;\n+\n+        ((struct util_info*) commit->object.util)->line_map[i] = i;\n+    }    \n+    \n+    process_commits(list, filename);\n+\n+    for(i = 0; i < num_blame_lines; i++) {\n+        printf(\"%d %s\\n\", i+1-1, sha1_to_hex(blame_lines[i]->object.sha1));\n+//        printf(\"%d %s\\n\", i+1-1, find_unique_abbrev(blame_lines[i]->object.sha1, 6));\n+    }\n+    \n+    if(DEBUG) {\n+        printf(\"num get patch: %d\\n\", num_get_patch);\n+        printf(\"num commits: %d\\n\", num_commits);\n+    }\n+\n+    return 0;\n+}\n-- \n1.2.1.g62a4-dirty\n"},{"id":"16484","messageId":"7vlkw57f63.fsf@assigned-by-dhcp.cox.net","threadId":"3404","inReplyTo":"20060220234054.GA7903@c165.ib.student.liu.se","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-02-21T00:01:56Z","receivedAt":"2006-02-21T00:01:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fredrik Kuivinen <freku045@student.liu.se> writes:\n\n> I have also been working on a blame program.\n\nVery nice to see these two.\n\nObviously I prefer this one for its performance ;-).\n\nIts interface is probably friendlier when used as a preprocessor\nby other tools.  I can imagine GUI source viewer that fontifies\nthe source code and prefers to get just the origin information\nfrom a \"blame backend\".\n\nBTW, these days I always compile things with \n\n\t-Wall -Wdeclaration-after-statement\n\nwhich caught quite a many.\n\nAlso I have my templates/hooks--pre-commit enabled so you might\nwant to lindent it before inclusion.\n\nI'll play with both a bit.  Thanks for nice toys, both of you!\n"},{"id":"16640","messageId":"20060223221048.GA6423@mythryan2.michonline.com","threadId":"3404","inReplyTo":"20060220234054.GA7903@c165.ib.student.liu.se","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Ryan Anderson","fromEmail":"ryan@michonline.com","sentAt":"2006-02-23T22:10:49Z","receivedAt":"2006-02-23T22:10:49Z","isPatch":true,"sender":{"key":"ryan@michonline.com","avatar":null},"body":"(Biased critique since I have the other tool in the tree, but still...)\n\nOn Tue, Feb 21, 2006 at 12:40:54AM +0100, Fredrik Kuivinen wrote:\n> diff --git a/blame.c b/blame.c\n> new file mode 100644\n> index 0000000..d4a2fad\n> --- /dev/null\n> +++ b/blame.c\n> @@ -0,0 +1,444 @@\n> +#include <assert.h>\n> +\n> +#include \"cache.h\"\n> +#include \"refs.h\"\n> +#include \"tag.h\"\n> +#include \"commit.h\"\n> +#include \"tree.h\"\n> +#include \"blob.h\"\n> +#include \"epoch.h\"\n> +#include \"diff.h\"\n> +\n> +#define DEBUG 0\n> +\n> +struct commit** blame_lines;\n> +int num_blame_lines;\n> +\n> +struct util_info\n> +{\n> +    int* line_map;\n> +    int num_lines;\n> +    unsigned char sha1[20]; /* blob sha, not commit! */\n> +    char* buf;\n> +    unsigned long size;\n> +//    const char* path;\n> +};\n> +\n> +struct chunk\n> +{\n> +    int off1, len1; // ---\n> +    int off2, len2; // +++\n> +};\n> +\n> +struct patch\n> +{\n> +    struct chunk* chunks;\n> +    int num;\n> +};\n> +\n> +static void get_blob(struct commit* commit);\n> +\n> +int num_get_patch = 0;\n> +int num_commits = 0;\n> +\n> +struct patch* get_patch(struct commit* commit, struct commit* other)\n> +{\n> +    struct patch* ret = xmalloc(sizeof(struct patch));\n> +    ret->chunks = NULL;\n> +    ret->num = 0;\n> +\n> +    struct util_info* info_c = (struct util_info*) commit->object.util;\n> +    struct util_info* info_o = (struct util_info*) other->object.util;\n> +\n> +    if(!memcmp(info_c->sha1, info_o->sha1, 20))\n> +        return ret;\n> +\n> +    get_blob(commit);\n> +    get_blob(other);\n> +\n> +    FILE* fout = fopen(\"/tmp/git-blame-tmp1\", \"w\");\n\nProbably should be using something like mkstemp (mkstmp?) here, so if\nsomeone is runnign things as root or with a malicous user around, things\ndon't collide.\n\nHell, so on a multi-user machine this doesn't blow up on you.\n\nBut, read down for a related comment.\n\n> +    if(!fout)\n> +        die(\"fopen tmp1 failed: %s\", strerror(errno));\n> +\n> +    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)\n> +        die(\"fwrite 1 failed: %s\", strerror(errno));\n> +    fclose(fout);\n> +\n> +    fout = fopen(\"/tmp/git-blame-tmp2\", \"w\");\n> +    if(!fout)\n> +        die(\"fopen tmp2 failed: %s\", strerror(errno));\n> +\n> +    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)\n> +        die(\"fwrite 2 failed: %s\", strerror(errno));\n> +    fclose(fout);\n> +\n> +    FILE* fin = popen(\"diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2\", \"r\");\n> +    if(!fin)\n> +        die(\"popen failed: %s\", strerror(errno));\n\nCan't git-diff-tree do this sufficiently, anyway?  See my Perl script\nfor an example, you just need both commit IDs and both filenames and the\nappropriate -M and you get the right results.\n\n(It's possible that's part of where the performance differences are,\nthough, not really sure at the moment.)\n\nI'm going to stop there for the moment, I'm not really confident in my\nunderstanding of git-internals to say much more just yet.\n\nThis could probably benefit a *LOT* from the libification project, I\nthink, though.\n\n\n-- \n\nRyan Anderson\n  sometimes Pug Majere\n"},{"id":"16643","messageId":"20060223225547.GB8673@c165.ib.student.liu.se","threadId":"3404","inReplyTo":"20060223221048.GA6423@mythryan2.michonline.com","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Fredrik Kuivinen","fromEmail":"freku045@student.liu.se","sentAt":"2006-02-23T22:55:47Z","receivedAt":"2006-02-23T22:55:47Z","isPatch":true,"sender":{"key":"frekui@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13770967?v=4"},"body":"On Thu, Feb 23, 2006 at 05:10:49PM -0500, Ryan Anderson wrote:\n> (Biased critique since I have the other tool in the tree, but still...)\n> \n> > +    FILE* fout = fopen(\"/tmp/git-blame-tmp1\", \"w\");\n> \n> Probably should be using something like mkstemp (mkstmp?) here, so if\n> someone is runnign things as root or with a malicous user around, things\n> don't collide.\n> \n> Hell, so on a multi-user machine this doesn't blow up on you.\n\nYep, I know. The code is mostly a proof of concept. I didn't submit it\nfor inclusion.\n\n\n> \n> But, read down for a related comment.\n> \n> > +    if(!fout)\n> > +        die(\"fopen tmp1 failed: %s\", strerror(errno));\n> > +\n> > +    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)\n> > +        die(\"fwrite 1 failed: %s\", strerror(errno));\n> > +    fclose(fout);\n> > +\n> > +    fout = fopen(\"/tmp/git-blame-tmp2\", \"w\");\n> > +    if(!fout)\n> > +        die(\"fopen tmp2 failed: %s\", strerror(errno));\n> > +\n> > +    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)\n> > +        die(\"fwrite 2 failed: %s\", strerror(errno));\n> > +    fclose(fout);\n> > +\n> > +    FILE* fin = popen(\"diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2\", \"r\");\n> > +    if(!fin)\n> > +        die(\"popen failed: %s\", strerror(errno));\n> \n> Can't git-diff-tree do this sufficiently, anyway?  See my Perl script\n> for an example, you just need both commit IDs and both filenames and the\n> appropriate -M and you get the right results.\n> \n> (It's possible that's part of where the performance differences are,\n> though, not really sure at the moment.)\n> \n\nYeah.. maybe. My first thought was to avoid forking and execing diff\nand use some C library for doing the diffing instead (libxdiff). But\nthen I just wanted to get some code working and the simplest solution\nI could think of was to fork and exec diff.\n\n> I'm going to stop there for the moment, I'm not really confident in my\n> understanding of git-internals to say much more just yet.\n> \n> This could probably benefit a *LOT* from the libification project, I\n> think, though.\n\nYes, perhaps. Some of the git-rev-list bits might simplify a couple of\nthings.\n\nI have found some severe problems with the code I posted, in\nparticular it doesn't handle parallel development tracks at all. I am\nworking on fixing it, but it isn't finished yet.\n\nThanks for the comments.\n\n- Fredrik\n"},{"id":"16644","messageId":"Pine.LNX.4.63.0602240055080.31816@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"3404","inReplyTo":"20060223225547.GB8673@c165.ib.student.liu.se","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-02-24T00:00:41Z","receivedAt":"2006-02-24T00:00:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 23 Feb 2006, Fredrik Kuivinen wrote:\n\n> On Thu, Feb 23, 2006 at 05:10:49PM -0500, Ryan Anderson wrote:\n> > (Biased critique since I have the other tool in the tree, but still...)\n> > \n> > > +    FILE* fout = fopen(\"/tmp/git-blame-tmp1\", \"w\");\n> > \n> > Probably should be using something like mkstemp (mkstmp?) here, so if\n> > someone is runnign things as root or with a malicous user around, things\n> > don't collide.\n> > \n> > Hell, so on a multi-user machine this doesn't blow up on you.\n> \n> Yep, I know. The code is mostly a proof of concept. I didn't submit it\n> for inclusion.\n\nHa ha, famous last words!\n\n> > But, read down for a related comment.\n> > \n> > > +    if(!fout)\n> > > +        die(\"fopen tmp1 failed: %s\", strerror(errno));\n> > > +\n> > > +    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)\n> > > +        die(\"fwrite 1 failed: %s\", strerror(errno));\n> > > +    fclose(fout);\n> > > +\n> > > +    fout = fopen(\"/tmp/git-blame-tmp2\", \"w\");\n> > > +    if(!fout)\n> > > +        die(\"fopen tmp2 failed: %s\", strerror(errno));\n> > > +\n> > > +    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)\n> > > +        die(\"fwrite 2 failed: %s\", strerror(errno));\n> > > +    fclose(fout);\n> > > +\n> > > +    FILE* fin = popen(\"diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2\", \"r\");\n> > > +    if(!fin)\n> > > +        die(\"popen failed: %s\", strerror(errno));\n> > \n> > Can't git-diff-tree do this sufficiently, anyway?  See my Perl script\n> > for an example, you just need both commit IDs and both filenames and the\n> > appropriate -M and you get the right results.\n> > \n> > (It's possible that's part of where the performance differences are,\n> > though, not really sure at the moment.)\n> > \n> \n> Yeah.. maybe. My first thought was to avoid forking and execing diff\n> and use some C library for doing the diffing instead (libxdiff). But\n> then I just wanted to get some code working and the simplest solution\n> I could think of was to fork and exec diff.\n\ngit-diff-tree fork()s a diff. So, by fork()ing git-diff-tree you get two \nfork()s (and no knife...).\n\n> > I'm going to stop there for the moment, I'm not really confident in my\n> > understanding of git-internals to say much more just yet.\n> > \n> > This could probably benefit a *LOT* from the libification project, I\n> > think, though.\n> \n> Yes, perhaps. Some of the git-rev-list bits might simplify a couple of\n> things.\n\nThe major problem is probably not solved: What Linus calls a \"stream \ninterface\".\n\nI.e. if you pipe the output of git-rev-list to another program, you \n*need* to execute the two semi-simultaneously. The \"alternative\" would be \nto use buffers, which can get huge (and are sometimes not needed: think \ngit-whatchanged, which starts outputting before it's getting no more \ninput).\n\n> I have found some severe problems with the code I posted, in\n> particular it doesn't handle parallel development tracks at all. I am\n> working on fixing it, but it isn't finished yet.\n\nLooking forward to them!\n\nCiao,\nDscho\n"},{"id":"16645","messageId":"7vek1thaop.fsf@assigned-by-dhcp.cox.net","threadId":"3404","inReplyTo":"Pine.LNX.4.63.0602240055080.31816@wbgn013.biozentrum.uni-wuerzburg.de","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-02-24T00:17:42Z","receivedAt":"2006-02-24T00:17:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> > This could probably benefit a *LOT* from the libification project, I\n>> > think, though.\n>> \n>> Yes, perhaps. Some of the git-rev-list bits might simplify a couple of\n>> things.\n>\n> The major problem is probably not solved: What Linus calls a \"stream \n> interface\".\n>\n> I.e. if you pipe the output of git-rev-list to another program, you \n> *need* to execute the two semi-simultaneously. The \"alternative\" would be \n> to use buffers, which can get huge (and are sometimes not needed: think \n> git-whatchanged, which starts outputting before it's getting no more \n> input).\n\nYou need a limited coroutine support, something like generator\nfunctions in Python ;-).  In C, traditional way of doing it is\nto make your application specific function a callback of\nrev-list or whatever generator is, which is very unpleasant to\ncode.\n\n>> I have found some severe problems with the code I posted, in\n>> particular it doesn't handle parallel development tracks at all. I am\n>> working on fixing it, but it isn't finished yet.\n>\n> Looking forward to them!\n\nLikewise.\n"},{"id":"16648","messageId":"Pine.LNX.4.63.0602240148250.32472@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"3404","inReplyTo":"7vek1thaop.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-02-24T00:52:38Z","receivedAt":"2006-02-24T00:52:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 23 Feb 2006, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> >> > This could probably benefit a *LOT* from the libification project, I\n> >> > think, though.\n> >> \n> >> Yes, perhaps. Some of the git-rev-list bits might simplify a couple of\n> >> things.\n> >\n> > The major problem is probably not solved: What Linus calls a \"stream \n> > interface\".\n> >\n> > I.e. if you pipe the output of git-rev-list to another program, you \n> > *need* to execute the two semi-simultaneously. The \"alternative\" would be \n> > to use buffers, which can get huge (and are sometimes not needed: think \n> > git-whatchanged, which starts outputting before it's getting no more \n> > input).\n> \n> You need a limited coroutine support, something like generator\n> functions in Python ;-).  In C, traditional way of doing it is\n> to make your application specific function a callback of\n> rev-list or whatever generator is, which is very unpleasant to\n> code.\n\nThe most unpleasant aspect is that you usually need something like \"this\" \nin C++: a pointer to an object (which you have to pass around all the \ntime). Without it you can not use the function in a nested way.\n\nHowever, I can also see benefits of this when compared to the traditional \nUNIX approach. It should be faster, for one, since you don't need to pass \ndata through pipes all the time. (This might be not as true for Linux as \nfor other OSes.)\n\nCiao,\nDscho\n"},{"id":"16891","messageId":"20060228082736.GA4593@c165.ib.student.liu.se","threadId":"3404","inReplyTo":"7vlkw57f63.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Fredrik Kuivinen","fromEmail":"freku045@student.liu.se","sentAt":"2006-02-28T08:27:36Z","receivedAt":"2006-02-28T08:27:36Z","isPatch":true,"sender":{"key":"frekui@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13770967?v=4"},"body":"On Mon, Feb 20, 2006 at 04:01:56PM -0800, Junio C Hamano wrote:\n> Fredrik Kuivinen <freku045@student.liu.se> writes:\n> \n> > I have also been working on a blame program.\n\n...\n\n> BTW, these days I always compile things with \n> \n> \t-Wall -Wdeclaration-after-statement\n> \n> which caught quite a many.\n\nJust out of curiosity, why do you prefer declarations before\nstatements?\n\n- Fredrik\n"},{"id":"16892","messageId":"7v64mzq3n8.fsf@assigned-by-dhcp.cox.net","threadId":"3404","inReplyTo":"20060228082736.GA4593@c165.ib.student.liu.se","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-02-28T08:38:35Z","receivedAt":"2006-02-28T08:38:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Fredrik Kuivinen <freku045@student.liu.se> writes:\n\n> On Mon, Feb 20, 2006 at 04:01:56PM -0800, Junio C Hamano wrote:\n>> \n>> BTW, these days I always compile things with \n>> \n>> \t-Wall -Wdeclaration-after-statement\n>> \n>> which caught quite a many.\n>\n> Just out of curiosity, why do you prefer declarations before\n> statements?\n\nInertia, IOW, mostly being used to read code written that way.\n"},{"id":"16893","messageId":"20060228084737.GA13537@mythryan2.michonline.com","threadId":"3404","inReplyTo":"20060228082736.GA4593@c165.ib.student.liu.se","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Ryan Anderson","fromEmail":"ryan@michonline.com","sentAt":"2006-02-28T08:47:37Z","receivedAt":"2006-02-28T08:47:37Z","isPatch":true,"sender":{"key":"ryan@michonline.com","avatar":null},"body":"On Tue, Feb 28, 2006 at 09:27:36AM +0100, Fredrik Kuivinen wrote:\n> On Mon, Feb 20, 2006 at 04:01:56PM -0800, Junio C Hamano wrote:\n> > Fredrik Kuivinen <freku045@student.liu.se> writes:\n> > \n> > > I have also been working on a blame program.\n> \n> ...\n> \n> > BTW, these days I always compile things with \n> > \n> > \t-Wall -Wdeclaration-after-statement\n> > \n> > which caught quite a many.\n> \n> Just out of curiosity, why do you prefer declarations before\n> statements?\n\nI won't speak for Junio, but the explanations I've heard in the past are\nbasically:\n\n1) It keeps all declarations in one spot.\n2) If your function is complicated enough to not need a variable until\nfairly far into the function, it probably should be two (or more) functions.\n\nBasically, I think that there's not wrong with doing it that way, per\nse, just that it's sometimes a symptom of other problems, so fi you look\nfor the symptom, the problem sometimes is more obvious.\n\n\n-- \n\nRyan Anderson\n  sometimes Pug Majere\n"},{"id":"16894","messageId":"440412F5.9050909@op5.se","threadId":"3404","inReplyTo":"20060228084737.GA13537@mythryan2.michonline.com","subject":"Re: [PATCH] Add git-annotate, a tool for assigning blame.","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2006-02-28T09:08:05Z","receivedAt":"2006-02-28T09:08:05Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Ryan Anderson wrote:\n> On Tue, Feb 28, 2006 at 09:27:36AM +0100, Fredrik Kuivinen wrote:\n> \n>>On Mon, Feb 20, 2006 at 04:01:56PM -0800, Junio C Hamano wrote:\n>>\n>>>Fredrik Kuivinen <freku045@student.liu.se> writes:\n>>>\n>>>\n>>>>I have also been working on a blame program.\n>>\n>>...\n>>\n>>\n>>>BTW, these days I always compile things with \n>>>\n>>>\t-Wall -Wdeclaration-after-statement\n>>>\n>>>which caught quite a many.\n>>\n>>Just out of curiosity, why do you prefer declarations before\n>>statements?\n> \n> \n> I won't speak for Junio, but the explanations I've heard in the past are\n> basically:\n> \n> 1) It keeps all declarations in one spot.\n> 2) If your function is complicated enough to not need a variable until\n> fairly far into the function, it probably should be two (or more) functions.\n> \n\n3) Not all compilers support it.\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n"}]}