Create Gitweb::Parse module in 'gitweb/lib/Gitweb/Parse.pm' to store the subroutines which related to parsing functions required for gitweb. This module depends on Git.pm, Escape.pm, View.pm and Util.pm. Subroutines moved: unquote parse_date parse_tag parse_commit_text parse_commit parse_commits parse_difftree_raw_line parsed_difftree_line parse_ls_tree_line parse_from_to_diffinfo Update 'gitweb/Makefile' to install Gitweb::Parse alongside gitweb. Signed-off-by: Pavan Kumar Sunkara <pavan.sss1991@xxxxxxxxx> --- gitweb/Makefile | 1 + gitweb/gitweb.perl | 392 +----------------------------------------- gitweb/lib/Gitweb/Parse.pm | 414 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 416 insertions(+), 391 deletions(-) create mode 100644 gitweb/lib/Gitweb/Parse.pm diff --git a/gitweb/Makefile b/gitweb/Makefile index f1803f5..1c1bdca 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -120,6 +120,7 @@ GITWEB_MODULES += lib/Gitweb/RepoConfig.pm GITWEB_MODULES += lib/Gitweb/View.pm GITWEB_MODULES += lib/Gitweb/Util.pm GITWEB_MODULES += lib/Gitweb/Format.pm +GITWEB_MODULES += lib/Gitweb/Parse.pm GITWEB_REPLACE = \ -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3de3de5..efa9bdb 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -33,6 +33,7 @@ use Gitweb::RepoConfig; use Gitweb::View; use Gitweb::Util; use Gitweb::Format; +use Gitweb::Parse; BEGIN { CGI->compile() if $ENV{'MOD_PERL'}; @@ -568,397 +569,6 @@ sub validate_refname { return $input; } -# git may return quoted and escaped filenames -sub unquote { - my $str = shift; - - sub unq { - my $seq = shift; - my %es = ( # character escape codes, aka escape sequences - 't' => "\t", # tab (HT, TAB) - 'n' => "\n", # newline (NL) - 'r' => "\r", # return (CR) - 'f' => "\f", # form feed (FF) - 'b' => "\b", # backspace (BS) - 'a' => "\a", # alarm (bell) (BEL) - 'e' => "\e", # escape (ESC) - 'v' => "\013", # vertical tab (VT) - ); - - if ($seq =~ m/^[0-7]{1,3}$/) { - # octal char sequence - return chr(oct($seq)); - } elsif (exists $es{$seq}) { - # C escape sequence, aka character escape code - return $es{$seq}; - } - # quoted ordinary character - return $seq; - } - - if ($str =~ m/^"(.*)"$/) { - # needs unquoting - $str = $1; - $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg; - } - return $str; -} - -## ---------------------------------------------------------------------- -## parse to hash functions - -sub parse_date { - my $epoch = shift; - my $tz = shift || "-0000"; - - my %date; - my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); - my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); - $date{'hour'} = $hour; - $date{'minute'} = $min; - $date{'mday'} = $mday; - $date{'day'} = $days[$wday]; - $date{'month'} = $months[$mon]; - $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", - $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; - $date{'mday-time'} = sprintf "%d %s %02d:%02d", - $mday, $months[$mon], $hour ,$min; - $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", - 1900+$year, 1+$mon, $mday, $hour ,$min, $sec; - - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); - ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); - $date{'hour_local'} = $hour; - $date{'minute_local'} = $min; - $date{'tz_local'} = $tz; - $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s", - 1900+$year, $mon+1, $mday, - $hour, $min, $sec, $tz); - return %date; -} - -sub parse_tag { - my $tag_id = shift; - my %tag; - my @comment; - - open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return; - $tag{'id'} = $tag_id; - while (my $line = <$fd>) { - chomp $line; - if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { - $tag{'object'} = $1; - } elsif ($line =~ m/^type (.+)$/) { - $tag{'type'} = $1; - } elsif ($line =~ m/^tag (.+)$/) { - $tag{'name'} = $1; - } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { - $tag{'author'} = $1; - $tag{'author_epoch'} = $2; - $tag{'author_tz'} = $3; - if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) { - $tag{'author_name'} = $1; - $tag{'author_email'} = $2; - } else { - $tag{'author_name'} = $tag{'author'}; - } - } elsif ($line =~ m/--BEGIN/) { - push @comment, $line; - last; - } elsif ($line eq "") { - last; - } - } - push @comment, <$fd>; - $tag{'comment'} = \@comment; - close $fd or return; - if (!defined $tag{'name'}) { - return - }; - return %tag -} - -sub parse_commit_text { - my ($commit_text, $withparents) = @_; - my @commit_lines = split '\n', $commit_text; - my %co; - - pop @commit_lines; # Remove '\0' - - if (! @commit_lines) { - return; - } - - my $header = shift @commit_lines; - if ($header !~ m/^[0-9a-fA-F]{40}/) { - return; - } - ($co{'id'}, my @parents) = split ' ', $header; - while (my $line = shift @commit_lines) { - last if $line eq "\n"; - if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { - $co{'tree'} = $1; - } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) { - push @parents, $1; - } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { - $co{'author'} = to_utf8($1); - $co{'author_epoch'} = $2; - $co{'author_tz'} = $3; - if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) { - $co{'author_name'} = $1; - $co{'author_email'} = $2; - } else { - $co{'author_name'} = $co{'author'}; - } - } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) { - $co{'committer'} = to_utf8($1); - $co{'committer_epoch'} = $2; - $co{'committer_tz'} = $3; - if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) { - $co{'committer_name'} = $1; - $co{'committer_email'} = $2; - } else { - $co{'committer_name'} = $co{'committer'}; - } - } - } - if (!defined $co{'tree'}) { - return; - }; - $co{'parents'} = \@parents; - $co{'parent'} = $parents[0]; - - foreach my $title (@commit_lines) { - $title =~ s/^ //; - if ($title ne "") { - $co{'title'} = chop_str($title, 80, 5); - # remove leading stuff of merges to make the interesting part visible - if (length($title) > 50) { - $title =~ s/^Automatic //; - $title =~ s/^merge (of|with) /Merge ... /i; - if (length($title) > 50) { - $title =~ s/(http|rsync):\/\///; - } - if (length($title) > 50) { - $title =~ s/(master|www|rsync)\.//; - } - if (length($title) > 50) { - $title =~ s/kernel.org:?//; - } - if (length($title) > 50) { - $title =~ s/\/pub\/scm//; - } - } - $co{'title_short'} = chop_str($title, 50, 5); - last; - } - } - if (! defined $co{'title'} || $co{'title'} eq "") { - $co{'title'} = $co{'title_short'} = '(no commit message)'; - } - # remove added spaces - foreach my $line (@commit_lines) { - $line =~ s/^ //; - } - $co{'comment'} = \@commit_lines; - - my $age = time - $co{'committer_epoch'}; - $co{'age'} = $age; - $co{'age_string'} = age_string($age); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); - if ($age > 60*60*24*7*2) { - $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - $co{'age_string_age'} = $co{'age_string'}; - } else { - $co{'age_string_date'} = $co{'age_string'}; - $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - } - return %co; -} - -sub parse_commit { - my ($commit_id) = @_; - my %co; - - local $/ = "\0"; - - open my $fd, "-|", git_cmd(), "rev-list", - "--parents", - "--header", - "--max-count=1", - $commit_id, - "--", - or die_error(500, "Open git-rev-list failed"); - %co = parse_commit_text(<$fd>, 1); - close $fd; - - return %co; -} - -sub parse_commits { - my ($commit_id, $maxcount, $skip, $filename, @args) = @_; - my @cos; - - $maxcount ||= 1; - $skip ||= 0; - - local $/ = "\0"; - - open my $fd, "-|", git_cmd(), "rev-list", - "--header", - @args, - ("--max-count=" . $maxcount), - ("--skip=" . $skip), - @extra_options, - $commit_id, - "--", - ($filename ? ($filename) : ()) - or die_error(500, "Open git-rev-list failed"); - while (my $line = <$fd>) { - my %co = parse_commit_text($line); - push @cos, \%co; - } - close $fd; - - return wantarray ? @cos : \@cos; -} - -# parse line of git-diff-tree "raw" output -sub parse_difftree_raw_line { - my $line = shift; - my %res; - - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { - $res{'from_mode'} = $1; - $res{'to_mode'} = $2; - $res{'from_id'} = $3; - $res{'to_id'} = $4; - $res{'status'} = $5; - $res{'similarity'} = $6; - if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied - ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7); - } else { - $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7); - } - } - # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh' - # combined diff (for merge commit) - elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) { - $res{'nparents'} = length($1); - $res{'from_mode'} = [ split(' ', $2) ]; - $res{'to_mode'} = pop @{$res{'from_mode'}}; - $res{'from_id'} = [ split(' ', $3) ]; - $res{'to_id'} = pop @{$res{'from_id'}}; - $res{'status'} = [ split('', $4) ]; - $res{'to_file'} = unquote($5); - } - # 'c512b523472485aef4fff9e57b229d9d243c967f' - elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { - $res{'commit'} = $1; - } - - return wantarray ? %res : \%res; -} - -# wrapper: return parsed line of git-diff-tree "raw" output -# (the argument might be raw line, or parsed info) -sub parsed_difftree_line { - my $line_or_ref = shift; - - if (ref($line_or_ref) eq "HASH") { - # pre-parsed (or generated by hand) - return $line_or_ref; - } else { - return parse_difftree_raw_line($line_or_ref); - } -} - -# parse line of git-ls-tree output -sub parse_ls_tree_line { - my $line = shift; - my %opts = @_; - my %res; - - if ($opts{'-l'}) { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s; - - $res{'mode'} = $1; - $res{'type'} = $2; - $res{'hash'} = $3; - $res{'size'} = $4; - if ($opts{'-z'}) { - $res{'name'} = $5; - } else { - $res{'name'} = unquote($5); - } - } else { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; - - $res{'mode'} = $1; - $res{'type'} = $2; - $res{'hash'} = $3; - if ($opts{'-z'}) { - $res{'name'} = $4; - } else { - $res{'name'} = unquote($4); - } - } - - return wantarray ? %res : \%res; -} - -# generates _two_ hashes, references to which are passed as 2 and 3 argument -sub parse_from_to_diffinfo { - my ($diffinfo, $from, $to, @parents) = @_; - - if ($diffinfo->{'nparents'}) { - # combined diff - $from->{'file'} = []; - $from->{'href'} = []; - fill_from_file_info($diffinfo, @parents) - unless exists $diffinfo->{'from_file'}; - for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { - $from->{'file'}[$i] = - defined $diffinfo->{'from_file'}[$i] ? - $diffinfo->{'from_file'}[$i] : - $diffinfo->{'to_file'}; - if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file - $from->{'href'}[$i] = href(action=>"blob", - hash_base=>$parents[$i], - hash=>$diffinfo->{'from_id'}[$i], - file_name=>$from->{'file'}[$i]); - } else { - $from->{'href'}[$i] = undef; - } - } - } else { - # ordinary (not combined) diff - $from->{'file'} = $diffinfo->{'from_file'}; - if ($diffinfo->{'status'} ne "A") { # not new (added) file - $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent, - hash=>$diffinfo->{'from_id'}, - file_name=>$from->{'file'}); - } else { - delete $from->{'href'}; - } - } - - $to->{'file'} = $diffinfo->{'to_file'}; - if (!is_deleted($diffinfo)) { # file exists in result - $to->{'href'} = href(action=>"blob", hash_base=>$hash, - hash=>$diffinfo->{'to_id'}, - file_name=>$to->{'file'}); - } else { - delete $to->{'href'}; - } -} - ## ...................................................................... ## functions printing or outputting HTML: div diff --git a/gitweb/lib/Gitweb/Parse.pm b/gitweb/lib/Gitweb/Parse.pm new file mode 100644 index 0000000..6eeef2c --- /dev/null +++ b/gitweb/lib/Gitweb/Parse.pm @@ -0,0 +1,414 @@ +#!/usr/bin/perl +# +# Gitweb::Parse -- gitweb's parse_* subs package +# +# This program is licensed under the GPLv2 + +package Gitweb::Parse; + +use strict; +use warnings; +use Exporter qw(import); + +our @EXPORT = qw(parse_date parse_tag parse_commit_text parse_commit + parse_commits parse_difftree_raw_line parsed_difftree_line + parse_ls_tree_line parse_from_to_diffinfo unquote); + +use Gitweb::Git qw(git_cmd); +use Gitweb::Request qw(@extra_options $hash $hash_parent); +use Gitweb::Escape qw(to_utf8); +use Gitweb::View qw(href chop_str age_string die_error); +use Gitweb::Util qw(fill_from_file_info is_deleted); + +# git may return quoted and escaped filenames +sub unquote { + my $str = shift; + + sub unq { + my $seq = shift; + my %es = ( # character escape codes, aka escape sequences + 't' => "\t", # tab (HT, TAB) + 'n' => "\n", # newline (NL) + 'r' => "\r", # return (CR) + 'f' => "\f", # form feed (FF) + 'b' => "\b", # backspace (BS) + 'a' => "\a", # alarm (bell) (BEL) + 'e' => "\e", # escape (ESC) + 'v' => "\013", # vertical tab (VT) + ); + + if ($seq =~ m/^[0-7]{1,3}$/) { + # octal char sequence + return chr(oct($seq)); + } elsif (exists $es{$seq}) { + # C escape sequence, aka character escape code + return $es{$seq}; + } + # quoted ordinary character + return $seq; + } + + if ($str =~ m/^"(.*)"$/) { + # needs unquoting + $str = $1; + $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg; + } + return $str; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub parse_date { + my $epoch = shift; + my $tz = shift || "-0000"; + + my %date; + my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); + $date{'hour'} = $hour; + $date{'minute'} = $min; + $date{'mday'} = $mday; + $date{'day'} = $days[$wday]; + $date{'month'} = $months[$mon]; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", + $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", + $mday, $months[$mon], $hour ,$min; + $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", + 1900+$year, 1+$mon, $mday, $hour ,$min, $sec; + + $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; + my $local = $epoch + ((int $1 + ($2/60)) * 3600); + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); + $date{'hour_local'} = $hour; + $date{'minute_local'} = $min; + $date{'tz_local'} = $tz; + $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s", + 1900+$year, $mon+1, $mday, + $hour, $min, $sec, $tz); + return %date; +} + +sub parse_tag { + my $tag_id = shift; + my %tag; + my @comment; + + open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return; + $tag{'id'} = $tag_id; + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { + $tag{'object'} = $1; + } elsif ($line =~ m/^type (.+)$/) { + $tag{'type'} = $1; + } elsif ($line =~ m/^tag (.+)$/) { + $tag{'name'} = $1; + } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { + $tag{'author'} = $1; + $tag{'author_epoch'} = $2; + $tag{'author_tz'} = $3; + if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) { + $tag{'author_name'} = $1; + $tag{'author_email'} = $2; + } else { + $tag{'author_name'} = $tag{'author'}; + } + } elsif ($line =~ m/--BEGIN/) { + push @comment, $line; + last; + } elsif ($line eq "") { + last; + } + } + push @comment, <$fd>; + $tag{'comment'} = \@comment; + close $fd or return; + if (!defined $tag{'name'}) { + return + }; + return %tag +} + +sub parse_commit_text { + my ($commit_text, $withparents) = @_; + my @commit_lines = split '\n', $commit_text; + my %co; + + pop @commit_lines; # Remove '\0' + + if (! @commit_lines) { + return; + } + + my $header = shift @commit_lines; + if ($header !~ m/^[0-9a-fA-F]{40}/) { + return; + } + ($co{'id'}, my @parents) = split ' ', $header; + while (my $line = shift @commit_lines) { + last if $line eq "\n"; + if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { + $co{'tree'} = $1; + } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) { + push @parents, $1; + } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { + $co{'author'} = to_utf8($1); + $co{'author_epoch'} = $2; + $co{'author_tz'} = $3; + if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) { + $co{'author_name'} = $1; + $co{'author_email'} = $2; + } else { + $co{'author_name'} = $co{'author'}; + } + } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) { + $co{'committer'} = to_utf8($1); + $co{'committer_epoch'} = $2; + $co{'committer_tz'} = $3; + if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) { + $co{'committer_name'} = $1; + $co{'committer_email'} = $2; + } else { + $co{'committer_name'} = $co{'committer'}; + } + } + } + if (!defined $co{'tree'}) { + return; + }; + $co{'parents'} = \@parents; + $co{'parent'} = $parents[0]; + + foreach my $title (@commit_lines) { + $title =~ s/^ //; + if ($title ne "") { + $co{'title'} = chop_str($title, 80, 5); + # remove leading stuff of merges to make the interesting part visible + if (length($title) > 50) { + $title =~ s/^Automatic //; + $title =~ s/^merge (of|with) /Merge ... /i; + if (length($title) > 50) { + $title =~ s/(http|rsync):\/\///; + } + if (length($title) > 50) { + $title =~ s/(master|www|rsync)\.//; + } + if (length($title) > 50) { + $title =~ s/kernel.org:?//; + } + if (length($title) > 50) { + $title =~ s/\/pub\/scm//; + } + } + $co{'title_short'} = chop_str($title, 50, 5); + last; + } + } + if (! defined $co{'title'} || $co{'title'} eq "") { + $co{'title'} = $co{'title_short'} = '(no commit message)'; + } + # remove added spaces + foreach my $line (@commit_lines) { + $line =~ s/^ //; + } + $co{'comment'} = \@commit_lines; + + my $age = time - $co{'committer_epoch'}; + $co{'age'} = $age; + $co{'age_string'} = age_string($age); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); + if ($age > 60*60*24*7*2) { + $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + $co{'age_string_age'} = $co{'age_string'}; + } else { + $co{'age_string_date'} = $co{'age_string'}; + $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + } + return %co; +} + +sub parse_commit { + my ($commit_id) = @_; + my %co; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--parents", + "--header", + "--max-count=1", + $commit_id, + "--", + or die_error(500, "Open git-rev-list failed"); + %co = parse_commit_text(<$fd>, 1); + close $fd; + + return %co; +} + +sub parse_commits { + my ($commit_id, $maxcount, $skip, $filename, @args) = @_; + my @cos; + + $maxcount ||= 1; + $skip ||= 0; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--header", + @args, + ("--max-count=" . $maxcount), + ("--skip=" . $skip), + @extra_options, + $commit_id, + "--", + ($filename ? ($filename) : ()) + or die_error(500, "Open git-rev-list failed"); + while (my $line = <$fd>) { + my %co = parse_commit_text($line); + push @cos, \%co; + } + close $fd; + + return wantarray ? @cos : \@cos; +} + +# parse line of git-diff-tree "raw" output +sub parse_difftree_raw_line { + my $line = shift; + my %res; + + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + $res{'from_mode'} = $1; + $res{'to_mode'} = $2; + $res{'from_id'} = $3; + $res{'to_id'} = $4; + $res{'status'} = $5; + $res{'similarity'} = $6; + if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied + ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7); + } else { + $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7); + } + } + # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh' + # combined diff (for merge commit) + elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) { + $res{'nparents'} = length($1); + $res{'from_mode'} = [ split(' ', $2) ]; + $res{'to_mode'} = pop @{$res{'from_mode'}}; + $res{'from_id'} = [ split(' ', $3) ]; + $res{'to_id'} = pop @{$res{'from_id'}}; + $res{'status'} = [ split('', $4) ]; + $res{'to_file'} = unquote($5); + } + # 'c512b523472485aef4fff9e57b229d9d243c967f' + elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { + $res{'commit'} = $1; + } + + return wantarray ? %res : \%res; +} + +# wrapper: return parsed line of git-diff-tree "raw" output +# (the argument might be raw line, or parsed info) +sub parsed_difftree_line { + my $line_or_ref = shift; + + if (ref($line_or_ref) eq "HASH") { + # pre-parsed (or generated by hand) + return $line_or_ref; + } else { + return parse_difftree_raw_line($line_or_ref); + } +} + +# parse line of git-ls-tree output +sub parse_ls_tree_line { + my $line = shift; + my %opts = @_; + my %res; + + if ($opts{'-l'}) { + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s; + + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + $res{'size'} = $4; + if ($opts{'-z'}) { + $res{'name'} = $5; + } else { + $res{'name'} = unquote($5); + } + } else { + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; + + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + if ($opts{'-z'}) { + $res{'name'} = $4; + } else { + $res{'name'} = unquote($4); + } + } + + return wantarray ? %res : \%res; +} + +# generates _two_ hashes, references to which are passed as 2 and 3 argument +sub parse_from_to_diffinfo { + my ($diffinfo, $from, $to, @parents) = @_; + + if ($diffinfo->{'nparents'}) { + # combined diff + $from->{'file'} = []; + $from->{'href'} = []; + fill_from_file_info($diffinfo, @parents) + unless exists $diffinfo->{'from_file'}; + for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) { + $from->{'file'}[$i] = + defined $diffinfo->{'from_file'}[$i] ? + $diffinfo->{'from_file'}[$i] : + $diffinfo->{'to_file'}; + if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file + $from->{'href'}[$i] = href(action=>"blob", + hash_base=>$parents[$i], + hash=>$diffinfo->{'from_id'}[$i], + file_name=>$from->{'file'}[$i]); + } else { + $from->{'href'}[$i] = undef; + } + } + } else { + # ordinary (not combined) diff + $from->{'file'} = $diffinfo->{'from_file'}; + if ($diffinfo->{'status'} ne "A") { # not new (added) file + $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, + file_name=>$from->{'file'}); + } else { + delete $from->{'href'}; + } + } + + $to->{'file'} = $diffinfo->{'to_file'}; + if (!is_deleted($diffinfo)) { # file exists in result + $to->{'href'} = href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, + file_name=>$to->{'file'}); + } else { + delete $to->{'href'}; + } +} + +1; -- 1.7.1.455.g8f441 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html