blob: ec73cb1256ba99a990648c548348ecdc3340a592 [file] [log] [blame]
Kay Sievers161332a2005-08-07 19:49:46 +02001#!/usr/bin/perl
2
Kay Sieversc994d622005-08-07 20:27:18 +02003# gitweb - simple web interface to track changes in git repositories
Kay Sievers22fafb92005-08-07 19:56:59 +02004#
Kay Sievers00cd0792006-05-22 14:30:47 +02005# (C) 2005-2006, Kay Sievers <kay.sievers@vrfy.org>
6# (C) 2005, Christian Gierke
Kay Sievers823d5dc2005-08-07 19:57:58 +02007#
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02008# This program is licensed under the GPLv2
Kay Sievers161332a2005-08-07 19:49:46 +02009
10use strict;
11use warnings;
Kay Sievers19806692005-08-07 20:26:27 +020012use CGI qw(:standard :escapeHTML -nosticky);
Kay Sievers7403d502005-08-07 20:23:49 +020013use CGI::Util qw(unescape);
Kay Sievers161332a2005-08-07 19:49:46 +020014use CGI::Carp qw(fatalsToBrowser);
Kay Sievers40c13812005-11-19 17:41:29 +010015use Encode;
Kay Sieversb87d78d2005-08-07 20:21:04 +020016use Fcntl ':mode';
Junio C Hamano7a13b992006-07-31 19:18:34 -070017use File::Find qw();
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +053018use File::Basename qw(basename);
Kay Sievers10bb9032005-11-23 04:26:40 +010019binmode STDOUT, ':utf8';
Kay Sievers161332a2005-08-07 19:49:46 +020020
Jakub Narebskib1f5f642006-12-28 00:00:52 +010021BEGIN {
Jakub Narebski3be8e722007-04-01 22:22:21 +020022 CGI->compile() if $ENV{'MOD_PERL'};
Jakub Narebskib1f5f642006-12-28 00:00:52 +010023}
24
Dennis Stosberg4a87b432006-06-21 15:07:08 +020025our $cgi = new CGI;
Junio C Hamano06c084d2006-08-02 13:50:20 -070026our $version = "++GIT_VERSION++";
Dennis Stosberg4a87b432006-06-21 15:07:08 +020027our $my_url = $cgi->url();
28our $my_uri = $cgi->url(-absolute => 1);
Kay Sievers44ad2972005-08-07 19:59:24 +020029
Alp Tokere130dda2006-07-12 23:55:10 +010030# core git executable to use
31# this can just be "git" if your webserver has a sensible PATH
Junio C Hamano06c084d2006-08-02 13:50:20 -070032our $GIT = "++GIT_BINDIR++/git";
Jakub Narebski3f7f27102006-06-21 09:48:04 +020033
Kay Sieversb87d78d2005-08-07 20:21:04 +020034# absolute fs-path which will be prepended to the project path
Dennis Stosberg4a87b432006-06-21 15:07:08 +020035#our $projectroot = "/pub/scm";
Junio C Hamano06c084d2006-08-02 13:50:20 -070036our $projectroot = "++GITWEB_PROJECTROOT++";
Kay Sieversb87d78d2005-08-07 20:21:04 +020037
Luke Luca5e9492007-10-16 20:45:25 -070038# fs traversing limit for getting project list
39# the number is relative to the projectroot
40our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
41
Kay Sieversb87d78d2005-08-07 20:21:04 +020042# target of the home link on top of all pages
Martin Waitz6132b7e2006-08-17 00:28:39 +020043our $home_link = $my_uri || "/";
Kay Sieversb87d78d2005-08-07 20:21:04 +020044
Yasushi SHOJI2de21fa2006-08-15 07:50:49 +090045# string of the home link on top of all pages
46our $home_link_str = "++GITWEB_HOME_LINK_STR++";
47
Alp Toker49da1da2006-07-11 21:10:26 +010048# name of your site or organization to appear in page titles
49# replace this with something more descriptive for clearer bookmarks
Petr Baudis8be28902006-10-24 05:18:39 +020050our $site_name = "++GITWEB_SITENAME++"
51 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
Alp Toker49da1da2006-07-11 21:10:26 +010052
Alan Chandlerb2d34762006-10-03 13:49:03 +010053# filename of html text to include at top of each page
54our $site_header = "++GITWEB_SITE_HEADER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020055# html text to include at home page
Junio C Hamano06c084d2006-08-02 13:50:20 -070056our $home_text = "++GITWEB_HOMETEXT++";
Alan Chandlerb2d34762006-10-03 13:49:03 +010057# filename of html text to include at bottom of each page
58our $site_footer = "++GITWEB_SITE_FOOTER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020059
Alan Chandlerb2d34762006-10-03 13:49:03 +010060# URI of stylesheets
61our @stylesheets = ("++GITWEB_CSS++");
Petr Baudis887a6122006-10-26 14:41:25 +020062# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
63our $stylesheet = undef;
Jakub Narebski9a7a62f2006-10-06 12:31:05 +020064# URI of GIT logo (72x27 size)
Junio C Hamano06c084d2006-08-02 13:50:20 -070065our $logo = "++GITWEB_LOGO++";
Jakub Narebski0b5deba2006-09-04 20:32:13 +020066# URI of GIT favicon, assumed to be image/png type
67our $favicon = "++GITWEB_FAVICON++";
Jakub Narebskiaedd9422006-06-17 11:23:56 +020068
Jakub Narebski9a7a62f2006-10-06 12:31:05 +020069# URI and label (title) of GIT logo link
70#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
71#our $logo_label = "git documentation";
72our $logo_url = "http://git.or.cz/";
73our $logo_label = "git homepage";
Junio C Hamano51a7c662006-09-23 12:36:01 -070074
Kay Sievers09bd7892005-08-07 20:21:23 +020075# source of projects list
Junio C Hamano06c084d2006-08-02 13:50:20 -070076our $projects_list = "++GITWEB_LIST++";
Kay Sieversb87d78d2005-08-07 20:21:04 +020077
Michael Hendricks55feb122007-07-04 18:36:48 -060078# the width (in characters) of the projects list "Description" column
79our $projects_list_description_width = 25;
80
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +020081# default order of projects list
82# valid values are none, project, descr, owner, and age
83our $default_projects_order = "project";
84
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +020085# show repository only if this file exists
86# (only effective if this variable evaluates to true)
87our $export_ok = "++GITWEB_EXPORT_OK++";
88
89# only allow viewing of repositories also shown on the overview page
90our $strict_export = "++GITWEB_STRICT_EXPORT++";
91
Jakub Narebski19a87212006-08-15 23:03:17 +020092# list of git base URLs used for URL to where fetch project from,
93# i.e. full URL is "$git_base_url/$project"
Jakub Narebskid6b7e0b2006-10-26 12:26:44 +020094our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
Jakub Narebski19a87212006-08-15 23:03:17 +020095
Jakub Narebskif5aa79d2006-06-17 13:32:15 +020096# default blob_plain mimetype and default charset for text/plain blob
Dennis Stosberg4a87b432006-06-21 15:07:08 +020097our $default_blob_plain_mimetype = 'text/plain';
98our $default_text_plain_charset = undef;
Jakub Narebskif5aa79d2006-06-17 13:32:15 +020099
Petr Baudis2d007372006-06-18 00:01:06 +0200100# file to use for guessing MIME types before trying /etc/mime.types
101# (relative to the current git repository)
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200102our $mimetypes_file = undef;
Petr Baudis2d007372006-06-18 00:01:06 +0200103
Martin Koegler00f429a2007-06-03 17:42:44 +0200104# assume this charset if line contains non-UTF-8 characters;
105# it should be valid encoding (see Encoding::Supported(3pm) for list),
106# for which encoding all byte sequences are valid, for example
107# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
108# could be even 'utf-8' for the old behavior)
109our $fallback_encoding = 'latin1';
110
Jakub Narebski69a9b412007-07-20 02:15:09 +0200111# rename detection options for git-diff and git-diff-tree
112# - default is '-M', with the cost proportional to
113# (number of removed files) * (number of new files).
114# - more costly is '-C' (which implies '-M'), with the cost proportional to
115# (number of changed files + number of removed files) * (number of new files)
116# - even more costly is '-C', '--find-copies-harder' with cost
117# (number of files in the original tree) * (number of new files)
118# - one might want to include '-B' option, e.g. '-B', '-M'
119our @diff_opts = ('-M'); # taken from git_commit
120
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200121# information about snapshot formats that gitweb is capable of serving
122our %known_snapshot_formats = (
123 # name => {
124 # 'display' => display name,
125 # 'type' => mime type,
126 # 'suffix' => filename suffix,
127 # 'format' => --format for git-archive,
128 # 'compressor' => [compressor command and arguments]
129 # (array reference, optional)}
130 #
131 'tgz' => {
132 'display' => 'tar.gz',
133 'type' => 'application/x-gzip',
134 'suffix' => '.tar.gz',
135 'format' => 'tar',
136 'compressor' => ['gzip']},
137
138 'tbz2' => {
139 'display' => 'tar.bz2',
140 'type' => 'application/x-bzip2',
141 'suffix' => '.tar.bz2',
142 'format' => 'tar',
143 'compressor' => ['bzip2']},
144
145 'zip' => {
146 'display' => 'zip',
147 'type' => 'application/x-zip',
148 'suffix' => '.zip',
149 'format' => 'zip'},
150);
151
152# Aliases so we understand old gitweb.snapshot values in repository
153# configuration.
154our %known_snapshot_format_aliases = (
155 'gzip' => 'tgz',
156 'bzip2' => 'tbz2',
157
158 # backward compatibility: legacy gitweb config support
159 'x-gzip' => undef, 'gz' => undef,
160 'x-bzip2' => undef, 'bz2' => undef,
161 'x-zip' => undef, '' => undef,
162);
163
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530164# You define site-wide feature defaults here; override them with
165# $GITWEB_CONFIG as necessary.
Jakub Narebski952c65f2006-08-22 16:52:50 +0200166our %feature = (
Jakub Narebski17848fc2006-08-26 19:14:22 +0200167 # feature => {
168 # 'sub' => feature-sub (subroutine),
169 # 'override' => allow-override (boolean),
170 # 'default' => [ default options...] (array reference)}
171 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200172 # if feature is overridable (it means that allow-override has true value),
Jakub Narebski17848fc2006-08-26 19:14:22 +0200173 # then feature-sub will be called with default options as parameters;
174 # return value of feature-sub indicates if to enable specified feature
175 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200176 # if there is no 'sub' key (no feature-sub), then feature cannot be
177 # overriden
178 #
Jakub Narebski17848fc2006-08-26 19:14:22 +0200179 # use gitweb_check_feature(<feature>) to check if <feature> is enabled
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530180
Petr Baudis45a3b122006-10-07 15:17:47 +0200181 # Enable the 'blame' blob view, showing the last commit that modified
182 # each line in the file. This can be very CPU-intensive.
183
184 # To enable system wide have in $GITWEB_CONFIG
185 # $feature{'blame'}{'default'} = [1];
186 # To have project specific config enable override in $GITWEB_CONFIG
187 # $feature{'blame'}{'override'} = 1;
188 # and in project config gitweb.blame = 0|1;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200189 'blame' => {
190 'sub' => \&feature_blame,
191 'override' => 0,
192 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530193
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200194 # Enable the 'snapshot' link, providing a compressed archive of any
Petr Baudis45a3b122006-10-07 15:17:47 +0200195 # tree. This can potentially generate high traffic if you have large
196 # project.
197
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200198 # Value is a list of formats defined in %known_snapshot_formats that
199 # you wish to offer.
Petr Baudis45a3b122006-10-07 15:17:47 +0200200 # To disable system wide have in $GITWEB_CONFIG
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200201 # $feature{'snapshot'}{'default'} = [];
Petr Baudis45a3b122006-10-07 15:17:47 +0200202 # To have project specific config enable override in $GITWEB_CONFIG
Uwe Zeisbergerbbee1d92006-12-08 12:44:31 +0100203 # $feature{'snapshot'}{'override'} = 1;
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200204 # and in project config, a comma-separated list of formats or "none"
205 # to disable. Example: gitweb.snapshot = tbz2,zip;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200206 'snapshot' => {
207 'sub' => \&feature_snapshot,
208 'override' => 0,
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200209 'default' => ['tgz']},
Jakub Narebski04f7a942006-09-11 00:29:27 +0200210
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000211 # Enable text search, which will list the commits which match author,
212 # committer or commit text to a given string. Enabled by default.
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200213 # Project specific override is not supported.
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000214 'search' => {
215 'override' => 0,
216 'default' => [1]},
217
Petr Baudise7738552007-05-17 04:31:12 +0200218 # Enable grep search, which will list the files in currently selected
219 # tree containing the given string. Enabled by default. This can be
220 # potentially CPU-intensive, of course.
221
222 # To enable system wide have in $GITWEB_CONFIG
223 # $feature{'grep'}{'default'} = [1];
224 # To have project specific config enable override in $GITWEB_CONFIG
225 # $feature{'grep'}{'override'} = 1;
226 # and in project config gitweb.grep = 0|1;
227 'grep' => {
228 'override' => 0,
229 'default' => [1]},
230
Petr Baudis45a3b122006-10-07 15:17:47 +0200231 # Enable the pickaxe search, which will list the commits that modified
232 # a given string in a file. This can be practical and quite faster
233 # alternative to 'blame', but still potentially CPU-intensive.
234
235 # To enable system wide have in $GITWEB_CONFIG
236 # $feature{'pickaxe'}{'default'} = [1];
237 # To have project specific config enable override in $GITWEB_CONFIG
238 # $feature{'pickaxe'}{'override'} = 1;
239 # and in project config gitweb.pickaxe = 0|1;
Jakub Narebski04f7a942006-09-11 00:29:27 +0200240 'pickaxe' => {
241 'sub' => \&feature_pickaxe,
242 'override' => 0,
243 'default' => [1]},
Martin Waitz9e756902006-10-01 23:57:48 +0200244
Petr Baudis45a3b122006-10-07 15:17:47 +0200245 # Make gitweb use an alternative format of the URLs which can be
246 # more readable and natural-looking: project name is embedded
247 # directly in the path and the query string contains other
248 # auxiliary information. All gitweb installations recognize
249 # URL in either format; this configures in which formats gitweb
250 # generates links.
251
252 # To enable system wide have in $GITWEB_CONFIG
253 # $feature{'pathinfo'}{'default'} = [1];
254 # Project specific override is not supported.
255
256 # Note that you will need to change the default location of CSS,
257 # favicon, logo and possibly other files to an absolute URL. Also,
258 # if gitweb.cgi serves as your indexfile, you will need to force
259 # $my_uri to contain the script name in your $GITWEB_CONFIG.
Martin Waitz9e756902006-10-01 23:57:48 +0200260 'pathinfo' => {
261 'override' => 0,
262 'default' => [0]},
Petr Baudise30496d2006-10-24 05:33:17 +0200263
264 # Make gitweb consider projects in project root subdirectories
265 # to be forks of existing projects. Given project $projname.git,
266 # projects matching $projname/*.git will not be shown in the main
267 # projects list, instead a '+' mark will be added to $projname
268 # there and a 'forks' view will be enabled for the project, listing
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +0200269 # all the forks. If project list is taken from a file, forks have
270 # to be listed after the main project.
Petr Baudise30496d2006-10-24 05:33:17 +0200271
272 # To enable system wide have in $GITWEB_CONFIG
273 # $feature{'forks'}{'default'} = [1];
274 # Project specific override is not supported.
275 'forks' => {
276 'override' => 0,
277 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530278);
279
280sub gitweb_check_feature {
281 my ($name) = @_;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +0200282 return unless exists $feature{$name};
Jakub Narebski952c65f2006-08-22 16:52:50 +0200283 my ($sub, $override, @defaults) = (
284 $feature{$name}{'sub'},
285 $feature{$name}{'override'},
286 @{$feature{$name}{'default'}});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530287 if (!$override) { return @defaults; }
Martin Waitza9455912006-10-03 20:07:43 +0200288 if (!defined $sub) {
289 warn "feature $name is not overrideable";
290 return @defaults;
291 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530292 return $sub->(@defaults);
293}
294
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530295sub feature_blame {
296 my ($val) = git_get_project_config('blame', '--bool');
297
298 if ($val eq 'true') {
299 return 1;
300 } elsif ($val eq 'false') {
301 return 0;
302 }
303
304 return $_[0];
305}
306
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530307sub feature_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200308 my (@fmts) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530309
310 my ($val) = git_get_project_config('snapshot');
311
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200312 if ($val) {
313 @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530314 }
315
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200316 return @fmts;
Luben Tuikovde9272f2006-09-28 16:49:21 -0700317}
318
Petr Baudise7738552007-05-17 04:31:12 +0200319sub feature_grep {
320 my ($val) = git_get_project_config('grep', '--bool');
321
322 if ($val eq 'true') {
323 return (1);
324 } elsif ($val eq 'false') {
325 return (0);
326 }
327
328 return ($_[0]);
329}
330
Jakub Narebski04f7a942006-09-11 00:29:27 +0200331sub feature_pickaxe {
332 my ($val) = git_get_project_config('pickaxe', '--bool');
333
334 if ($val eq 'true') {
335 return (1);
336 } elsif ($val eq 'false') {
337 return (0);
338 }
339
340 return ($_[0]);
341}
342
Junio C Hamano2172ce42006-10-03 02:30:47 -0700343# checking HEAD file with -e is fragile if the repository was
344# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
345# and then pruned.
346sub check_head_link {
347 my ($dir) = @_;
348 my $headfile = "$dir/HEAD";
349 return ((-e $headfile) ||
350 (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
351}
352
353sub check_export_ok {
354 my ($dir) = @_;
355 return (check_head_link($dir) &&
356 (!$export_ok || -e "$dir/$export_ok"));
357}
358
Jakub Narebskia7817852007-07-22 23:41:20 +0200359# process alternate names for backward compatibility
360# filter out unsupported (unknown) snapshot formats
361sub filter_snapshot_fmts {
362 my @fmts = @_;
363
364 @fmts = map {
365 exists $known_snapshot_format_aliases{$_} ?
366 $known_snapshot_format_aliases{$_} : $_} @fmts;
367 @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
368
369}
370
Junio C Hamano06c084d2006-08-02 13:50:20 -0700371our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
Dennis Stosberg4b5dc982006-08-29 09:19:02 +0200372do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
Jeff Kingc8d138a2006-08-02 15:23:34 -0400373
374# version of the core git binary
375our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
376
377$projects_list ||= $projectroot;
Jeff Kingc8d138a2006-08-02 15:23:34 -0400378
Jakub Narebski154b4d72006-08-05 12:55:20 +0200379# ======================================================================
Kay Sievers09bd7892005-08-07 20:21:23 +0200380# input validation and dispatch
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200381our $action = $cgi->param('a');
Kay Sievers09bd7892005-08-07 20:21:23 +0200382if (defined $action) {
Kay Sieversc91da262005-09-03 14:50:33 +0200383 if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +0200384 die_error(undef, "Invalid action parameter");
Kay Sieversb87d78d2005-08-07 20:21:04 +0200385 }
Kay Sieversb87d78d2005-08-07 20:21:04 +0200386}
387
Jakub Narebski24d06932006-09-26 01:57:02 +0200388# parameters which are pathnames
Martin Waitzdd702352006-09-16 23:08:32 +0200389our $project = $cgi->param('p');
Martin Waitz13d02162006-08-17 00:28:40 +0200390if (defined $project) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200391 if (!validate_pathname($project) ||
Matthias Lederhofer7939fe42006-09-17 00:30:27 +0200392 !(-d "$projectroot/$project") ||
Junio C Hamano2172ce42006-10-03 02:30:47 -0700393 !check_head_link("$projectroot/$project") ||
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200394 ($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
395 ($strict_export && !project_in_list($project))) {
Matthias Lederhofer7939fe42006-09-17 00:30:27 +0200396 undef $project;
Jakub Narebskicac4bd92006-08-05 13:13:53 +0200397 die_error(undef, "No such project");
Kay Sievers9cd3d982005-08-07 20:17:42 +0200398 }
Kay Sievers2ad93312005-08-07 20:14:48 +0200399}
Kay Sievers6191f8e2005-08-07 20:19:56 +0200400
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200401our $file_name = $cgi->param('f');
Jakub Narebski24d06932006-09-26 01:57:02 +0200402if (defined $file_name) {
403 if (!validate_pathname($file_name)) {
404 die_error(undef, "Invalid file parameter");
405 }
406}
Martin Waitz5c95fab2006-08-17 00:28:38 +0200407
Jakub Narebski24d06932006-09-26 01:57:02 +0200408our $file_parent = $cgi->param('fp');
409if (defined $file_parent) {
410 if (!validate_pathname($file_parent)) {
411 die_error(undef, "Invalid file parent parameter");
412 }
413}
414
415# parameters which are refnames
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200416our $hash = $cgi->param('h');
Kay Sievers4fac5292005-08-07 20:27:38 +0200417if (defined $hash) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200418 if (!validate_refname($hash)) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +0200419 die_error(undef, "Invalid hash parameter");
Kay Sievers4fac5292005-08-07 20:27:38 +0200420 }
Kay Sieversa59d4af2005-08-07 20:15:44 +0200421}
Kay Sievers6191f8e2005-08-07 20:19:56 +0200422
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200423our $hash_parent = $cgi->param('hp');
Kay Sieversc91da262005-09-03 14:50:33 +0200424if (defined $hash_parent) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200425 if (!validate_refname($hash_parent)) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +0200426 die_error(undef, "Invalid hash parent parameter");
Kay Sieversc91da262005-09-03 14:50:33 +0200427 }
Kay Sievers09bd7892005-08-07 20:21:23 +0200428}
429
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200430our $hash_base = $cgi->param('hb');
Kay Sieversc91da262005-09-03 14:50:33 +0200431if (defined $hash_base) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200432 if (!validate_refname($hash_base)) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +0200433 die_error(undef, "Invalid hash base parameter");
Kay Sieversc91da262005-09-03 14:50:33 +0200434 }
Kay Sieversa59d4af2005-08-07 20:15:44 +0200435}
Kay Sievers6191f8e2005-08-07 20:19:56 +0200436
Miklos Vajna868bc062007-07-12 20:39:27 +0200437my %allowed_options = (
438 "--no-merges" => [ qw(rss atom log shortlog history) ],
439);
440
441our @extra_options = $cgi->param('opt');
442if (defined @extra_options) {
Jakub Narebski12075102007-07-28 16:27:32 +0200443 foreach my $opt (@extra_options) {
444 if (not exists $allowed_options{$opt}) {
Miklos Vajna868bc062007-07-12 20:39:27 +0200445 die_error(undef, "Invalid option parameter");
446 }
Jakub Narebski12075102007-07-28 16:27:32 +0200447 if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
Miklos Vajna868bc062007-07-12 20:39:27 +0200448 die_error(undef, "Invalid option parameter for this action");
449 }
450 }
451}
452
Jakub Narebski420e92f2006-08-24 23:53:54 +0200453our $hash_parent_base = $cgi->param('hpb');
454if (defined $hash_parent_base) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200455 if (!validate_refname($hash_parent_base)) {
Jakub Narebski420e92f2006-08-24 23:53:54 +0200456 die_error(undef, "Invalid hash parent base parameter");
457 }
458}
459
Jakub Narebski24d06932006-09-26 01:57:02 +0200460# other parameters
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200461our $page = $cgi->param('pg');
Kay Sieversea4a6df2005-08-07 20:26:49 +0200462if (defined $page) {
Matthias Lederhoferac8e3f22006-09-17 13:52:45 +0200463 if ($page =~ m/[^0-9]/) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +0200464 die_error(undef, "Invalid page parameter");
Kay Sieversb87d78d2005-08-07 20:21:04 +0200465 }
Kay Sieversa59d4af2005-08-07 20:15:44 +0200466}
Kay Sievers823d5dc2005-08-07 19:57:58 +0200467
Petr Baudise7738552007-05-17 04:31:12 +0200468our $searchtype = $cgi->param('st');
469if (defined $searchtype) {
470 if ($searchtype =~ m/[^a-z]/) {
471 die_error(undef, "Invalid searchtype parameter");
472 }
473}
474
Petr Baudis0e559912008-02-26 13:22:08 +0100475our $search_use_regexp = $cgi->param('sr');
476
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200477our $searchtext = $cgi->param('s');
Jakub Narebski7e431ef2007-05-16 01:56:10 +0200478our $search_regexp;
Kay Sievers19806692005-08-07 20:26:27 +0200479if (defined $searchtext) {
Robert Fitzsimons9d032c72006-12-23 03:35:15 +0000480 if (length($searchtext) < 2) {
481 die_error(undef, "At least two characters are required for search parameter");
482 }
Petr Baudis0e559912008-02-26 13:22:08 +0100483 $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
Kay Sievers19806692005-08-07 20:26:27 +0200484}
485
Martin Waitzdd702352006-09-16 23:08:32 +0200486# now read PATH_INFO and use it as alternative to parameters
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200487sub evaluate_path_info {
488 return if defined $project;
489 my $path_info = $ENV{"PATH_INFO"};
490 return if !$path_info;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200491 $path_info =~ s,^/+,,;
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200492 return if !$path_info;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200493 # find which part of PATH_INFO is project
Martin Waitzdd702352006-09-16 23:08:32 +0200494 $project = $path_info;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200495 $project =~ s,/+$,,;
Junio C Hamano2172ce42006-10-03 02:30:47 -0700496 while ($project && !check_head_link("$projectroot/$project")) {
Martin Waitzdd702352006-09-16 23:08:32 +0200497 $project =~ s,/*[^/]*$,,;
498 }
Jakub Narebskicd90e752006-09-20 00:49:51 +0200499 # validate project
Jakub Narebski24d06932006-09-26 01:57:02 +0200500 $project = validate_pathname($project);
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200501 if (!$project ||
502 ($export_ok && !-e "$projectroot/$project/$export_ok") ||
503 ($strict_export && !project_in_list($project))) {
504 undef $project;
505 return;
Martin Waitzdd702352006-09-16 23:08:32 +0200506 }
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200507 # do not change any parameters if an action is given using the query string
508 return if $action;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200509 $path_info =~ s,^$project/*,,;
510 my ($refname, $pathname) = split(/:/, $path_info, 2);
511 if (defined $pathname) {
512 # we got "project.git/branch:filename" or "project.git/branch:dir/"
513 # we could use git_get_type(branch:pathname), but it needs $git_dir
514 $pathname =~ s,^/+,,;
515 if (!$pathname || substr($pathname, -1) eq "/") {
516 $action ||= "tree";
Martin Waitz053d62b2006-09-21 09:48:21 +0200517 $pathname =~ s,/$,,;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200518 } else {
519 $action ||= "blob_plain";
520 }
Jakub Narebski24d06932006-09-26 01:57:02 +0200521 $hash_base ||= validate_refname($refname);
522 $file_name ||= validate_pathname($pathname);
Jakub Narebskicd90e752006-09-20 00:49:51 +0200523 } elsif (defined $refname) {
Martin Waitzdd702352006-09-16 23:08:32 +0200524 # we got "project.git/branch"
525 $action ||= "shortlog";
Jakub Narebski24d06932006-09-26 01:57:02 +0200526 $hash ||= validate_refname($refname);
Martin Waitzdd702352006-09-16 23:08:32 +0200527 }
528}
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200529evaluate_path_info();
Martin Waitzdd702352006-09-16 23:08:32 +0200530
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200531# path to the current git repository
532our $git_dir;
533$git_dir = "$projectroot/$project" if $project;
Martin Waitzdd702352006-09-16 23:08:32 +0200534
Jakub Narebski717b8312006-07-31 21:22:15 +0200535# dispatch
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200536my %actions = (
537 "blame" => \&git_blame2,
538 "blobdiff" => \&git_blobdiff,
539 "blobdiff_plain" => \&git_blobdiff_plain,
540 "blob" => \&git_blob,
541 "blob_plain" => \&git_blob_plain,
542 "commitdiff" => \&git_commitdiff,
543 "commitdiff_plain" => \&git_commitdiff_plain,
544 "commit" => \&git_commit,
Petr Baudise30496d2006-10-24 05:33:17 +0200545 "forks" => \&git_forks,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200546 "heads" => \&git_heads,
547 "history" => \&git_history,
548 "log" => \&git_log,
549 "rss" => \&git_rss,
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +0100550 "atom" => \&git_atom,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200551 "search" => \&git_search,
Petr Baudis88ad7292006-10-24 05:15:46 +0200552 "search_help" => \&git_search_help,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200553 "shortlog" => \&git_shortlog,
554 "summary" => \&git_summary,
555 "tag" => \&git_tag,
556 "tags" => \&git_tags,
557 "tree" => \&git_tree,
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +0530558 "snapshot" => \&git_snapshot,
Jakub Narebskica946012006-12-10 13:25:47 +0100559 "object" => \&git_object,
Jakub Narebski77a153f2006-08-22 16:59:20 +0200560 # those below don't need $project
561 "opml" => \&git_opml,
562 "project_list" => \&git_project_list,
Jakub Narebskifc2b2be2006-09-15 04:56:03 +0200563 "project_index" => \&git_project_index,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200564);
565
Gerrit Pape7f9778b2007-05-10 07:32:07 +0000566if (!defined $action) {
567 if (defined $hash) {
568 $action = git_get_type($hash);
569 } elsif (defined $hash_base && defined $file_name) {
570 $action = git_get_type("$hash_base:$file_name");
571 } elsif (defined $project) {
572 $action = 'summary';
573 } else {
574 $action = 'project_list';
575 }
Jakub Narebski77a153f2006-08-22 16:59:20 +0200576}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200577if (!defined($actions{$action})) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +0200578 die_error(undef, "Unknown action");
Kay Sievers09bd7892005-08-07 20:21:23 +0200579}
Jakub Narebskid04d3d42006-09-19 21:53:22 +0200580if ($action !~ m/^(opml|project_list|project_index)$/ &&
581 !$project) {
582 die_error(undef, "Project needed");
583}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200584$actions{$action}->();
585exit;
Kay Sievers09bd7892005-08-07 20:21:23 +0200586
Jakub Narebski717b8312006-07-31 21:22:15 +0200587## ======================================================================
Martin Waitz06a9d862006-08-16 00:23:50 +0200588## action links
589
590sub href(%) {
Jakub Narebski498fe002006-08-22 19:05:25 +0200591 my %params = @_;
Jakub Narebskibd5d1e42006-11-19 15:05:21 +0100592 # default is to use -absolute url() i.e. $my_uri
593 my $href = $params{-full} ? $my_url : $my_uri;
Jakub Narebski498fe002006-08-22 19:05:25 +0200594
Petr Baudis34c06112006-10-11 22:31:15 +0200595 # XXX: Warning: If you touch this, check the search form for updating,
596 # too.
Jakub Narebski498fe002006-08-22 19:05:25 +0200597
598 my @mapping = (
Martin Waitz06a9d862006-08-16 00:23:50 +0200599 project => "p",
Jakub Narebski420e92f2006-08-24 23:53:54 +0200600 action => "a",
Martin Waitz06a9d862006-08-16 00:23:50 +0200601 file_name => "f",
Martin Waitz5c95fab2006-08-17 00:28:38 +0200602 file_parent => "fp",
Martin Waitz06a9d862006-08-16 00:23:50 +0200603 hash => "h",
604 hash_parent => "hp",
605 hash_base => "hb",
Jakub Narebski420e92f2006-08-24 23:53:54 +0200606 hash_parent_base => "hpb",
Martin Waitz06a9d862006-08-16 00:23:50 +0200607 page => "pg",
Jakub Narebskia1565c42006-09-15 19:30:34 +0200608 order => "o",
Martin Waitz06a9d862006-08-16 00:23:50 +0200609 searchtext => "s",
Petr Baudis88ad7292006-10-24 05:15:46 +0200610 searchtype => "st",
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200611 snapshot_format => "sf",
Jakub Narebski12075102007-07-28 16:27:32 +0200612 extra_options => "opt",
Petr Baudis0e559912008-02-26 13:22:08 +0100613 search_use_regexp => "sr",
Martin Waitz06a9d862006-08-16 00:23:50 +0200614 );
Jakub Narebski498fe002006-08-22 19:05:25 +0200615 my %mapping = @mapping;
Martin Waitz06a9d862006-08-16 00:23:50 +0200616
Jakub Narebskiafa9b622008-02-14 09:22:30 +0100617 $params{'project'} = $project unless exists $params{'project'};
618
Jakub Narebski1cad2832007-11-01 13:06:27 +0100619 if ($params{-replay}) {
620 while (my ($name, $symbol) = each %mapping) {
621 if (!exists $params{$name}) {
622 # to allow for multivalued params we use arrayref form
623 $params{$name} = [ $cgi->param($symbol) ];
624 }
625 }
626 }
627
Martin Waitz9e756902006-10-01 23:57:48 +0200628 my ($use_pathinfo) = gitweb_check_feature('pathinfo');
629 if ($use_pathinfo) {
630 # use PATH_INFO for project name
631 $href .= "/$params{'project'}" if defined $params{'project'};
632 delete $params{'project'};
633
634 # Summary just uses the project path URL
635 if (defined $params{'action'} && $params{'action'} eq 'summary') {
636 delete $params{'action'};
637 }
638 }
639
640 # now encode the parameters explicitly
Jakub Narebski498fe002006-08-22 19:05:25 +0200641 my @result = ();
642 for (my $i = 0; $i < @mapping; $i += 2) {
643 my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]);
644 if (defined $params{$name}) {
Jakub Narebskif22cca42007-07-29 01:04:09 +0200645 if (ref($params{$name}) eq "ARRAY") {
646 foreach my $par (@{$params{$name}}) {
647 push @result, $symbol . "=" . esc_param($par);
648 }
649 } else {
650 push @result, $symbol . "=" . esc_param($params{$name});
651 }
Jakub Narebski498fe002006-08-22 19:05:25 +0200652 }
653 }
Martin Waitz9e756902006-10-01 23:57:48 +0200654 $href .= "?" . join(';', @result) if scalar @result;
655
656 return $href;
Martin Waitz06a9d862006-08-16 00:23:50 +0200657}
658
659
660## ======================================================================
Jakub Narebski717b8312006-07-31 21:22:15 +0200661## validation, quoting/unquoting and escaping
662
Jakub Narebski24d06932006-09-26 01:57:02 +0200663sub validate_pathname {
664 my $input = shift || return undef;
Jakub Narebski717b8312006-07-31 21:22:15 +0200665
Jakub Narebski24d06932006-09-26 01:57:02 +0200666 # no '.' or '..' as elements of path, i.e. no '.' nor '..'
667 # at the beginning, at the end, and between slashes.
668 # also this catches doubled slashes
669 if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
670 return undef;
671 }
672 # no null characters
673 if ($input =~ m!\0!) {
674 return undef;
675 }
676 return $input;
677}
678
679sub validate_refname {
680 my $input = shift || return undef;
681
682 # textual hashes are O.K.
Jakub Narebski717b8312006-07-31 21:22:15 +0200683 if ($input =~ m/^[0-9a-fA-F]{40}$/) {
684 return $input;
685 }
Jakub Narebski24d06932006-09-26 01:57:02 +0200686 # it must be correct pathname
687 $input = validate_pathname($input)
688 or return undef;
689 # restrictions on ref name according to git-check-ref-format
690 if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +0200691 return undef;
692 }
693 return $input;
694}
695
Martin Koegler00f429a2007-06-03 17:42:44 +0200696# decode sequences of octets in utf8 into Perl's internal form,
697# which is utf-8 with utf8 flag set if needed. gitweb writes out
698# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
699sub to_utf8 {
700 my $str = shift;
İsmail Dönmeze5d3de52007-12-04 10:55:41 +0200701 if (utf8::valid($str)) {
702 utf8::decode($str);
703 return $str;
Martin Koegler00f429a2007-06-03 17:42:44 +0200704 } else {
705 return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
706 }
707}
708
Kay Sievers232ff552005-11-24 16:56:55 +0100709# quote unsafe chars, but keep the slash, even when it's not
710# correct, but quoted slashes look too horrible in bookmarks
711sub esc_param {
Kay Sievers353347b2005-11-14 05:47:18 +0100712 my $str = shift;
Petr Baudisa2f3db22006-09-24 00:18:41 +0200713 $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg;
Kay Sievers18216712005-11-14 06:10:07 +0100714 $str =~ s/\+/%2B/g;
Kay Sieversa9e60b72005-11-14 15:15:12 +0100715 $str =~ s/ /\+/g;
Kay Sievers353347b2005-11-14 05:47:18 +0100716 return $str;
717}
718
Jakub Narebskif93bff82006-09-26 01:58:41 +0200719# quote unsafe chars in whole URL, so some charactrs cannot be quoted
720sub esc_url {
721 my $str = shift;
722 $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
723 $str =~ s/\+/%2B/g;
724 $str =~ s/ /\+/g;
725 return $str;
726}
727
Kay Sievers232ff552005-11-24 16:56:55 +0100728# replace invalid utf8 character with SUBSTITUTION sequence
Jakub Narebski6255ef02006-11-01 14:33:21 +0100729sub esc_html ($;%) {
Kay Sievers40c13812005-11-19 17:41:29 +0100730 my $str = shift;
Jakub Narebski6255ef02006-11-01 14:33:21 +0100731 my %opts = @_;
732
Martin Koegler00f429a2007-06-03 17:42:44 +0200733 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +0800734 $str = $cgi->escapeHTML($str);
Jakub Narebski6255ef02006-11-01 14:33:21 +0100735 if ($opts{'-nbsp'}) {
736 $str =~ s/ /&nbsp;/g;
737 }
Junio C Hamano25ffbb22006-11-08 15:11:10 -0800738 $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
Kay Sievers40c13812005-11-19 17:41:29 +0100739 return $str;
740}
741
Jakub Narebski391862e2006-11-25 09:43:59 +0100742# quote control characters and escape filename to HTML
743sub esc_path {
744 my $str = shift;
745 my %opts = @_;
746
Martin Koegler00f429a2007-06-03 17:42:44 +0200747 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +0800748 $str = $cgi->escapeHTML($str);
Jakub Narebski391862e2006-11-25 09:43:59 +0100749 if ($opts{'-nbsp'}) {
750 $str =~ s/ /&nbsp;/g;
751 }
752 $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
753 return $str;
754}
755
756# Make control characters "printable", using character escape codes (CEC)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100757sub quot_cec {
758 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +0100759 my %opts = @_;
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100760 my %es = ( # character escape codes, aka escape sequences
Jakub Narebskic84c4832008-02-17 18:48:13 +0100761 "\t" => '\t', # tab (HT)
762 "\n" => '\n', # line feed (LF)
763 "\r" => '\r', # carrige return (CR)
764 "\f" => '\f', # form feed (FF)
765 "\b" => '\b', # backspace (BS)
766 "\a" => '\a', # alarm (bell) (BEL)
767 "\e" => '\e', # escape (ESC)
768 "\013" => '\v', # vertical tab (VT)
769 "\000" => '\0', # nul character (NUL)
770 );
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100771 my $chr = ( (exists $es{$cntrl})
772 ? $es{$cntrl}
773 : sprintf('\%03o', ord($cntrl)) );
Jakub Narebskic84c4832008-02-17 18:48:13 +0100774 if ($opts{-nohtml}) {
775 return $chr;
776 } else {
777 return "<span class=\"cntrl\">$chr</span>";
778 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100779}
780
Jakub Narebski391862e2006-11-25 09:43:59 +0100781# Alternatively use unicode control pictures codepoints,
782# Unicode "printable representation" (PR)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100783sub quot_upr {
784 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +0100785 my %opts = @_;
786
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100787 my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
Jakub Narebskic84c4832008-02-17 18:48:13 +0100788 if ($opts{-nohtml}) {
789 return $chr;
790 } else {
791 return "<span class=\"cntrl\">$chr</span>";
792 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100793}
794
Kay Sievers232ff552005-11-24 16:56:55 +0100795# git may return quoted and escaped filenames
796sub unquote {
797 my $str = shift;
Jakub Narebski403d0902006-11-08 11:48:56 +0100798
799 sub unq {
800 my $seq = shift;
801 my %es = ( # character escape codes, aka escape sequences
802 't' => "\t", # tab (HT, TAB)
803 'n' => "\n", # newline (NL)
804 'r' => "\r", # return (CR)
805 'f' => "\f", # form feed (FF)
806 'b' => "\b", # backspace (BS)
807 'a' => "\a", # alarm (bell) (BEL)
808 'e' => "\e", # escape (ESC)
809 'v' => "\013", # vertical tab (VT)
810 );
811
812 if ($seq =~ m/^[0-7]{1,3}$/) {
813 # octal char sequence
814 return chr(oct($seq));
815 } elsif (exists $es{$seq}) {
816 # C escape sequence, aka character escape code
Jakub Narebskic84c4832008-02-17 18:48:13 +0100817 return $es{$seq};
Jakub Narebski403d0902006-11-08 11:48:56 +0100818 }
819 # quoted ordinary character
820 return $seq;
821 }
822
Kay Sievers232ff552005-11-24 16:56:55 +0100823 if ($str =~ m/^"(.*)"$/) {
Jakub Narebski403d0902006-11-08 11:48:56 +0100824 # needs unquoting
Kay Sievers232ff552005-11-24 16:56:55 +0100825 $str = $1;
Jakub Narebski403d0902006-11-08 11:48:56 +0100826 $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
Kay Sievers232ff552005-11-24 16:56:55 +0100827 }
828 return $str;
829}
830
Jakub Narebskif16db172006-08-06 02:08:31 +0200831# escape tabs (convert tabs to spaces)
832sub untabify {
833 my $line = shift;
834
835 while ((my $pos = index($line, "\t")) != -1) {
836 if (my $count = (8 - ($pos % 8))) {
837 my $spaces = ' ' x $count;
838 $line =~ s/\t/$spaces/;
839 }
840 }
841
842 return $line;
843}
844
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200845sub project_in_list {
846 my $project = shift;
847 my @list = git_get_projects_list();
848 return @list && scalar(grep { $_->{'path'} eq $project } @list);
849}
850
Jakub Narebski717b8312006-07-31 21:22:15 +0200851## ----------------------------------------------------------------------
852## HTML aware string manipulation
853
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100854# Try to chop given string on a word boundary between position
855# $len and $len+$add_len. If there is no word boundary there,
856# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
857# (marking chopped part) would be longer than given string.
Jakub Narebski717b8312006-07-31 21:22:15 +0200858sub chop_str {
859 my $str = shift;
860 my $len = shift;
861 my $add_len = shift || 10;
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100862 my $where = shift || 'right'; # 'left' | 'center' | 'right'
Jakub Narebski717b8312006-07-31 21:22:15 +0200863
864 # allow only $len chars, but don't cut a word if it would fit in $add_len
865 # if it doesn't fit, cut it if it's still longer than the dots we would add
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100866 # remove chopped character entities entirely
867
868 # when chopping in the middle, distribute $len into left and right part
869 # return early if chopping wouldn't make string shorter
870 if ($where eq 'center') {
871 return $str if ($len + 5 >= length($str)); # filler is length 5
872 $len = int($len/2);
873 } else {
874 return $str if ($len + 4 >= length($str)); # filler is length 4
Jakub Narebski717b8312006-07-31 21:22:15 +0200875 }
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100876
877 # regexps: ending and beginning with word part up to $add_len
878 my $endre = qr/.{$len}\w{0,$add_len}/;
879 my $begre = qr/\w{0,$add_len}.{$len}/;
880
881 if ($where eq 'left') {
882 $str =~ m/^(.*?)($begre)$/;
883 my ($lead, $body) = ($1, $2);
884 if (length($lead) > 4) {
885 $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
886 $lead = " ...";
887 }
888 return "$lead$body";
889
890 } elsif ($where eq 'center') {
891 $str =~ m/^($endre)(.*)$/;
892 my ($left, $str) = ($1, $2);
893 $str =~ m/^(.*?)($begre)$/;
894 my ($mid, $right) = ($1, $2);
895 if (length($mid) > 5) {
896 $left =~ s/&[^;]*$//;
897 $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
898 $mid = " ... ";
899 }
900 return "$left$mid$right";
901
902 } else {
903 $str =~ m/^($endre)(.*)$/;
904 my $body = $1;
905 my $tail = $2;
906 if (length($tail) > 4) {
907 $body =~ s/&[^;]*$//;
908 $tail = "... ";
909 }
910 return "$body$tail";
911 }
Jakub Narebski717b8312006-07-31 21:22:15 +0200912}
913
David Symondsce58ec92007-10-23 11:31:22 +1000914# takes the same arguments as chop_str, but also wraps a <span> around the
915# result with a title attribute if it does get chopped. Additionally, the
916# string is HTML-escaped.
917sub chop_and_escape_str {
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100918 my ($str) = @_;
David Symondsce58ec92007-10-23 11:31:22 +1000919
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100920 my $chopped = chop_str(@_);
David Symondsce58ec92007-10-23 11:31:22 +1000921 if ($chopped eq $str) {
922 return esc_html($chopped);
923 } else {
Jakub Narebski850b90a2008-02-16 23:07:46 +0100924 $str =~ s/([[:cntrl:]])/?/g;
925 return $cgi->span({-title=>$str}, esc_html($chopped));
David Symondsce58ec92007-10-23 11:31:22 +1000926 }
927}
928
Jakub Narebski717b8312006-07-31 21:22:15 +0200929## ----------------------------------------------------------------------
930## functions returning short strings
931
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +0000932# CSS class for given age value (in seconds)
933sub age_class {
934 my $age = shift;
935
Jakub Narebski785cdea2007-05-13 12:39:22 +0200936 if (!defined $age) {
937 return "noage";
938 } elsif ($age < 60*60*2) {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +0000939 return "age0";
940 } elsif ($age < 60*60*24*2) {
941 return "age1";
942 } else {
943 return "age2";
944 }
945}
946
Jakub Narebski717b8312006-07-31 21:22:15 +0200947# convert age in seconds to "nn units ago" string
948sub age_string {
949 my $age = shift;
950 my $age_str;
951
952 if ($age > 60*60*24*365*2) {
953 $age_str = (int $age/60/60/24/365);
954 $age_str .= " years ago";
955 } elsif ($age > 60*60*24*(365/12)*2) {
956 $age_str = int $age/60/60/24/(365/12);
957 $age_str .= " months ago";
958 } elsif ($age > 60*60*24*7*2) {
959 $age_str = int $age/60/60/24/7;
960 $age_str .= " weeks ago";
961 } elsif ($age > 60*60*24*2) {
962 $age_str = int $age/60/60/24;
963 $age_str .= " days ago";
964 } elsif ($age > 60*60*2) {
965 $age_str = int $age/60/60;
966 $age_str .= " hours ago";
967 } elsif ($age > 60*2) {
968 $age_str = int $age/60;
969 $age_str .= " min ago";
970 } elsif ($age > 2) {
971 $age_str = int $age;
972 $age_str .= " sec ago";
973 } else {
974 $age_str .= " right now";
975 }
976 return $age_str;
977}
978
Jakub Narebski01ac1e32007-07-28 16:27:31 +0200979use constant {
980 S_IFINVALID => 0030000,
981 S_IFGITLINK => 0160000,
982};
983
984# submodule/subproject, a commit object reference
985sub S_ISGITLINK($) {
986 my $mode = shift;
987
988 return (($mode & S_IFMT) == S_IFGITLINK)
989}
990
Jakub Narebski717b8312006-07-31 21:22:15 +0200991# convert file mode in octal to symbolic file mode string
992sub mode_str {
993 my $mode = oct shift;
994
Jakub Narebski01ac1e32007-07-28 16:27:31 +0200995 if (S_ISGITLINK($mode)) {
996 return 'm---------';
997 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +0200998 return 'drwxr-xr-x';
999 } elsif (S_ISLNK($mode)) {
1000 return 'lrwxrwxrwx';
1001 } elsif (S_ISREG($mode)) {
1002 # git cares only about the executable bit
1003 if ($mode & S_IXUSR) {
1004 return '-rwxr-xr-x';
1005 } else {
1006 return '-rw-r--r--';
1007 };
1008 } else {
1009 return '----------';
1010 }
1011}
1012
1013# convert file mode in octal to file type string
1014sub file_type {
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02001015 my $mode = shift;
1016
1017 if ($mode !~ m/^[0-7]+$/) {
1018 return $mode;
1019 } else {
1020 $mode = oct $mode;
1021 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001022
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001023 if (S_ISGITLINK($mode)) {
1024 return "submodule";
1025 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001026 return "directory";
1027 } elsif (S_ISLNK($mode)) {
1028 return "symlink";
1029 } elsif (S_ISREG($mode)) {
1030 return "file";
1031 } else {
1032 return "unknown";
1033 }
1034}
1035
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001036# convert file mode in octal to file type description string
1037sub file_type_long {
1038 my $mode = shift;
1039
1040 if ($mode !~ m/^[0-7]+$/) {
1041 return $mode;
1042 } else {
1043 $mode = oct $mode;
1044 }
1045
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001046 if (S_ISGITLINK($mode)) {
1047 return "submodule";
1048 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001049 return "directory";
1050 } elsif (S_ISLNK($mode)) {
1051 return "symlink";
1052 } elsif (S_ISREG($mode)) {
1053 if ($mode & S_IXUSR) {
1054 return "executable";
1055 } else {
1056 return "file";
1057 };
1058 } else {
1059 return "unknown";
1060 }
1061}
1062
1063
Jakub Narebski717b8312006-07-31 21:22:15 +02001064## ----------------------------------------------------------------------
1065## functions returning short HTML fragments, or transforming HTML fragments
Pavel Roskin3dff5372007-02-03 23:49:16 -05001066## which don't belong to other sections
Jakub Narebski717b8312006-07-31 21:22:15 +02001067
Junio C Hamano225932e2006-11-09 00:57:13 -08001068# format line of commit message.
Jakub Narebski717b8312006-07-31 21:22:15 +02001069sub format_log_line_html {
1070 my $line = shift;
1071
Junio C Hamano225932e2006-11-09 00:57:13 -08001072 $line = esc_html($line, -nbsp=>1);
Jakub Narebskibfe21912006-12-10 13:25:49 +01001073 if ($line =~ m/([0-9a-fA-F]{8,40})/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001074 my $hash_text = $1;
Jakub Narebskibfe21912006-12-10 13:25:49 +01001075 my $link =
1076 $cgi->a({-href => href(action=>"object", hash=>$hash_text),
1077 -class => "text"}, $hash_text);
1078 $line =~ s/$hash_text/$link/;
Jakub Narebski717b8312006-07-31 21:22:15 +02001079 }
1080 return $line;
1081}
1082
1083# format marker of refs pointing to given object
Jakub Narebski847e01f2006-08-14 02:05:47 +02001084sub format_ref_marker {
Jakub Narebski717b8312006-07-31 21:22:15 +02001085 my ($refs, $id) = @_;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001086 my $markers = '';
Jakub Narebski717b8312006-07-31 21:22:15 +02001087
1088 if (defined $refs->{$id}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001089 foreach my $ref (@{$refs->{$id}}) {
1090 my ($type, $name) = qw();
1091 # e.g. tags/v2.6.11 or heads/next
1092 if ($ref =~ m!^(.*?)s?/(.*)$!) {
1093 $type = $1;
1094 $name = $2;
1095 } else {
1096 $type = "ref";
1097 $name = $ref;
1098 }
1099
Jakub Narebski5fce2782006-12-15 23:49:12 +01001100 $markers .= " <span class=\"$type\" title=\"$ref\">" .
1101 esc_html($name) . "</span>";
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001102 }
1103 }
1104
1105 if ($markers) {
1106 return ' <span class="refs">'. $markers . '</span>';
Jakub Narebski717b8312006-07-31 21:22:15 +02001107 } else {
1108 return "";
1109 }
1110}
1111
Jakub Narebski17d07442006-08-14 02:08:27 +02001112# format, perhaps shortened and with markers, title line
1113sub format_subject_html {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02001114 my ($long, $short, $href, $extra) = @_;
Jakub Narebski17d07442006-08-14 02:08:27 +02001115 $extra = '' unless defined($extra);
1116
1117 if (length($short) < length($long)) {
Jakub Narebski7c278012006-08-22 12:02:48 +02001118 return $cgi->a({-href => $href, -class => "list subject",
Martin Koegler00f429a2007-06-03 17:42:44 +02001119 -title => to_utf8($long)},
Jakub Narebski17d07442006-08-14 02:08:27 +02001120 esc_html($short) . $extra);
1121 } else {
Jakub Narebski7c278012006-08-22 12:02:48 +02001122 return $cgi->a({-href => $href, -class => "list subject"},
Jakub Narebski17d07442006-08-14 02:08:27 +02001123 esc_html($long) . $extra);
1124 }
1125}
1126
Jakub Narebski90921742007-06-08 13:27:42 +02001127# format git diff header line, i.e. "diff --(git|combined|cc) ..."
1128sub format_git_diff_header_line {
1129 my $line = shift;
1130 my $diffinfo = shift;
1131 my ($from, $to) = @_;
1132
1133 if ($diffinfo->{'nparents'}) {
1134 # combined diff
1135 $line =~ s!^(diff (.*?) )"?.*$!$1!;
1136 if ($to->{'href'}) {
1137 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1138 esc_path($to->{'file'}));
1139 } else { # file was deleted (no href)
1140 $line .= esc_path($to->{'file'});
1141 }
1142 } else {
1143 # "ordinary" diff
1144 $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
1145 if ($from->{'href'}) {
1146 $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
1147 'a/' . esc_path($from->{'file'}));
1148 } else { # file was added (no href)
1149 $line .= 'a/' . esc_path($from->{'file'});
1150 }
1151 $line .= ' ';
1152 if ($to->{'href'}) {
1153 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1154 'b/' . esc_path($to->{'file'}));
1155 } else { # file was deleted
1156 $line .= 'b/' . esc_path($to->{'file'});
1157 }
1158 }
1159
1160 return "<div class=\"diff header\">$line</div>\n";
1161}
1162
1163# format extended diff header line, before patch itself
1164sub format_extended_diff_header_line {
1165 my $line = shift;
1166 my $diffinfo = shift;
1167 my ($from, $to) = @_;
1168
1169 # match <path>
1170 if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
1171 $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1172 esc_path($from->{'file'}));
1173 }
1174 if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
1175 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1176 esc_path($to->{'file'}));
1177 }
1178 # match single <mode>
1179 if ($line =~ m/\s(\d{6})$/) {
1180 $line .= '<span class="info"> (' .
1181 file_type_long($1) .
1182 ')</span>';
1183 }
1184 # match <hash>
1185 if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
1186 # can match only for combined diff
1187 $line = 'index ';
1188 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1189 if ($from->{'href'}[$i]) {
1190 $line .= $cgi->a({-href=>$from->{'href'}[$i],
1191 -class=>"hash"},
1192 substr($diffinfo->{'from_id'}[$i],0,7));
1193 } else {
1194 $line .= '0' x 7;
1195 }
1196 # separator
1197 $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
1198 }
1199 $line .= '..';
1200 if ($to->{'href'}) {
1201 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1202 substr($diffinfo->{'to_id'},0,7));
1203 } else {
1204 $line .= '0' x 7;
1205 }
1206
1207 } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
1208 # can match only for ordinary diff
1209 my ($from_link, $to_link);
1210 if ($from->{'href'}) {
1211 $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
1212 substr($diffinfo->{'from_id'},0,7));
1213 } else {
1214 $from_link = '0' x 7;
1215 }
1216 if ($to->{'href'}) {
1217 $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1218 substr($diffinfo->{'to_id'},0,7));
1219 } else {
1220 $to_link = '0' x 7;
1221 }
1222 my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
1223 $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
1224 }
1225
1226 return $line . "<br/>\n";
1227}
1228
1229# format from-file/to-file diff header
1230sub format_diff_from_to_header {
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001231 my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
Jakub Narebski90921742007-06-08 13:27:42 +02001232 my $line;
1233 my $result = '';
1234
1235 $line = $from_line;
1236 #assert($line =~ m/^---/) if DEBUG;
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001237 # no extra formatting for "^--- /dev/null"
1238 if (! $diffinfo->{'nparents'}) {
1239 # ordinary (single parent) diff
1240 if ($line =~ m!^--- "?a/!) {
1241 if ($from->{'href'}) {
1242 $line = '--- a/' .
1243 $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1244 esc_path($from->{'file'}));
1245 } else {
1246 $line = '--- a/' .
1247 esc_path($from->{'file'});
1248 }
1249 }
1250 $result .= qq!<div class="diff from_file">$line</div>\n!;
1251
1252 } else {
1253 # combined diff (merge commit)
1254 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1255 if ($from->{'href'}[$i]) {
1256 $line = '--- ' .
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001257 $cgi->a({-href=>href(action=>"blobdiff",
1258 hash_parent=>$diffinfo->{'from_id'}[$i],
1259 hash_parent_base=>$parents[$i],
1260 file_parent=>$from->{'file'}[$i],
1261 hash=>$diffinfo->{'to_id'},
1262 hash_base=>$hash,
1263 file_name=>$to->{'file'}),
1264 -class=>"path",
1265 -title=>"diff" . ($i+1)},
1266 $i+1) .
1267 '/' .
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001268 $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
1269 esc_path($from->{'file'}[$i]));
1270 } else {
1271 $line = '--- /dev/null';
1272 }
1273 $result .= qq!<div class="diff from_file">$line</div>\n!;
Jakub Narebski90921742007-06-08 13:27:42 +02001274 }
1275 }
Jakub Narebski90921742007-06-08 13:27:42 +02001276
1277 $line = $to_line;
1278 #assert($line =~ m/^\+\+\+/) if DEBUG;
1279 # no extra formatting for "^+++ /dev/null"
1280 if ($line =~ m!^\+\+\+ "?b/!) {
1281 if ($to->{'href'}) {
1282 $line = '+++ b/' .
1283 $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1284 esc_path($to->{'file'}));
1285 } else {
1286 $line = '+++ b/' .
1287 esc_path($to->{'file'});
1288 }
1289 }
1290 $result .= qq!<div class="diff to_file">$line</div>\n!;
1291
1292 return $result;
1293}
1294
Jakub Narebskicd030c32007-06-08 13:33:28 +02001295# create note for patch simplified by combined diff
1296sub format_diff_cc_simplified {
1297 my ($diffinfo, @parents) = @_;
1298 my $result = '';
1299
1300 $result .= "<div class=\"diff header\">" .
1301 "diff --cc ";
1302 if (!is_deleted($diffinfo)) {
1303 $result .= $cgi->a({-href => href(action=>"blob",
1304 hash_base=>$hash,
1305 hash=>$diffinfo->{'to_id'},
1306 file_name=>$diffinfo->{'to_file'}),
1307 -class => "path"},
1308 esc_path($diffinfo->{'to_file'}));
1309 } else {
1310 $result .= esc_path($diffinfo->{'to_file'});
1311 }
1312 $result .= "</div>\n" . # class="diff header"
1313 "<div class=\"diff nodifferences\">" .
1314 "Simple merge" .
1315 "</div>\n"; # class="diff nodifferences"
1316
1317 return $result;
1318}
1319
Jakub Narebski90921742007-06-08 13:27:42 +02001320# format patch (diff) line (not to be used for diff headers)
Jakub Narebskieee08902006-08-24 00:15:14 +02001321sub format_diff_line {
1322 my $line = shift;
Jakub Narebski59e3b142006-11-18 23:35:40 +01001323 my ($from, $to) = @_;
Jakub Narebskieee08902006-08-24 00:15:14 +02001324 my $diff_class = "";
1325
1326 chomp $line;
1327
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001328 if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
1329 # combined diff
1330 my $prefix = substr($line, 0, scalar @{$from->{'href'}});
1331 if ($line =~ m/^\@{3}/) {
1332 $diff_class = " chunk_header";
1333 } elsif ($line =~ m/^\\/) {
1334 $diff_class = " incomplete";
1335 } elsif ($prefix =~ tr/+/+/) {
1336 $diff_class = " add";
1337 } elsif ($prefix =~ tr/-/-/) {
1338 $diff_class = " rem";
1339 }
1340 } else {
1341 # assume ordinary diff
1342 my $char = substr($line, 0, 1);
1343 if ($char eq '+') {
1344 $diff_class = " add";
1345 } elsif ($char eq '-') {
1346 $diff_class = " rem";
1347 } elsif ($char eq '@') {
1348 $diff_class = " chunk_header";
1349 } elsif ($char eq "\\") {
1350 $diff_class = " incomplete";
1351 }
Jakub Narebskieee08902006-08-24 00:15:14 +02001352 }
1353 $line = untabify($line);
Jakub Narebski59e3b142006-11-18 23:35:40 +01001354 if ($from && $to && $line =~ m/^\@{2} /) {
1355 my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
1356 $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
1357
1358 $from_lines = 0 unless defined $from_lines;
1359 $to_lines = 0 unless defined $to_lines;
1360
1361 if ($from->{'href'}) {
1362 $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
1363 -class=>"list"}, $from_text);
1364 }
1365 if ($to->{'href'}) {
1366 $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
1367 -class=>"list"}, $to_text);
1368 }
1369 $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
1370 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1371 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001372 } elsif ($from && $to && $line =~ m/^\@{3}/) {
1373 my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
1374 my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
1375
1376 @from_text = split(' ', $ranges);
1377 for (my $i = 0; $i < @from_text; ++$i) {
1378 ($from_start[$i], $from_nlines[$i]) =
1379 (split(',', substr($from_text[$i], 1)), 0);
1380 }
1381
1382 $to_text = pop @from_text;
1383 $to_start = pop @from_start;
1384 $to_nlines = pop @from_nlines;
1385
1386 $line = "<span class=\"chunk_info\">$prefix ";
1387 for (my $i = 0; $i < @from_text; ++$i) {
1388 if ($from->{'href'}[$i]) {
1389 $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
1390 -class=>"list"}, $from_text[$i]);
1391 } else {
1392 $line .= $from_text[$i];
1393 }
1394 $line .= " ";
1395 }
1396 if ($to->{'href'}) {
1397 $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
1398 -class=>"list"}, $to_text);
1399 } else {
1400 $line .= $to_text;
1401 }
1402 $line .= " $prefix</span>" .
1403 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1404 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebski59e3b142006-11-18 23:35:40 +01001405 }
Jakub Narebski6255ef02006-11-01 14:33:21 +01001406 return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02001407}
1408
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001409# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
1410# linked. Pass the hash of the tree/commit to snapshot.
1411sub format_snapshot_links {
1412 my ($hash) = @_;
1413 my @snapshot_fmts = gitweb_check_feature('snapshot');
Jakub Narebskia7817852007-07-22 23:41:20 +02001414 @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001415 my $num_fmts = @snapshot_fmts;
1416 if ($num_fmts > 1) {
1417 # A parenthesized list of links bearing format names.
Jakub Narebskia7817852007-07-22 23:41:20 +02001418 # e.g. "snapshot (_tar.gz_ _zip_)"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001419 return "snapshot (" . join(' ', map
1420 $cgi->a({
1421 -href => href(
1422 action=>"snapshot",
1423 hash=>$hash,
1424 snapshot_format=>$_
1425 )
1426 }, $known_snapshot_formats{$_}{'display'})
1427 , @snapshot_fmts) . ")";
1428 } elsif ($num_fmts == 1) {
1429 # A single "snapshot" link whose tooltip bears the format name.
Jakub Narebskia7817852007-07-22 23:41:20 +02001430 # i.e. "_snapshot_"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001431 my ($fmt) = @snapshot_fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +02001432 return
1433 $cgi->a({
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001434 -href => href(
1435 action=>"snapshot",
1436 hash=>$hash,
1437 snapshot_format=>$fmt
1438 ),
1439 -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
1440 }, "snapshot");
1441 } else { # $num_fmts == 0
1442 return undef;
1443 }
1444}
1445
Jakub Narebski717b8312006-07-31 21:22:15 +02001446## ----------------------------------------------------------------------
1447## git utility subroutines, invoking git commands
1448
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001449# returns path to the core git executable and the --git-dir parameter as list
1450sub git_cmd {
1451 return $GIT, '--git-dir='.$git_dir;
1452}
1453
1454# returns path to the core git executable and the --git-dir parameter as string
1455sub git_cmd_str {
1456 return join(' ', git_cmd());
1457}
1458
Jakub Narebski717b8312006-07-31 21:22:15 +02001459# get HEAD ref of given project as hash
Jakub Narebski847e01f2006-08-14 02:05:47 +02001460sub git_get_head_hash {
Jakub Narebski717b8312006-07-31 21:22:15 +02001461 my $project = shift;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001462 my $o_git_dir = $git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02001463 my $retval = undef;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001464 $git_dir = "$projectroot/$project";
1465 if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
Jakub Narebski717b8312006-07-31 21:22:15 +02001466 my $head = <$fd>;
1467 close $fd;
1468 if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
1469 $retval = $1;
1470 }
1471 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001472 if (defined $o_git_dir) {
1473 $git_dir = $o_git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02001474 }
1475 return $retval;
1476}
1477
1478# get type of given object
1479sub git_get_type {
1480 my $hash = shift;
1481
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001482 open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02001483 my $type = <$fd>;
1484 close $fd or return;
1485 chomp $type;
1486 return $type;
1487}
1488
Jakub Narebskib2019272007-11-03 00:41:19 +01001489# repository configuration
1490our $config_file = '';
1491our %config;
1492
1493# store multiple values for single key as anonymous array reference
1494# single values stored directly in the hash, not as [ <value> ]
1495sub hash_set_multi {
1496 my ($hash, $key, $value) = @_;
1497
1498 if (!exists $hash->{$key}) {
1499 $hash->{$key} = $value;
1500 } elsif (!ref $hash->{$key}) {
1501 $hash->{$key} = [ $hash->{$key}, $value ];
1502 } else {
1503 push @{$hash->{$key}}, $value;
1504 }
1505}
1506
1507# return hash of git project configuration
1508# optionally limited to some section, e.g. 'gitweb'
1509sub git_parse_project_config {
1510 my $section_regexp = shift;
1511 my %config;
1512
1513 local $/ = "\0";
1514
1515 open my $fh, "-|", git_cmd(), "config", '-z', '-l',
1516 or return;
1517
1518 while (my $keyval = <$fh>) {
1519 chomp $keyval;
1520 my ($key, $value) = split(/\n/, $keyval, 2);
1521
1522 hash_set_multi(\%config, $key, $value)
1523 if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
1524 }
1525 close $fh;
1526
1527 return %config;
1528}
1529
1530# convert config value to boolean, 'true' or 'false'
1531# no value, number > 0, 'true' and 'yes' values are true
1532# rest of values are treated as false (never as error)
1533sub config_to_bool {
1534 my $val = shift;
1535
1536 # strip leading and trailing whitespace
1537 $val =~ s/^\s+//;
1538 $val =~ s/\s+$//;
1539
1540 return (!defined $val || # section.key
1541 ($val =~ /^\d+$/ && $val) || # section.key = 1
1542 ($val =~ /^(?:true|yes)$/i)); # section.key = true
1543}
1544
1545# convert config value to simple decimal number
1546# an optional value suffix of 'k', 'm', or 'g' will cause the value
1547# to be multiplied by 1024, 1048576, or 1073741824
1548sub config_to_int {
1549 my $val = shift;
1550
1551 # strip leading and trailing whitespace
1552 $val =~ s/^\s+//;
1553 $val =~ s/\s+$//;
1554
1555 if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
1556 $unit = lc($unit);
1557 # unknown unit is treated as 1
1558 return $num * ($unit eq 'g' ? 1073741824 :
1559 $unit eq 'm' ? 1048576 :
1560 $unit eq 'k' ? 1024 : 1);
1561 }
1562 return $val;
1563}
1564
1565# convert config value to array reference, if needed
1566sub config_to_multi {
1567 my $val = shift;
1568
Jakub Narebskid76a5852007-12-20 10:48:09 +01001569 return ref($val) ? $val : (defined($val) ? [ $val ] : []);
Jakub Narebskib2019272007-11-03 00:41:19 +01001570}
1571
Jakub Narebski717b8312006-07-31 21:22:15 +02001572sub git_get_project_config {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05301573 my ($key, $type) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02001574
Jakub Narebskib2019272007-11-03 00:41:19 +01001575 # key sanity check
Jakub Narebski717b8312006-07-31 21:22:15 +02001576 return unless ($key);
1577 $key =~ s/^gitweb\.//;
1578 return if ($key =~ m/\W/);
1579
Jakub Narebskib2019272007-11-03 00:41:19 +01001580 # type sanity check
1581 if (defined $type) {
1582 $type =~ s/^--//;
1583 $type = undef
1584 unless ($type eq 'bool' || $type eq 'int');
1585 }
1586
1587 # get config
1588 if (!defined $config_file ||
1589 $config_file ne "$git_dir/config") {
1590 %config = git_parse_project_config('gitweb');
1591 $config_file = "$git_dir/config";
1592 }
1593
1594 # ensure given type
1595 if (!defined $type) {
1596 return $config{"gitweb.$key"};
1597 } elsif ($type eq 'bool') {
1598 # backward compatibility: 'git config --bool' returns true/false
1599 return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
1600 } elsif ($type eq 'int') {
1601 return config_to_int($config{"gitweb.$key"});
1602 }
1603 return $config{"gitweb.$key"};
Jakub Narebski717b8312006-07-31 21:22:15 +02001604}
1605
Jakub Narebski717b8312006-07-31 21:22:15 +02001606# get hash of given path at given ref
1607sub git_get_hash_by_path {
1608 my $base = shift;
1609 my $path = shift || return undef;
Jakub Narebski1d782b02006-09-21 18:09:12 +02001610 my $type = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02001611
Jakub Narebski4b02f482006-09-26 01:54:24 +02001612 $path =~ s,/+$,,;
Jakub Narebski717b8312006-07-31 21:22:15 +02001613
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001614 open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
Jakub Narebskicac4bd92006-08-05 13:13:53 +02001615 or die_error(undef, "Open git-ls-tree failed");
Jakub Narebski717b8312006-07-31 21:22:15 +02001616 my $line = <$fd>;
1617 close $fd or return undef;
1618
Jakub Narebski198a2a82007-05-12 21:16:34 +02001619 if (!defined $line) {
1620 # there is no tree or hash given by $path at $base
1621 return undef;
1622 }
1623
Jakub Narebski717b8312006-07-31 21:22:15 +02001624 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01001625 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
Jakub Narebski1d782b02006-09-21 18:09:12 +02001626 if (defined $type && $type ne $2) {
1627 # type doesn't match
1628 return undef;
1629 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001630 return $3;
1631}
1632
Jakub Narebskied224de2007-05-07 01:10:04 +02001633# get path of entry with given hash at given tree-ish (ref)
1634# used to get 'from' filename for combined diff (merge commit) for renames
1635sub git_get_path_by_hash {
1636 my $base = shift || return;
1637 my $hash = shift || return;
1638
1639 local $/ = "\0";
1640
1641 open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
1642 or return undef;
1643 while (my $line = <$fd>) {
1644 chomp $line;
1645
1646 #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
1647 #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
1648 if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
1649 close $fd;
1650 return $1;
1651 }
1652 }
1653 close $fd;
1654 return undef;
1655}
1656
Jakub Narebski717b8312006-07-31 21:22:15 +02001657## ......................................................................
1658## git utility functions, directly accessing git repository
1659
Jakub Narebski847e01f2006-08-14 02:05:47 +02001660sub git_get_project_description {
Jakub Narebski717b8312006-07-31 21:22:15 +02001661 my $path = shift;
1662
Jakub Narebski0e121a22007-11-03 00:41:20 +01001663 $git_dir = "$projectroot/$path";
Bruno Ribasc1dcf7e2008-01-30 03:37:56 -02001664 open my $fd, "$git_dir/description"
Jakub Narebski0e121a22007-11-03 00:41:20 +01001665 or return git_get_project_config('description');
Jakub Narebski717b8312006-07-31 21:22:15 +02001666 my $descr = <$fd>;
1667 close $fd;
Junio C Hamano2eb54ef2007-05-16 21:04:16 -07001668 if (defined $descr) {
1669 chomp $descr;
1670 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001671 return $descr;
1672}
1673
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02001674sub git_get_project_url_list {
1675 my $path = shift;
1676
Jakub Narebski0e121a22007-11-03 00:41:20 +01001677 $git_dir = "$projectroot/$path";
Bruno Ribas201945e2008-02-06 15:15:12 -02001678 open my $fd, "$git_dir/cloneurl"
Jakub Narebski0e121a22007-11-03 00:41:20 +01001679 or return wantarray ?
1680 @{ config_to_multi(git_get_project_config('url')) } :
1681 config_to_multi(git_get_project_config('url'));
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02001682 my @git_project_url_list = map { chomp; $_ } <$fd>;
1683 close $fd;
1684
1685 return wantarray ? @git_project_url_list : \@git_project_url_list;
1686}
1687
Jakub Narebski847e01f2006-08-14 02:05:47 +02001688sub git_get_projects_list {
Petr Baudise30496d2006-10-24 05:33:17 +02001689 my ($filter) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02001690 my @list;
1691
Petr Baudise30496d2006-10-24 05:33:17 +02001692 $filter ||= '';
1693 $filter =~ s/\.git$//;
1694
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001695 my ($check_forks) = gitweb_check_feature('forks');
1696
Jakub Narebski717b8312006-07-31 21:22:15 +02001697 if (-d $projects_list) {
1698 # search in directory
Petr Baudise30496d2006-10-24 05:33:17 +02001699 my $dir = $projects_list . ($filter ? "/$filter" : '');
Aneesh Kumar K.V6768d6b2006-11-03 10:41:45 +05301700 # remove the trailing "/"
1701 $dir =~ s!/+$!!;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001702 my $pfxlen = length("$dir");
Luke Luca5e9492007-10-16 20:45:25 -07001703 my $pfxdepth = ($dir =~ tr!/!!);
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001704
1705 File::Find::find({
1706 follow_fast => 1, # follow symbolic links
Junio C Hamanod20602e2007-07-27 01:23:03 -07001707 follow_skip => 2, # ignore duplicates
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001708 dangling_symlinks => 0, # ignore dangling symlinks, silently
1709 wanted => sub {
1710 # skip project-list toplevel, if we get it.
1711 return if (m!^[/.]$!);
1712 # only directories can be git repositories
1713 return unless (-d $_);
Luke Luca5e9492007-10-16 20:45:25 -07001714 # don't traverse too deep (Find is super slow on os x)
1715 if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
1716 $File::Find::prune = 1;
1717 return;
1718 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001719
1720 my $subdir = substr($File::Find::name, $pfxlen + 1);
1721 # we check related file in $projectroot
Petr Baudise30496d2006-10-24 05:33:17 +02001722 if ($check_forks and $subdir =~ m#/.#) {
1723 $File::Find::prune = 1;
1724 } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
1725 push @list, { path => ($filter ? "$filter/" : '') . $subdir };
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001726 $File::Find::prune = 1;
1727 }
1728 },
1729 }, "$dir");
1730
Jakub Narebski717b8312006-07-31 21:22:15 +02001731 } elsif (-f $projects_list) {
1732 # read from file(url-encoded):
1733 # 'git%2Fgit.git Linus+Torvalds'
1734 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
1735 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001736 my %paths;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +02001737 open my ($fd), $projects_list or return;
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001738 PROJECT:
Jakub Narebski717b8312006-07-31 21:22:15 +02001739 while (my $line = <$fd>) {
1740 chomp $line;
1741 my ($path, $owner) = split ' ', $line;
1742 $path = unescape($path);
1743 $owner = unescape($owner);
1744 if (!defined $path) {
1745 next;
1746 }
Junio C Hamano83ee94c2006-11-07 22:37:17 -08001747 if ($filter ne '') {
1748 # looking for forks;
1749 my $pfx = substr($path, 0, length($filter));
1750 if ($pfx ne $filter) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001751 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08001752 }
1753 my $sfx = substr($path, length($filter));
1754 if ($sfx !~ /^\/.*\.git$/) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001755 next PROJECT;
1756 }
1757 } elsif ($check_forks) {
1758 PATH:
1759 foreach my $filter (keys %paths) {
1760 # looking for forks;
1761 my $pfx = substr($path, 0, length($filter));
1762 if ($pfx ne $filter) {
1763 next PATH;
1764 }
1765 my $sfx = substr($path, length($filter));
1766 if ($sfx !~ /^\/.*\.git$/) {
1767 next PATH;
1768 }
1769 # is a fork, don't include it in
1770 # the list
1771 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08001772 }
1773 }
Junio C Hamano2172ce42006-10-03 02:30:47 -07001774 if (check_export_ok("$projectroot/$path")) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001775 my $pr = {
1776 path => $path,
Martin Koegler00f429a2007-06-03 17:42:44 +02001777 owner => to_utf8($owner),
Jakub Narebski717b8312006-07-31 21:22:15 +02001778 };
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001779 push @list, $pr;
1780 (my $forks_path = $path) =~ s/\.git$//;
1781 $paths{$forks_path}++;
Jakub Narebski717b8312006-07-31 21:22:15 +02001782 }
1783 }
1784 close $fd;
1785 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001786 return @list;
1787}
1788
Junio C Hamano47852452007-07-03 22:10:42 -07001789our $gitweb_project_owner = undef;
1790sub git_get_project_list_from_file {
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001791
Junio C Hamano47852452007-07-03 22:10:42 -07001792 return if (defined $gitweb_project_owner);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001793
Junio C Hamano47852452007-07-03 22:10:42 -07001794 $gitweb_project_owner = {};
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001795 # read from file (url-encoded):
1796 # 'git%2Fgit.git Linus+Torvalds'
1797 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
1798 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
1799 if (-f $projects_list) {
1800 open (my $fd , $projects_list);
1801 while (my $line = <$fd>) {
1802 chomp $line;
1803 my ($pr, $ow) = split ' ', $line;
1804 $pr = unescape($pr);
1805 $ow = unescape($ow);
Junio C Hamano47852452007-07-03 22:10:42 -07001806 $gitweb_project_owner->{$pr} = to_utf8($ow);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001807 }
1808 close $fd;
1809 }
Junio C Hamano47852452007-07-03 22:10:42 -07001810}
1811
1812sub git_get_project_owner {
1813 my $project = shift;
1814 my $owner;
1815
1816 return undef unless $project;
Bruno Ribasb59012e2008-02-08 14:38:04 -02001817 $git_dir = "$projectroot/$project";
Junio C Hamano47852452007-07-03 22:10:42 -07001818
1819 if (!defined $gitweb_project_owner) {
1820 git_get_project_list_from_file();
1821 }
1822
1823 if (exists $gitweb_project_owner->{$project}) {
1824 $owner = $gitweb_project_owner->{$project};
1825 }
Bruno Ribasb59012e2008-02-08 14:38:04 -02001826 if (!defined $owner){
1827 $owner = git_get_project_config('owner');
1828 }
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001829 if (!defined $owner) {
Bruno Ribasb59012e2008-02-08 14:38:04 -02001830 $owner = get_file_owner("$git_dir");
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001831 }
1832
1833 return $owner;
1834}
1835
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001836sub git_get_last_activity {
1837 my ($path) = @_;
1838 my $fd;
1839
1840 $git_dir = "$projectroot/$path";
1841 open($fd, "-|", git_cmd(), 'for-each-ref',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00001842 '--format=%(committer)',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001843 '--sort=-committerdate',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00001844 '--count=1',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001845 'refs/heads') or return;
1846 my $most_recent = <$fd>;
1847 close $fd or return;
Jakub Narebski785cdea2007-05-13 12:39:22 +02001848 if (defined $most_recent &&
1849 $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001850 my $timestamp = $1;
1851 my $age = time - $timestamp;
1852 return ($age, age_string($age));
1853 }
Matt McCutchenc9563952007-06-28 18:15:22 -04001854 return (undef, undef);
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001855}
1856
Jakub Narebski847e01f2006-08-14 02:05:47 +02001857sub git_get_references {
Jakub Narebski717b8312006-07-31 21:22:15 +02001858 my $type = shift || "";
1859 my %refs;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01001860 # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
1861 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
1862 open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
1863 ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
Jakub Narebski9704d752006-09-19 14:31:49 +02001864 or return;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001865
Jakub Narebski717b8312006-07-31 21:22:15 +02001866 while (my $line = <$fd>) {
1867 chomp $line;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01001868 if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001869 if (defined $refs{$1}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001870 push @{$refs{$1}}, $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02001871 } else {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001872 $refs{$1} = [ $2 ];
Jakub Narebski717b8312006-07-31 21:22:15 +02001873 }
1874 }
1875 }
1876 close $fd or return;
1877 return \%refs;
1878}
1879
Jakub Narebski56a322f2006-08-24 19:41:23 +02001880sub git_get_rev_name_tags {
1881 my $hash = shift || return undef;
1882
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001883 open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
Jakub Narebski56a322f2006-08-24 19:41:23 +02001884 or return;
1885 my $name_rev = <$fd>;
1886 close $fd;
1887
1888 if ($name_rev =~ m|^$hash tags/(.*)$|) {
1889 return $1;
1890 } else {
1891 # catches also '$hash undefined' output
1892 return undef;
1893 }
1894}
1895
Jakub Narebski717b8312006-07-31 21:22:15 +02001896## ----------------------------------------------------------------------
1897## parse to hash functions
1898
Jakub Narebski847e01f2006-08-14 02:05:47 +02001899sub parse_date {
Jakub Narebski717b8312006-07-31 21:22:15 +02001900 my $epoch = shift;
1901 my $tz = shift || "-0000";
1902
1903 my %date;
1904 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
1905 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
1906 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
1907 $date{'hour'} = $hour;
1908 $date{'minute'} = $min;
1909 $date{'mday'} = $mday;
1910 $date{'day'} = $days[$wday];
1911 $date{'month'} = $months[$mon];
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01001912 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
1913 $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
Jakub Narebski952c65f2006-08-22 16:52:50 +02001914 $date{'mday-time'} = sprintf "%d %s %02d:%02d",
1915 $mday, $months[$mon], $hour ,$min;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01001916 $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
Vincent Zanottia62d6d82007-11-10 19:55:27 +01001917 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
Jakub Narebski717b8312006-07-31 21:22:15 +02001918
1919 $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
1920 my $local = $epoch + ((int $1 + ($2/60)) * 3600);
1921 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
1922 $date{'hour_local'} = $hour;
1923 $date{'minute_local'} = $min;
1924 $date{'tz_local'} = $tz;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01001925 $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
1926 1900+$year, $mon+1, $mday,
1927 $hour, $min, $sec, $tz);
Jakub Narebski717b8312006-07-31 21:22:15 +02001928 return %date;
1929}
1930
Jakub Narebski847e01f2006-08-14 02:05:47 +02001931sub parse_tag {
Jakub Narebski717b8312006-07-31 21:22:15 +02001932 my $tag_id = shift;
1933 my %tag;
1934 my @comment;
1935
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001936 open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02001937 $tag{'id'} = $tag_id;
1938 while (my $line = <$fd>) {
1939 chomp $line;
1940 if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
1941 $tag{'object'} = $1;
1942 } elsif ($line =~ m/^type (.+)$/) {
1943 $tag{'type'} = $1;
1944 } elsif ($line =~ m/^tag (.+)$/) {
1945 $tag{'name'} = $1;
1946 } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
1947 $tag{'author'} = $1;
1948 $tag{'epoch'} = $2;
1949 $tag{'tz'} = $3;
1950 } elsif ($line =~ m/--BEGIN/) {
1951 push @comment, $line;
1952 last;
1953 } elsif ($line eq "") {
1954 last;
1955 }
1956 }
1957 push @comment, <$fd>;
1958 $tag{'comment'} = \@comment;
1959 close $fd or return;
1960 if (!defined $tag{'name'}) {
1961 return
1962 };
1963 return %tag
1964}
1965
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00001966sub parse_commit_text {
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00001967 my ($commit_text, $withparents) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00001968 my @commit_lines = split '\n', $commit_text;
Jakub Narebski717b8312006-07-31 21:22:15 +02001969 my %co;
1970
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00001971 pop @commit_lines; # Remove '\0'
1972
Jakub Narebski198a2a82007-05-12 21:16:34 +02001973 if (! @commit_lines) {
1974 return;
1975 }
1976
Jakub Narebski717b8312006-07-31 21:22:15 +02001977 my $header = shift @commit_lines;
Jakub Narebski198a2a82007-05-12 21:16:34 +02001978 if ($header !~ m/^[0-9a-fA-F]{40}/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001979 return;
1980 }
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00001981 ($co{'id'}, my @parents) = split ' ', $header;
Jakub Narebski717b8312006-07-31 21:22:15 +02001982 while (my $line = shift @commit_lines) {
1983 last if $line eq "\n";
1984 if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
1985 $co{'tree'} = $1;
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00001986 } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00001987 push @parents, $1;
Jakub Narebski717b8312006-07-31 21:22:15 +02001988 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
1989 $co{'author'} = $1;
1990 $co{'author_epoch'} = $2;
1991 $co{'author_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01001992 if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
1993 $co{'author_name'} = $1;
1994 $co{'author_email'} = $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02001995 } else {
1996 $co{'author_name'} = $co{'author'};
1997 }
1998 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
1999 $co{'committer'} = $1;
2000 $co{'committer_epoch'} = $2;
2001 $co{'committer_tz'} = $3;
2002 $co{'committer_name'} = $co{'committer'};
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002003 if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
2004 $co{'committer_name'} = $1;
2005 $co{'committer_email'} = $2;
2006 } else {
2007 $co{'committer_name'} = $co{'committer'};
2008 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002009 }
2010 }
2011 if (!defined $co{'tree'}) {
2012 return;
2013 };
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002014 $co{'parents'} = \@parents;
2015 $co{'parent'} = $parents[0];
Jakub Narebski717b8312006-07-31 21:22:15 +02002016
2017 foreach my $title (@commit_lines) {
2018 $title =~ s/^ //;
2019 if ($title ne "") {
2020 $co{'title'} = chop_str($title, 80, 5);
2021 # remove leading stuff of merges to make the interesting part visible
2022 if (length($title) > 50) {
2023 $title =~ s/^Automatic //;
2024 $title =~ s/^merge (of|with) /Merge ... /i;
2025 if (length($title) > 50) {
2026 $title =~ s/(http|rsync):\/\///;
2027 }
2028 if (length($title) > 50) {
2029 $title =~ s/(master|www|rsync)\.//;
2030 }
2031 if (length($title) > 50) {
2032 $title =~ s/kernel.org:?//;
2033 }
2034 if (length($title) > 50) {
2035 $title =~ s/\/pub\/scm//;
2036 }
2037 }
2038 $co{'title_short'} = chop_str($title, 50, 5);
2039 last;
2040 }
2041 }
Petr Baudis7e0fe5c2006-10-06 18:55:04 +02002042 if ($co{'title'} eq "") {
2043 $co{'title'} = $co{'title_short'} = '(no commit message)';
2044 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002045 # remove added spaces
2046 foreach my $line (@commit_lines) {
2047 $line =~ s/^ //;
2048 }
2049 $co{'comment'} = \@commit_lines;
2050
2051 my $age = time - $co{'committer_epoch'};
2052 $co{'age'} = $age;
2053 $co{'age_string'} = age_string($age);
2054 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
2055 if ($age > 60*60*24*7*2) {
2056 $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2057 $co{'age_string_age'} = $co{'age_string'};
2058 } else {
2059 $co{'age_string_date'} = $co{'age_string'};
2060 $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2061 }
2062 return %co;
2063}
2064
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002065sub parse_commit {
2066 my ($commit_id) = @_;
2067 my %co;
2068
2069 local $/ = "\0";
2070
2071 open my $fd, "-|", git_cmd(), "rev-list",
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002072 "--parents",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002073 "--header",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002074 "--max-count=1",
2075 $commit_id,
2076 "--",
2077 or die_error(undef, "Open git-rev-list failed");
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002078 %co = parse_commit_text(<$fd>, 1);
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002079 close $fd;
2080
2081 return %co;
2082}
2083
2084sub parse_commits {
Jakub Narebski311e5522008-02-26 13:22:06 +01002085 my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002086 my @cos;
2087
2088 $maxcount ||= 1;
2089 $skip ||= 0;
2090
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002091 local $/ = "\0";
2092
2093 open my $fd, "-|", git_cmd(), "rev-list",
2094 "--header",
Jakub Narebski311e5522008-02-26 13:22:06 +01002095 @args,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002096 ("--max-count=" . $maxcount),
Robert Fitzsimonsf47efbb2006-12-24 14:31:49 +00002097 ("--skip=" . $skip),
Miklos Vajna868bc062007-07-12 20:39:27 +02002098 @extra_options,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002099 $commit_id,
2100 "--",
2101 ($filename ? ($filename) : ())
2102 or die_error(undef, "Open git-rev-list failed");
2103 while (my $line = <$fd>) {
2104 my %co = parse_commit_text($line);
2105 push @cos, \%co;
2106 }
2107 close $fd;
2108
2109 return wantarray ? @cos : \@cos;
2110}
2111
Jakub Narebskia446d6b2006-08-14 02:07:00 +02002112# parse ref from ref_file, given by ref_id, with given type
2113sub parse_ref {
2114 my $ref_file = shift;
2115 my $ref_id = shift;
2116 my $type = shift || git_get_type($ref_id);
2117 my %ref_item;
2118
2119 $ref_item{'type'} = $type;
2120 $ref_item{'id'} = $ref_id;
2121 $ref_item{'epoch'} = 0;
2122 $ref_item{'age'} = "unknown";
2123 if ($type eq "tag") {
2124 my %tag = parse_tag($ref_id);
2125 $ref_item{'comment'} = $tag{'comment'};
2126 if ($tag{'type'} eq "commit") {
2127 my %co = parse_commit($tag{'object'});
2128 $ref_item{'epoch'} = $co{'committer_epoch'};
2129 $ref_item{'age'} = $co{'age_string'};
2130 } elsif (defined($tag{'epoch'})) {
2131 my $age = time - $tag{'epoch'};
2132 $ref_item{'epoch'} = $tag{'epoch'};
2133 $ref_item{'age'} = age_string($age);
2134 }
2135 $ref_item{'reftype'} = $tag{'type'};
2136 $ref_item{'name'} = $tag{'name'};
2137 $ref_item{'refid'} = $tag{'object'};
2138 } elsif ($type eq "commit"){
2139 my %co = parse_commit($ref_id);
2140 $ref_item{'reftype'} = "commit";
2141 $ref_item{'name'} = $ref_file;
2142 $ref_item{'title'} = $co{'title'};
2143 $ref_item{'refid'} = $ref_id;
2144 $ref_item{'epoch'} = $co{'committer_epoch'};
2145 $ref_item{'age'} = $co{'age_string'};
2146 } else {
2147 $ref_item{'reftype'} = $type;
2148 $ref_item{'name'} = $ref_file;
2149 $ref_item{'refid'} = $ref_id;
2150 }
2151
2152 return %ref_item;
2153}
2154
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002155# parse line of git-diff-tree "raw" output
Jakub Narebski740e67f2006-08-21 23:07:00 +02002156sub parse_difftree_raw_line {
2157 my $line = shift;
2158 my %res;
2159
2160 # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
2161 # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
2162 if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
2163 $res{'from_mode'} = $1;
2164 $res{'to_mode'} = $2;
2165 $res{'from_id'} = $3;
2166 $res{'to_id'} = $4;
Jakub Narebski6aa6f922007-11-01 12:38:09 +01002167 $res{'status'} = $res{'status_str'} = $5;
Jakub Narebski740e67f2006-08-21 23:07:00 +02002168 $res{'similarity'} = $6;
2169 if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002170 ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002171 } else {
Jakub Narebski9d301452007-11-01 12:38:08 +01002172 $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002173 }
2174 }
Jakub Narebski78bc4032007-05-07 01:10:03 +02002175 # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
2176 # combined diff (for merge commit)
2177 elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
2178 $res{'nparents'} = length($1);
2179 $res{'from_mode'} = [ split(' ', $2) ];
2180 $res{'to_mode'} = pop @{$res{'from_mode'}};
2181 $res{'from_id'} = [ split(' ', $3) ];
2182 $res{'to_id'} = pop @{$res{'from_id'}};
Jakub Narebski6aa6f922007-11-01 12:38:09 +01002183 $res{'status_str'} = $4;
Jakub Narebski78bc4032007-05-07 01:10:03 +02002184 $res{'status'} = [ split('', $4) ];
2185 $res{'to_file'} = unquote($5);
2186 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002187 # 'c512b523472485aef4fff9e57b229d9d243c967f'
Jakub Narebski0edcb372006-08-31 00:36:04 +02002188 elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
2189 $res{'commit'} = $1;
2190 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002191
2192 return wantarray ? %res : \%res;
2193}
2194
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002195# wrapper: return parsed line of git-diff-tree "raw" output
2196# (the argument might be raw line, or parsed info)
2197sub parsed_difftree_line {
2198 my $line_or_ref = shift;
2199
2200 if (ref($line_or_ref) eq "HASH") {
2201 # pre-parsed (or generated by hand)
2202 return $line_or_ref;
2203 } else {
2204 return parse_difftree_raw_line($line_or_ref);
2205 }
2206}
2207
Jakub Narebskicb849b42006-08-31 00:32:15 +02002208# parse line of git-ls-tree output
2209sub parse_ls_tree_line ($;%) {
2210 my $line = shift;
2211 my %opts = @_;
2212 my %res;
2213
2214 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002215 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
Jakub Narebskicb849b42006-08-31 00:32:15 +02002216
2217 $res{'mode'} = $1;
2218 $res{'type'} = $2;
2219 $res{'hash'} = $3;
2220 if ($opts{'-z'}) {
2221 $res{'name'} = $4;
2222 } else {
2223 $res{'name'} = unquote($4);
2224 }
2225
2226 return wantarray ? %res : \%res;
2227}
2228
Jakub Narebski90921742007-06-08 13:27:42 +02002229# generates _two_ hashes, references to which are passed as 2 and 3 argument
2230sub parse_from_to_diffinfo {
2231 my ($diffinfo, $from, $to, @parents) = @_;
2232
2233 if ($diffinfo->{'nparents'}) {
2234 # combined diff
2235 $from->{'file'} = [];
2236 $from->{'href'} = [];
2237 fill_from_file_info($diffinfo, @parents)
2238 unless exists $diffinfo->{'from_file'};
2239 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
Jakub Narebski9d301452007-11-01 12:38:08 +01002240 $from->{'file'}[$i] =
2241 defined $diffinfo->{'from_file'}[$i] ?
2242 $diffinfo->{'from_file'}[$i] :
2243 $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002244 if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
2245 $from->{'href'}[$i] = href(action=>"blob",
2246 hash_base=>$parents[$i],
2247 hash=>$diffinfo->{'from_id'}[$i],
2248 file_name=>$from->{'file'}[$i]);
2249 } else {
2250 $from->{'href'}[$i] = undef;
2251 }
2252 }
2253 } else {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002254 # ordinary (not combined) diff
Jakub Narebski9d301452007-11-01 12:38:08 +01002255 $from->{'file'} = $diffinfo->{'from_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002256 if ($diffinfo->{'status'} ne "A") { # not new (added) file
2257 $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
2258 hash=>$diffinfo->{'from_id'},
2259 file_name=>$from->{'file'});
2260 } else {
2261 delete $from->{'href'};
2262 }
2263 }
2264
Jakub Narebski9d301452007-11-01 12:38:08 +01002265 $to->{'file'} = $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002266 if (!is_deleted($diffinfo)) { # file exists in result
2267 $to->{'href'} = href(action=>"blob", hash_base=>$hash,
2268 hash=>$diffinfo->{'to_id'},
2269 file_name=>$to->{'file'});
2270 } else {
2271 delete $to->{'href'};
2272 }
2273}
2274
Jakub Narebski717b8312006-07-31 21:22:15 +02002275## ......................................................................
2276## parse to array of hashes functions
2277
Jakub Narebskicd146402006-11-02 20:23:11 +01002278sub git_get_heads_list {
2279 my $limit = shift;
2280 my @headslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002281
Jakub Narebskicd146402006-11-02 20:23:11 +01002282 open my $fd, '-|', git_cmd(), 'for-each-ref',
2283 ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
2284 '--format=%(objectname) %(refname) %(subject)%00%(committer)',
2285 'refs/heads'
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002286 or return;
2287 while (my $line = <$fd>) {
Jakub Narebskicd146402006-11-02 20:23:11 +01002288 my %ref_item;
Jakub Narebski120ddde2006-09-19 14:33:22 +02002289
Jakub Narebskicd146402006-11-02 20:23:11 +01002290 chomp $line;
2291 my ($refinfo, $committerinfo) = split(/\0/, $line);
2292 my ($hash, $name, $title) = split(' ', $refinfo, 3);
2293 my ($committer, $epoch, $tz) =
2294 ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01002295 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01002296 $name =~ s!^refs/heads/!!;
2297
2298 $ref_item{'name'} = $name;
2299 $ref_item{'id'} = $hash;
2300 $ref_item{'title'} = $title || '(no commit message)';
2301 $ref_item{'epoch'} = $epoch;
2302 if ($epoch) {
2303 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
2304 } else {
2305 $ref_item{'age'} = "unknown";
Jakub Narebski717b8312006-07-31 21:22:15 +02002306 }
Jakub Narebskicd146402006-11-02 20:23:11 +01002307
2308 push @headslist, \%ref_item;
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002309 }
2310 close $fd;
Junio C Hamano7a13b992006-07-31 19:18:34 -07002311
Jakub Narebskicd146402006-11-02 20:23:11 +01002312 return wantarray ? @headslist : \@headslist;
2313}
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002314
Jakub Narebskicd146402006-11-02 20:23:11 +01002315sub git_get_tags_list {
2316 my $limit = shift;
2317 my @tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002318
Jakub Narebskicd146402006-11-02 20:23:11 +01002319 open my $fd, '-|', git_cmd(), 'for-each-ref',
2320 ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
2321 '--format=%(objectname) %(objecttype) %(refname) '.
2322 '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
2323 'refs/tags'
2324 or return;
2325 while (my $line = <$fd>) {
2326 my %ref_item;
2327
2328 chomp $line;
2329 my ($refinfo, $creatorinfo) = split(/\0/, $line);
2330 my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
2331 my ($creator, $epoch, $tz) =
2332 ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01002333 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01002334 $name =~ s!^refs/tags/!!;
2335
2336 $ref_item{'type'} = $type;
2337 $ref_item{'id'} = $id;
2338 $ref_item{'name'} = $name;
2339 if ($type eq "tag") {
2340 $ref_item{'subject'} = $title;
2341 $ref_item{'reftype'} = $reftype;
2342 $ref_item{'refid'} = $refid;
2343 } else {
2344 $ref_item{'reftype'} = $type;
2345 $ref_item{'refid'} = $id;
2346 }
2347
2348 if ($type eq "tag" || $type eq "commit") {
2349 $ref_item{'epoch'} = $epoch;
2350 if ($epoch) {
2351 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
2352 } else {
2353 $ref_item{'age'} = "unknown";
2354 }
2355 }
2356
2357 push @tagslist, \%ref_item;
Jakub Narebski717b8312006-07-31 21:22:15 +02002358 }
Jakub Narebskicd146402006-11-02 20:23:11 +01002359 close $fd;
2360
2361 return wantarray ? @tagslist : \@tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002362}
2363
2364## ----------------------------------------------------------------------
2365## filesystem-related functions
2366
2367sub get_file_owner {
2368 my $path = shift;
2369
2370 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
2371 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
2372 if (!defined $gcos) {
2373 return undef;
2374 }
2375 my $owner = $gcos;
2376 $owner =~ s/[,;].*$//;
Martin Koegler00f429a2007-06-03 17:42:44 +02002377 return to_utf8($owner);
Jakub Narebski717b8312006-07-31 21:22:15 +02002378}
2379
2380## ......................................................................
2381## mimetype related functions
2382
2383sub mimetype_guess_file {
2384 my $filename = shift;
2385 my $mimemap = shift;
2386 -r $mimemap or return undef;
2387
2388 my %mimemap;
2389 open(MIME, $mimemap) or return undef;
2390 while (<MIME>) {
Jakub Narebski618918e2006-08-14 02:15:22 +02002391 next if m/^#/; # skip comments
Jakub Narebski717b8312006-07-31 21:22:15 +02002392 my ($mime, $exts) = split(/\t+/);
Junio C Hamano46b059d2006-07-31 19:24:37 -07002393 if (defined $exts) {
2394 my @exts = split(/\s+/, $exts);
2395 foreach my $ext (@exts) {
2396 $mimemap{$ext} = $mime;
2397 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002398 }
2399 }
2400 close(MIME);
2401
Jakub Narebski80593192006-09-19 13:57:03 +02002402 $filename =~ /\.([^.]*)$/;
Jakub Narebski717b8312006-07-31 21:22:15 +02002403 return $mimemap{$1};
2404}
2405
2406sub mimetype_guess {
2407 my $filename = shift;
2408 my $mime;
2409 $filename =~ /\./ or return undef;
2410
2411 if ($mimetypes_file) {
2412 my $file = $mimetypes_file;
Jakub Narebskid5aa50d2006-08-14 02:16:33 +02002413 if ($file !~ m!^/!) { # if it is relative path
2414 # it is relative to project
2415 $file = "$projectroot/$project/$file";
2416 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002417 $mime = mimetype_guess_file($filename, $file);
2418 }
2419 $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
2420 return $mime;
2421}
2422
Jakub Narebski847e01f2006-08-14 02:05:47 +02002423sub blob_mimetype {
Jakub Narebski717b8312006-07-31 21:22:15 +02002424 my $fd = shift;
2425 my $filename = shift;
2426
2427 if ($filename) {
2428 my $mime = mimetype_guess($filename);
2429 $mime and return $mime;
2430 }
2431
2432 # just in case
2433 return $default_blob_plain_mimetype unless $fd;
2434
2435 if (-T $fd) {
2436 return 'text/plain' .
2437 ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
2438 } elsif (! $filename) {
2439 return 'application/octet-stream';
2440 } elsif ($filename =~ m/\.png$/i) {
2441 return 'image/png';
2442 } elsif ($filename =~ m/\.gif$/i) {
2443 return 'image/gif';
2444 } elsif ($filename =~ m/\.jpe?g$/i) {
2445 return 'image/jpeg';
2446 } else {
2447 return 'application/octet-stream';
2448 }
2449}
2450
2451## ======================================================================
2452## functions printing HTML: header, footer, error page
2453
Kay Sievers12a88f22005-08-07 20:02:47 +02002454sub git_header_html {
Kay Sieversa59d4af2005-08-07 20:15:44 +02002455 my $status = shift || "200 OK";
Kay Sievers11044292005-10-19 03:18:45 +02002456 my $expires = shift;
Kay Sieversa59d4af2005-08-07 20:15:44 +02002457
Petr Baudis8be28902006-10-24 05:18:39 +02002458 my $title = "$site_name";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002459 if (defined $project) {
Martin Koegler00f429a2007-06-03 17:42:44 +02002460 $title .= " - " . to_utf8($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02002461 if (defined $action) {
2462 $title .= "/$action";
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00002463 if (defined $file_name) {
Jakub Narebski403d0902006-11-08 11:48:56 +01002464 $title .= " - " . esc_path($file_name);
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00002465 if ($action eq "tree" && $file_name !~ m|/$|) {
2466 $title .= "/";
2467 }
2468 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02002469 }
2470 }
Alp Tokerf6801d62006-07-11 11:19:34 +01002471 my $content_type;
2472 # require explicit support from the UA if we are to send the page as
2473 # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
2474 # we have to do this because MSIE sometimes globs '*/*', pretending to
2475 # support xhtml+xml but choking when it gets what it asked for.
Jakub Narebski952c65f2006-08-22 16:52:50 +02002476 if (defined $cgi->http('HTTP_ACCEPT') &&
2477 $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
2478 $cgi->Accept('application/xhtml+xml') != 0) {
Alp Tokerf6801d62006-07-11 11:19:34 +01002479 $content_type = 'application/xhtml+xml';
2480 } else {
2481 $content_type = 'text/html';
2482 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02002483 print $cgi->header(-type=>$content_type, -charset => 'utf-8',
2484 -status=> $status, -expires => $expires);
Jakub Narebski45c9a752006-12-27 23:59:51 +01002485 my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
Kay Sieversa59d4af2005-08-07 20:15:44 +02002486 print <<EOF;
Kay Sievers6191f8e2005-08-07 20:19:56 +02002487<?xml version="1.0" encoding="utf-8"?>
Kay Sievers161332a2005-08-07 19:49:46 +02002488<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
Kay Sievers034df392005-08-07 20:20:07 +02002489<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
Jakub Narebskid4baf9e2006-08-17 11:21:28 +02002490<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
Jakub Narebskiae20de52006-06-21 09:48:03 +02002491<!-- git core binaries version $git_version -->
Kay Sievers161332a2005-08-07 19:49:46 +02002492<head>
Alp Tokerf6801d62006-07-11 11:19:34 +01002493<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
Jakub Narebski45c9a752006-12-27 23:59:51 +01002494<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
Kay Sieversc994d622005-08-07 20:27:18 +02002495<meta name="robots" content="index, nofollow"/>
Kay Sieversb87d78d2005-08-07 20:21:04 +02002496<title>$title</title>
Kay Sievers161332a2005-08-07 19:49:46 +02002497EOF
Alan Chandlerb2d34762006-10-03 13:49:03 +01002498# print out each stylesheet that exist
2499 if (defined $stylesheet) {
2500#provides backwards capability for those people who define style sheet in a config file
2501 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
2502 } else {
2503 foreach my $stylesheet (@stylesheets) {
2504 next unless $stylesheet;
2505 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
2506 }
2507 }
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002508 if (defined $project) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002509 printf('<link rel="alternate" title="%s log RSS feed" '.
2510 'href="%s" type="application/rss+xml" />'."\n",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002511 esc_param($project), href(action=>"rss"));
Jakub Narebski12075102007-07-28 16:27:32 +02002512 printf('<link rel="alternate" title="%s log RSS feed (no merges)" '.
2513 'href="%s" type="application/rss+xml" />'."\n",
2514 esc_param($project), href(action=>"rss",
2515 extra_options=>"--no-merges"));
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002516 printf('<link rel="alternate" title="%s log Atom feed" '.
2517 'href="%s" type="application/atom+xml" />'."\n",
2518 esc_param($project), href(action=>"atom"));
Jakub Narebski12075102007-07-28 16:27:32 +02002519 printf('<link rel="alternate" title="%s log Atom feed (no merges)" '.
2520 'href="%s" type="application/atom+xml" />'."\n",
2521 esc_param($project), href(action=>"atom",
2522 extra_options=>"--no-merges"));
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002523 } else {
2524 printf('<link rel="alternate" title="%s projects list" '.
2525 'href="%s" type="text/plain; charset=utf-8"/>'."\n",
2526 $site_name, href(project=>undef, action=>"project_index"));
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002527 printf('<link rel="alternate" title="%s projects feeds" '.
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002528 'href="%s" type="text/x-opml"/>'."\n",
2529 $site_name, href(project=>undef, action=>"opml"));
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002530 }
Jakub Narebski0b5deba2006-09-04 20:32:13 +02002531 if (defined $favicon) {
2532 print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n);
2533 }
Jakub Narebski10161352006-08-05 13:18:58 +02002534
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002535 print "</head>\n" .
Alan Chandlerb2d34762006-10-03 13:49:03 +01002536 "<body>\n";
2537
2538 if (-f $site_header) {
2539 open (my $fd, $site_header);
2540 print <$fd>;
2541 close $fd;
2542 }
2543
2544 print "<div class=\"page_header\">\n" .
Jakub Narebski9a7a62f2006-10-06 12:31:05 +02002545 $cgi->a({-href => esc_url($logo_url),
2546 -title => $logo_label},
2547 qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
Jakub Narebskif93bff82006-09-26 01:58:41 +02002548 print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002549 if (defined $project) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002550 print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
Kay Sieversb87d78d2005-08-07 20:21:04 +02002551 if (defined $action) {
2552 print " / $action";
2553 }
Kay Sievers19806692005-08-07 20:26:27 +02002554 print "\n";
Robert Fitzsimons6be93512006-12-23 03:35:16 +00002555 }
Petr Baudisd77b5672007-05-17 04:24:19 +02002556 print "</div>\n";
2557
Robert Fitzsimons6be93512006-12-23 03:35:16 +00002558 my ($have_search) = gitweb_check_feature('search');
2559 if ((defined $project) && ($have_search)) {
Kay Sievers19806692005-08-07 20:26:27 +02002560 if (!defined $searchtext) {
2561 $searchtext = "";
2562 }
Kay Sieversc39e47d2005-09-17 03:00:21 +02002563 my $search_hash;
Timo Hirvonen4c5c2022006-06-20 16:41:05 +03002564 if (defined $hash_base) {
2565 $search_hash = $hash_base;
2566 } elsif (defined $hash) {
Kay Sieversc39e47d2005-09-17 03:00:21 +02002567 $search_hash = $hash;
2568 } else {
Jakub Narebski8adc4bd2006-06-22 08:52:57 +02002569 $search_hash = "HEAD";
Kay Sieversc39e47d2005-09-17 03:00:21 +02002570 }
Matt McCutchen40375a82007-06-28 14:57:07 -04002571 my $action = $my_uri;
2572 my ($use_pathinfo) = gitweb_check_feature('pathinfo');
2573 if ($use_pathinfo) {
2574 $action .= "/$project";
2575 } else {
2576 $cgi->param("p", $project);
2577 }
Kay Sievers19806692005-08-07 20:26:27 +02002578 $cgi->param("a", "search");
Kay Sieversc39e47d2005-09-17 03:00:21 +02002579 $cgi->param("h", $search_hash);
Matt McCutchen40375a82007-06-28 14:57:07 -04002580 print $cgi->startform(-method => "get", -action => $action) .
Kay Sieversc994d622005-08-07 20:27:18 +02002581 "<div class=\"search\">\n" .
Matt McCutchen40375a82007-06-28 14:57:07 -04002582 (!$use_pathinfo && $cgi->hidden(-name => "p") . "\n") .
Kay Sieversc994d622005-08-07 20:27:18 +02002583 $cgi->hidden(-name => "a") . "\n" .
Kay Sieversc39e47d2005-09-17 03:00:21 +02002584 $cgi->hidden(-name => "h") . "\n" .
Petr Baudis88ad7292006-10-24 05:15:46 +02002585 $cgi->popup_menu(-name => 'st', -default => 'commit',
Petr Baudise7738552007-05-17 04:31:12 +02002586 -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
Petr Baudis88ad7292006-10-24 05:15:46 +02002587 $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
2588 " search:\n",
Kay Sieversc994d622005-08-07 20:27:18 +02002589 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
Petr Baudis0e559912008-02-26 13:22:08 +01002590 "<span title=\"Extended regular expression\">" .
2591 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
2592 -checked => $search_use_regexp) .
2593 "</span>" .
Kay Sieversc994d622005-08-07 20:27:18 +02002594 "</div>" .
2595 $cgi->end_form() . "\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02002596 }
Kay Sievers161332a2005-08-07 19:49:46 +02002597}
2598
Kay Sievers12a88f22005-08-07 20:02:47 +02002599sub git_footer_html {
Kay Sievers6191f8e2005-08-07 20:19:56 +02002600 print "<div class=\"page_footer\">\n";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002601 if (defined $project) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02002602 my $descr = git_get_project_description($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02002603 if (defined $descr) {
Kay Sievers40c13812005-11-19 17:41:29 +01002604 print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
Kay Sievers6191f8e2005-08-07 20:19:56 +02002605 }
Jakub Narebskia1565c42006-09-15 19:30:34 +02002606 print $cgi->a({-href => href(action=>"rss"),
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002607 -class => "rss_logo"}, "RSS") . " ";
2608 print $cgi->a({-href => href(action=>"atom"),
2609 -class => "rss_logo"}, "Atom") . "\n";
Kay Sieversc994d622005-08-07 20:27:18 +02002610 } else {
Jakub Narebskia1565c42006-09-15 19:30:34 +02002611 print $cgi->a({-href => href(project=>undef, action=>"opml"),
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002612 -class => "rss_logo"}, "OPML") . " ";
2613 print $cgi->a({-href => href(project=>undef, action=>"project_index"),
2614 -class => "rss_logo"}, "TXT") . "\n";
Kay Sieversff7669a2005-08-07 20:13:02 +02002615 }
Alan Chandlerb2d34762006-10-03 13:49:03 +01002616 print "</div>\n" ;
2617
2618 if (-f $site_footer) {
2619 open (my $fd, $site_footer);
2620 print <$fd>;
2621 close $fd;
2622 }
2623
2624 print "</body>\n" .
Kay Sievers9cd3d982005-08-07 20:17:42 +02002625 "</html>";
Kay Sievers161332a2005-08-07 19:49:46 +02002626}
2627
Kay Sievers061cc7c2005-08-07 20:15:57 +02002628sub die_error {
2629 my $status = shift || "403 Forbidden";
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02002630 my $error = shift || "Malformed query, file missing or permission denied";
Kay Sievers664f4cc2005-08-07 20:17:19 +02002631
Kay Sieversa59d4af2005-08-07 20:15:44 +02002632 git_header_html($status);
Jakub Narebski59b9f612006-08-22 23:42:53 +02002633 print <<EOF;
2634<div class="page_body">
2635<br /><br />
2636$status - $error
2637<br />
2638</div>
2639EOF
Kay Sieversa59d4af2005-08-07 20:15:44 +02002640 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02002641 exit;
Kay Sieversa59d4af2005-08-07 20:15:44 +02002642}
2643
Jakub Narebski717b8312006-07-31 21:22:15 +02002644## ----------------------------------------------------------------------
2645## functions printing or outputting HTML: navigation
2646
Jakub Narebski847e01f2006-08-14 02:05:47 +02002647sub git_print_page_nav {
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002648 my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
2649 $extra = '' if !defined $extra; # pager or formats
2650
2651 my @navs = qw(summary shortlog log commit commitdiff tree);
2652 if ($suppress) {
2653 @navs = grep { $_ ne $suppress } @navs;
2654 }
2655
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002656 my %arg = map { $_ => {action=>$_} } @navs;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002657 if (defined $head) {
2658 for (qw(commit commitdiff)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02002659 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002660 }
2661 if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
2662 for (qw(shortlog log)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02002663 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002664 }
2665 }
2666 }
Jakub Narebski3be8e722007-04-01 22:22:21 +02002667 $arg{'tree'}{'hash'} = $treehead if defined $treehead;
2668 $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002669
2670 print "<div class=\"page_nav\">\n" .
2671 (join " | ",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002672 map { $_ eq $current ?
2673 $_ : $cgi->a({-href => href(%{$arg{$_}})}, "$_")
2674 } @navs);
Jakub Narebski898a8932006-07-30 16:14:43 +02002675 print "<br/>\n$extra<br/>\n" .
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002676 "</div>\n";
2677}
2678
Jakub Narebski847e01f2006-08-14 02:05:47 +02002679sub format_paging_nav {
Jakub Narebski6855f422006-07-30 20:31:00 +02002680 my ($action, $hash, $head, $page, $nrevs) = @_;
Jakub Narebski43ffc062006-07-30 17:49:00 +02002681 my $paging_nav;
2682
2683
2684 if ($hash ne $head || $page) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002685 $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
Jakub Narebski43ffc062006-07-30 17:49:00 +02002686 } else {
2687 $paging_nav .= "HEAD";
2688 }
2689
2690 if ($page > 0) {
2691 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01002692 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski26298b52006-08-10 12:38:47 +02002693 -accesskey => "p", -title => "Alt-p"}, "prev");
Jakub Narebski43ffc062006-07-30 17:49:00 +02002694 } else {
2695 $paging_nav .= " &sdot; prev";
2696 }
2697
2698 if ($nrevs >= (100 * ($page+1)-1)) {
2699 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01002700 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebski26298b52006-08-10 12:38:47 +02002701 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski43ffc062006-07-30 17:49:00 +02002702 } else {
2703 $paging_nav .= " &sdot; next";
2704 }
2705
2706 return $paging_nav;
2707}
2708
Jakub Narebski717b8312006-07-31 21:22:15 +02002709## ......................................................................
2710## functions printing or outputting HTML: div
Kay Sievers42f7eb92005-08-07 20:21:46 +02002711
Jakub Narebski847e01f2006-08-14 02:05:47 +02002712sub git_print_header_div {
Jakub Narebski717b8312006-07-31 21:22:15 +02002713 my ($action, $title, $hash, $hash_base) = @_;
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002714 my %args = ();
Jakub Narebski717b8312006-07-31 21:22:15 +02002715
Jakub Narebski3be8e722007-04-01 22:22:21 +02002716 $args{'action'} = $action;
2717 $args{'hash'} = $hash if $hash;
2718 $args{'hash_base'} = $hash_base if $hash_base;
Jakub Narebski717b8312006-07-31 21:22:15 +02002719
2720 print "<div class=\"header\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002721 $cgi->a({-href => href(%args), -class => "title"},
2722 $title ? $title : $action) .
2723 "\n</div>\n";
Kay Sievers42f7eb92005-08-07 20:21:46 +02002724}
2725
Jakub Narebski6fd92a22006-08-28 14:48:12 +02002726#sub git_print_authorship (\%) {
2727sub git_print_authorship {
2728 my $co = shift;
2729
Jakub Narebskia44465c2006-08-28 23:17:31 +02002730 my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
Jakub Narebski6fd92a22006-08-28 14:48:12 +02002731 print "<div class=\"author_date\">" .
2732 esc_html($co->{'author_name'}) .
Jakub Narebskia44465c2006-08-28 23:17:31 +02002733 " [$ad{'rfc2822'}";
2734 if ($ad{'hour_local'} < 6) {
2735 printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
2736 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
2737 } else {
2738 printf(" (%02d:%02d %s)",
2739 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
2740 }
2741 print "]</div>\n";
Jakub Narebski6fd92a22006-08-28 14:48:12 +02002742}
2743
Jakub Narebski717b8312006-07-31 21:22:15 +02002744sub git_print_page_path {
2745 my $name = shift;
2746 my $type = shift;
Luben Tuikov59fb1c92006-08-17 10:39:29 -07002747 my $hb = shift;
Junio C Hamanodf2c37a2006-01-09 13:13:39 +01002748
Jakub Narebski4df118e2006-10-21 17:53:55 +02002749
2750 print "<div class=\"page_path\">";
2751 print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
Martin Koegler00f429a2007-06-03 17:42:44 +02002752 -title => 'tree root'}, to_utf8("[$project]"));
Jakub Narebski4df118e2006-10-21 17:53:55 +02002753 print " / ";
2754 if (defined $name) {
Jakub Narebski762c7202006-09-04 18:17:58 +02002755 my @dirname = split '/', $name;
2756 my $basename = pop @dirname;
2757 my $fullname = '';
2758
Jakub Narebski762c7202006-09-04 18:17:58 +02002759 foreach my $dir (@dirname) {
Petr Baudis16fdb482006-09-21 02:05:50 +02002760 $fullname .= ($fullname ? '/' : '') . $dir;
Jakub Narebski762c7202006-09-04 18:17:58 +02002761 print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
2762 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01002763 -title => $fullname}, esc_path($dir));
Petr Baudis26d0a972006-09-23 01:00:12 +02002764 print " / ";
Jakub Narebski762c7202006-09-04 18:17:58 +02002765 }
2766 if (defined $type && $type eq 'blob') {
Jakub Narebski952c65f2006-08-22 16:52:50 +02002767 print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
Jakub Narebski762c7202006-09-04 18:17:58 +02002768 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01002769 -title => $name}, esc_path($basename));
Jakub Narebski762c7202006-09-04 18:17:58 +02002770 } elsif (defined $type && $type eq 'tree') {
2771 print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
2772 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01002773 -title => $name}, esc_path($basename));
Jakub Narebski4df118e2006-10-21 17:53:55 +02002774 print " / ";
Luben Tuikov59fb1c92006-08-17 10:39:29 -07002775 } else {
Jakub Narebski403d0902006-11-08 11:48:56 +01002776 print esc_path($basename);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07002777 }
Kay Sievers8ed23e12005-08-07 20:05:44 +02002778 }
Jakub Narebski4df118e2006-10-21 17:53:55 +02002779 print "<br/></div>\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02002780}
2781
Jakub Narebskib7f92532006-08-28 14:48:10 +02002782# sub git_print_log (\@;%) {
2783sub git_print_log ($;%) {
Jakub Narebskid16d0932006-08-17 11:21:23 +02002784 my $log = shift;
Jakub Narebskib7f92532006-08-28 14:48:10 +02002785 my %opts = @_;
Jakub Narebskid16d0932006-08-17 11:21:23 +02002786
Jakub Narebskib7f92532006-08-28 14:48:10 +02002787 if ($opts{'-remove_title'}) {
2788 # remove title, i.e. first line of log
2789 shift @$log;
2790 }
Jakub Narebskid16d0932006-08-17 11:21:23 +02002791 # remove leading empty lines
2792 while (defined $log->[0] && $log->[0] eq "") {
2793 shift @$log;
2794 }
2795
2796 # print log
2797 my $signoff = 0;
2798 my $empty = 0;
2799 foreach my $line (@$log) {
Jakub Narebskib7f92532006-08-28 14:48:10 +02002800 if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
2801 $signoff = 1;
Jakub Narebskifba20b42006-08-28 14:48:13 +02002802 $empty = 0;
Jakub Narebskib7f92532006-08-28 14:48:10 +02002803 if (! $opts{'-remove_signoff'}) {
2804 print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
2805 next;
2806 } else {
2807 # remove signoff lines
2808 next;
2809 }
2810 } else {
2811 $signoff = 0;
2812 }
2813
Jakub Narebskid16d0932006-08-17 11:21:23 +02002814 # print only one empty line
2815 # do not print empty line after signoff
2816 if ($line eq "") {
2817 next if ($empty || $signoff);
2818 $empty = 1;
2819 } else {
2820 $empty = 0;
2821 }
Jakub Narebskib7f92532006-08-28 14:48:10 +02002822
2823 print format_log_line_html($line) . "<br/>\n";
2824 }
2825
2826 if ($opts{'-final_empty_line'}) {
2827 # end with single empty line
2828 print "<br/>\n" unless $empty;
Jakub Narebskid16d0932006-08-17 11:21:23 +02002829 }
2830}
2831
Jakub Narebskie33fba42006-12-10 13:25:46 +01002832# return link target (what link points to)
2833sub git_get_link_target {
2834 my $hash = shift;
2835 my $link_target;
2836
2837 # read link
2838 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
2839 or return;
2840 {
2841 local $/;
2842 $link_target = <$fd>;
2843 }
2844 close $fd
2845 or return;
2846
2847 return $link_target;
2848}
2849
Jakub Narebski3bf9d572006-12-10 13:25:48 +01002850# given link target, and the directory (basedir) the link is in,
2851# return target of link relative to top directory (top tree);
2852# return undef if it is not possible (including absolute links).
2853sub normalize_link_target {
2854 my ($link_target, $basedir, $hash_base) = @_;
2855
2856 # we can normalize symlink target only if $hash_base is provided
2857 return unless $hash_base;
2858
2859 # absolute symlinks (beginning with '/') cannot be normalized
2860 return if (substr($link_target, 0, 1) eq '/');
2861
2862 # normalize link target to path from top (root) tree (dir)
2863 my $path;
2864 if ($basedir) {
2865 $path = $basedir . '/' . $link_target;
2866 } else {
2867 # we are in top (root) tree (dir)
2868 $path = $link_target;
2869 }
2870
2871 # remove //, /./, and /../
2872 my @path_parts;
2873 foreach my $part (split('/', $path)) {
2874 # discard '.' and ''
2875 next if (!$part || $part eq '.');
2876 # handle '..'
2877 if ($part eq '..') {
2878 if (@path_parts) {
2879 pop @path_parts;
2880 } else {
2881 # link leads outside repository (outside top dir)
2882 return;
2883 }
2884 } else {
2885 push @path_parts, $part;
2886 }
2887 }
2888 $path = join('/', @path_parts);
2889
2890 return $path;
2891}
Jakub Narebskie33fba42006-12-10 13:25:46 +01002892
Jakub Narebskifa702002006-08-31 00:35:07 +02002893# print tree entry (row of git_tree), but without encompassing <tr> element
2894sub git_print_tree_entry {
2895 my ($t, $basedir, $hash_base, $have_blame) = @_;
2896
2897 my %base_key = ();
Jakub Narebskie33fba42006-12-10 13:25:46 +01002898 $base_key{'hash_base'} = $hash_base if defined $hash_base;
Jakub Narebskifa702002006-08-31 00:35:07 +02002899
Luben Tuikov4de741b2006-09-25 22:38:16 -07002900 # The format of a table row is: mode list link. Where mode is
2901 # the mode of the entry, list is the name of the entry, an href,
2902 # and link is the action links of the entry.
2903
Jakub Narebskifa702002006-08-31 00:35:07 +02002904 print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
2905 if ($t->{'type'} eq "blob") {
2906 print "<td class=\"list\">" .
Luben Tuikov4de741b2006-09-25 22:38:16 -07002907 $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02002908 file_name=>"$basedir$t->{'name'}", %base_key),
Jakub Narebskie33fba42006-12-10 13:25:46 +01002909 -class => "list"}, esc_path($t->{'name'}));
2910 if (S_ISLNK(oct $t->{'mode'})) {
2911 my $link_target = git_get_link_target($t->{'hash'});
2912 if ($link_target) {
Jakub Narebski3bf9d572006-12-10 13:25:48 +01002913 my $norm_target = normalize_link_target($link_target, $basedir, $hash_base);
2914 if (defined $norm_target) {
2915 print " -> " .
2916 $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
2917 file_name=>$norm_target),
2918 -title => $norm_target}, esc_path($link_target));
2919 } else {
2920 print " -> " . esc_path($link_target);
2921 }
Jakub Narebskie33fba42006-12-10 13:25:46 +01002922 }
2923 }
2924 print "</td>\n";
Luben Tuikov4de741b2006-09-25 22:38:16 -07002925 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02002926 print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01002927 file_name=>"$basedir$t->{'name'}", %base_key)},
2928 "blob");
Jakub Narebskifa702002006-08-31 00:35:07 +02002929 if ($have_blame) {
Petr Baudis4777b012006-10-24 05:36:10 +02002930 print " | " .
2931 $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01002932 file_name=>"$basedir$t->{'name'}", %base_key)},
2933 "blame");
Jakub Narebskifa702002006-08-31 00:35:07 +02002934 }
2935 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02002936 print " | " .
2937 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02002938 hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
2939 "history");
2940 }
2941 print " | " .
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07002942 $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
Jakub Narebskie7fb0222006-10-21 17:52:19 +02002943 file_name=>"$basedir$t->{'name'}")},
2944 "raw");
Luben Tuikov4de741b2006-09-25 22:38:16 -07002945 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02002946
2947 } elsif ($t->{'type'} eq "tree") {
Luben Tuikov0fa105e2006-09-26 12:45:37 -07002948 print "<td class=\"list\">";
2949 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskifa702002006-08-31 00:35:07 +02002950 file_name=>"$basedir$t->{'name'}", %base_key)},
Jakub Narebski403d0902006-11-08 11:48:56 +01002951 esc_path($t->{'name'}));
Luben Tuikov0fa105e2006-09-26 12:45:37 -07002952 print "</td>\n";
2953 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02002954 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01002955 file_name=>"$basedir$t->{'name'}", %base_key)},
2956 "tree");
Jakub Narebskifa702002006-08-31 00:35:07 +02002957 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02002958 print " | " .
2959 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02002960 file_name=>"$basedir$t->{'name'}")},
2961 "history");
2962 }
2963 print "</td>\n";
Jakub Narebski01ac1e32007-07-28 16:27:31 +02002964 } else {
2965 # unknown object: we can only present history for it
2966 # (this includes 'commit' object, i.e. submodule support)
2967 print "<td class=\"list\">" .
2968 esc_path($t->{'name'}) .
2969 "</td>\n";
2970 print "<td class=\"link\">";
2971 if (defined $hash_base) {
2972 print $cgi->a({-href => href(action=>"history",
2973 hash_base=>$hash_base,
2974 file_name=>"$basedir$t->{'name'}")},
2975 "history");
2976 }
2977 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02002978 }
2979}
2980
Jakub Narebski717b8312006-07-31 21:22:15 +02002981## ......................................................................
2982## functions printing large fragments of HTML
Kay Sieversede5e102005-08-07 20:23:12 +02002983
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002984# get pre-image filenames for merge (combined) diff
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02002985sub fill_from_file_info {
2986 my ($diff, @parents) = @_;
2987
2988 $diff->{'from_file'} = [ ];
2989 $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
2990 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
2991 if ($diff->{'status'}[$i] eq 'R' ||
2992 $diff->{'status'}[$i] eq 'C') {
2993 $diff->{'from_file'}[$i] =
2994 git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
2995 }
2996 }
2997
2998 return $diff;
2999}
3000
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003001# is current raw difftree line of file deletion
Jakub Narebski90921742007-06-08 13:27:42 +02003002sub is_deleted {
3003 my $diffinfo = shift;
3004
Jakub Narebski6aa6f922007-11-01 12:38:09 +01003005 return $diffinfo->{'status_str'} =~ /D/;
Jakub Narebski90921742007-06-08 13:27:42 +02003006}
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003007
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003008# does patch correspond to [previous] difftree raw line
3009# $diffinfo - hashref of parsed raw diff format
3010# $patchinfo - hashref of parsed patch diff format
3011# (the same keys as in $diffinfo)
3012sub is_patch_split {
3013 my ($diffinfo, $patchinfo) = @_;
3014
3015 return defined $diffinfo && defined $patchinfo
Jakub Narebski9d301452007-11-01 12:38:08 +01003016 && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003017}
3018
3019
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003020sub git_difftree_body {
Jakub Narebskied224de2007-05-07 01:10:04 +02003021 my ($difftree, $hash, @parents) = @_;
3022 my ($parent) = $parents[0];
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003023 my ($have_blame) = gitweb_check_feature('blame');
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003024 print "<div class=\"list_head\">\n";
3025 if ($#{$difftree} > 10) {
3026 print(($#{$difftree} + 1) . " files changed:\n");
3027 }
3028 print "</div>\n";
3029
Jakub Narebskied224de2007-05-07 01:10:04 +02003030 print "<table class=\"" .
3031 (@parents > 1 ? "combined " : "") .
3032 "diff_tree\">\n";
Jakub Narebski47598d72007-06-08 13:24:56 +02003033
3034 # header only for combined diff in 'commitdiff' view
Jakub Narebski3ef408a2007-09-08 21:54:28 +02003035 my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
Jakub Narebski47598d72007-06-08 13:24:56 +02003036 if ($has_header) {
3037 # table header
3038 print "<thead><tr>\n" .
3039 "<th></th><th></th>\n"; # filename, patchN link
3040 for (my $i = 0; $i < @parents; $i++) {
3041 my $par = $parents[$i];
3042 print "<th>" .
3043 $cgi->a({-href => href(action=>"commitdiff",
3044 hash=>$hash, hash_parent=>$par),
3045 -title => 'commitdiff to parent number ' .
3046 ($i+1) . ': ' . substr($par,0,7)},
3047 $i+1) .
3048 "&nbsp;</th>\n";
3049 }
3050 print "</tr></thead>\n<tbody>\n";
3051 }
3052
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003053 my $alternate = 1;
Jakub Narebskib4657e72006-08-28 14:48:14 +02003054 my $patchno = 0;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003055 foreach my $line (@{$difftree}) {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003056 my $diff = parsed_difftree_line($line);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003057
3058 if ($alternate) {
3059 print "<tr class=\"dark\">\n";
3060 } else {
3061 print "<tr class=\"light\">\n";
3062 }
3063 $alternate ^= 1;
3064
Jakub Narebski493e01d2007-05-07 01:10:06 +02003065 if (exists $diff->{'nparents'}) { # combined diff
Jakub Narebskied224de2007-05-07 01:10:04 +02003066
Jakub Narebski493e01d2007-05-07 01:10:06 +02003067 fill_from_file_info($diff, @parents)
3068 unless exists $diff->{'from_file'};
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003069
Jakub Narebski90921742007-06-08 13:27:42 +02003070 if (!is_deleted($diff)) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003071 # file exists in the result (child) commit
3072 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003073 $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3074 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003075 hash_base=>$hash),
Jakub Narebski493e01d2007-05-07 01:10:06 +02003076 -class => "list"}, esc_path($diff->{'to_file'})) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003077 "</td>\n";
3078 } else {
3079 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003080 esc_path($diff->{'to_file'}) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003081 "</td>\n";
3082 }
3083
3084 if ($action eq 'commitdiff') {
3085 # link to patch
3086 $patchno++;
3087 print "<td class=\"link\">" .
3088 $cgi->a({-href => "#patch$patchno"}, "patch") .
3089 " | " .
3090 "</td>\n";
3091 }
3092
3093 my $has_history = 0;
3094 my $not_deleted = 0;
Jakub Narebski493e01d2007-05-07 01:10:06 +02003095 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003096 my $hash_parent = $parents[$i];
Jakub Narebski493e01d2007-05-07 01:10:06 +02003097 my $from_hash = $diff->{'from_id'}[$i];
3098 my $from_path = $diff->{'from_file'}[$i];
3099 my $status = $diff->{'status'}[$i];
Jakub Narebskied224de2007-05-07 01:10:04 +02003100
3101 $has_history ||= ($status ne 'A');
3102 $not_deleted ||= ($status ne 'D');
3103
Jakub Narebskied224de2007-05-07 01:10:04 +02003104 if ($status eq 'A') {
3105 print "<td class=\"link\" align=\"right\"> | </td>\n";
3106 } elsif ($status eq 'D') {
3107 print "<td class=\"link\">" .
3108 $cgi->a({-href => href(action=>"blob",
3109 hash_base=>$hash,
3110 hash=>$from_hash,
3111 file_name=>$from_path)},
3112 "blob" . ($i+1)) .
3113 " | </td>\n";
3114 } else {
Jakub Narebski493e01d2007-05-07 01:10:06 +02003115 if ($diff->{'to_id'} eq $from_hash) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003116 print "<td class=\"link nochange\">";
3117 } else {
3118 print "<td class=\"link\">";
3119 }
3120 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003121 hash=>$diff->{'to_id'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003122 hash_parent=>$from_hash,
3123 hash_base=>$hash,
3124 hash_parent_base=>$hash_parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003125 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003126 file_parent=>$from_path)},
3127 "diff" . ($i+1)) .
3128 " | </td>\n";
3129 }
3130 }
3131
3132 print "<td class=\"link\">";
3133 if ($not_deleted) {
3134 print $cgi->a({-href => href(action=>"blob",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003135 hash=>$diff->{'to_id'},
3136 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003137 hash_base=>$hash)},
3138 "blob");
3139 print " | " if ($has_history);
3140 }
3141 if ($has_history) {
3142 print $cgi->a({-href => href(action=>"history",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003143 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003144 hash_base=>$hash)},
3145 "history");
3146 }
3147 print "</td>\n";
3148
3149 print "</tr>\n";
3150 next; # instead of 'else' clause, to avoid extra indent
3151 }
3152 # else ordinary diff
3153
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003154 my ($to_mode_oct, $to_mode_str, $to_file_type);
3155 my ($from_mode_oct, $from_mode_str, $from_file_type);
Jakub Narebski493e01d2007-05-07 01:10:06 +02003156 if ($diff->{'to_mode'} ne ('0' x 6)) {
3157 $to_mode_oct = oct $diff->{'to_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003158 if (S_ISREG($to_mode_oct)) { # only for regular file
3159 $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003160 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003161 $to_file_type = file_type($diff->{'to_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003162 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003163 if ($diff->{'from_mode'} ne ('0' x 6)) {
3164 $from_mode_oct = oct $diff->{'from_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003165 if (S_ISREG($to_mode_oct)) { # only for regular file
3166 $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
3167 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003168 $from_file_type = file_type($diff->{'from_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003169 }
3170
Jakub Narebski493e01d2007-05-07 01:10:06 +02003171 if ($diff->{'status'} eq "A") { # created
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003172 my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
3173 $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
3174 $mode_chng .= "]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07003175 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003176 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3177 hash_base=>$hash, file_name=>$diff->{'file'}),
3178 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003179 print "</td>\n";
3180 print "<td>$mode_chng</td>\n";
3181 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02003182 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02003183 # link to patch
3184 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07003185 print $cgi->a({-href => "#patch$patchno"}, "patch");
Jakub Narebski897d1d22006-11-19 22:51:39 +01003186 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02003187 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003188 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3189 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski3faa5412007-01-08 02:10:42 +01003190 "blob");
Jakub Narebskib4657e72006-08-28 14:48:14 +02003191 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003192
Jakub Narebski493e01d2007-05-07 01:10:06 +02003193 } elsif ($diff->{'status'} eq "D") { # deleted
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003194 my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07003195 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003196 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
3197 hash_base=>$parent, file_name=>$diff->{'file'}),
3198 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003199 print "</td>\n";
3200 print "<td>$mode_chng</td>\n";
3201 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02003202 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02003203 # link to patch
3204 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07003205 print $cgi->a({-href => "#patch$patchno"}, "patch");
3206 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02003207 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003208 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
3209 hash_base=>$parent, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003210 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003211 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003212 print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003213 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003214 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003215 }
Jakub Narebskib4657e72006-08-28 14:48:14 +02003216 print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003217 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003218 "history");
Luben Tuikov499faed2006-09-27 17:23:25 -07003219 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003220
Jakub Narebski493e01d2007-05-07 01:10:06 +02003221 } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003222 my $mode_chnge = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003223 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003224 $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
Jakub Narebski6e72cf42007-01-03 20:47:24 +01003225 if ($from_file_type ne $to_file_type) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003226 $mode_chnge .= " from $from_file_type to $to_file_type";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003227 }
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003228 if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
3229 if ($from_mode_str && $to_mode_str) {
3230 $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
3231 } elsif ($to_mode_str) {
3232 $mode_chnge .= " mode: $to_mode_str";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003233 }
3234 }
3235 $mode_chnge .= "]</span>\n";
3236 }
3237 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003238 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3239 hash_base=>$hash, file_name=>$diff->{'file'}),
3240 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003241 print "</td>\n";
3242 print "<td>$mode_chnge</td>\n";
3243 print "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01003244 if ($action eq 'commitdiff') {
3245 # link to patch
3246 $patchno++;
3247 print $cgi->a({-href => "#patch$patchno"}, "patch") .
3248 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003249 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01003250 # "commit" view and modified file (not onlu mode changed)
3251 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003252 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01003253 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003254 file_name=>$diff->{'file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01003255 "diff") .
3256 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003257 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003258 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3259 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003260 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003261 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003262 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003263 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003264 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003265 }
Luben Tuikoveb51ec92006-09-27 17:24:49 -07003266 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003267 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003268 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003269 print "</td>\n";
3270
Jakub Narebski493e01d2007-05-07 01:10:06 +02003271 } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003272 my %status_name = ('R' => 'moved', 'C' => 'copied');
Jakub Narebski493e01d2007-05-07 01:10:06 +02003273 my $nstatus = $status_name{$diff->{'status'}};
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003274 my $mode_chng = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003275 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003276 # mode also for directories, so we cannot use $to_mode_str
3277 $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003278 }
3279 print "<td>" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003280 $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003281 hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
3282 -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003283 "<td><span class=\"file_status $nstatus\">[$nstatus from " .
3284 $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003285 hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
3286 -class => "list"}, esc_path($diff->{'from_file'})) .
3287 " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
Luben Tuikov499faed2006-09-27 17:23:25 -07003288 "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01003289 if ($action eq 'commitdiff') {
3290 # link to patch
3291 $patchno++;
3292 print $cgi->a({-href => "#patch$patchno"}, "patch") .
3293 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003294 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01003295 # "commit" view and modified file (not only pure rename or copy)
3296 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003297 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01003298 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003299 file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01003300 "diff") .
3301 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003302 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003303 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3304 hash_base=>$parent, file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003305 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003306 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003307 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003308 file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003309 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003310 }
Jakub Narebski897d1d22006-11-19 22:51:39 +01003311 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003312 file_name=>$diff->{'to_file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003313 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003314 print "</td>\n";
3315
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003316 } # we should not encounter Unmerged (U) or Unknown (X) status
3317 print "</tr>\n";
3318 }
Jakub Narebski47598d72007-06-08 13:24:56 +02003319 print "</tbody>" if $has_header;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003320 print "</table>\n";
3321}
3322
Jakub Narebskieee08902006-08-24 00:15:14 +02003323sub git_patchset_body {
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003324 my ($fd, $difftree, $hash, @hash_parents) = @_;
3325 my ($hash_parent) = $hash_parents[0];
Jakub Narebskieee08902006-08-24 00:15:14 +02003326
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003327 my $is_combined = (@hash_parents > 1);
Jakub Narebskieee08902006-08-24 00:15:14 +02003328 my $patch_idx = 0;
Martin Koegler4280cde2007-04-22 22:49:25 -07003329 my $patch_number = 0;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003330 my $patch_line;
Jakub Narebskife875852006-08-25 20:59:39 +02003331 my $diffinfo;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003332 my $to_name;
Jakub Narebski744d0ac2006-11-08 17:59:41 +01003333 my (%from, %to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003334
3335 print "<div class=\"patchset\">\n";
3336
Jakub Narebski6d55f052006-11-18 23:35:39 +01003337 # skip to first patch
3338 while ($patch_line = <$fd>) {
Jakub Narebski157e43b2006-08-24 19:34:36 +02003339 chomp $patch_line;
Jakub Narebskieee08902006-08-24 00:15:14 +02003340
Jakub Narebski6d55f052006-11-18 23:35:39 +01003341 last if ($patch_line =~ m/^diff /);
3342 }
3343
3344 PATCH:
3345 while ($patch_line) {
Jakub Narebski6d55f052006-11-18 23:35:39 +01003346
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003347 # parse "git diff" header line
3348 if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
3349 # $1 is from_name, which we do not use
3350 $to_name = unquote($2);
3351 $to_name =~ s!^b/!!;
3352 } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
3353 # $1 is 'cc' or 'combined', which we do not use
3354 $to_name = unquote($2);
3355 } else {
3356 $to_name = undef;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003357 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01003358
3359 # check if current patch belong to current raw line
3360 # and parse raw git-diff line if needed
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003361 if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
Jakub Narebski22065372007-05-12 12:42:32 +02003362 # this is continuation of a split patch
Jakub Narebski6d55f052006-11-18 23:35:39 +01003363 print "<div class=\"patch cont\">\n";
3364 } else {
3365 # advance raw git-diff output if needed
3366 $patch_idx++ if defined $diffinfo;
Jakub Narebskieee08902006-08-24 00:15:14 +02003367
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003368 # read and prepare patch information
3369 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
3370
Jakub Narebskicd030c32007-06-08 13:33:28 +02003371 # compact combined diff output can have some patches skipped
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003372 # find which patch (using pathname of result) we are at now;
3373 if ($is_combined) {
3374 while ($to_name ne $diffinfo->{'to_file'}) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02003375 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
3376 format_diff_cc_simplified($diffinfo, @hash_parents) .
3377 "</div>\n"; # class="patch"
3378
3379 $patch_idx++;
3380 $patch_number++;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003381
3382 last if $patch_idx > $#$difftree;
3383 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02003384 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003385 }
Jakub Narebski711fa742007-09-08 21:49:11 +02003386
Jakub Narebski90921742007-06-08 13:27:42 +02003387 # modifies %from, %to hashes
3388 parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
Jakub Narebski5f855052007-05-17 22:54:28 +02003389
Jakub Narebski6d55f052006-11-18 23:35:39 +01003390 # this is first patch for raw difftree line with $patch_idx index
3391 # we index @$difftree array from 0, but number patches from 1
3392 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
Jakub Narebski744d0ac2006-11-08 17:59:41 +01003393 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003394
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003395 # git diff header
3396 #assert($patch_line =~ m/^diff /) if DEBUG;
3397 #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
3398 $patch_number++;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003399 # print "git diff" header
Jakub Narebski90921742007-06-08 13:27:42 +02003400 print format_git_diff_header_line($patch_line, $diffinfo,
3401 \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003402
Jakub Narebski6d55f052006-11-18 23:35:39 +01003403 # print extended diff header
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003404 print "<div class=\"diff extended_header\">\n";
Jakub Narebski6d55f052006-11-18 23:35:39 +01003405 EXTENDED_HEADER:
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003406 while ($patch_line = <$fd>) {
3407 chomp $patch_line;
3408
3409 last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
3410
Jakub Narebski90921742007-06-08 13:27:42 +02003411 print format_extended_diff_header_line($patch_line, $diffinfo,
3412 \%from, \%to);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003413 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003414 print "</div>\n"; # class="diff extended_header"
Jakub Narebskie4e4f822006-08-25 21:04:13 +02003415
Jakub Narebski6d55f052006-11-18 23:35:39 +01003416 # from-file/to-file diff header
Jakub Narebski0bdb28c2007-01-10 00:07:43 +01003417 if (! $patch_line) {
3418 print "</div>\n"; # class="patch"
3419 last PATCH;
3420 }
Jakub Narebski66399ef2007-01-07 02:52:25 +01003421 next PATCH if ($patch_line =~ m/^diff /);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003422 #assert($patch_line =~ m/^---/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003423
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003424 my $last_patch_line = $patch_line;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003425 $patch_line = <$fd>;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003426 chomp $patch_line;
Jakub Narebski90921742007-06-08 13:27:42 +02003427 #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003428
Jakub Narebski90921742007-06-08 13:27:42 +02003429 print format_diff_from_to_header($last_patch_line, $patch_line,
Jakub Narebski91af4ce2007-06-08 13:32:44 +02003430 $diffinfo, \%from, \%to,
3431 @hash_parents);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003432
3433 # the patch itself
3434 LINE:
3435 while ($patch_line = <$fd>) {
3436 chomp $patch_line;
3437
3438 next PATCH if ($patch_line =~ m/^diff /);
3439
Jakub Narebski59e3b142006-11-18 23:35:40 +01003440 print format_diff_line($patch_line, \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003441 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003442
Jakub Narebski6d55f052006-11-18 23:35:39 +01003443 } continue {
3444 print "</div>\n"; # class="patch"
Jakub Narebskieee08902006-08-24 00:15:14 +02003445 }
Jakub Narebskid26c4262007-05-17 00:05:55 +02003446
Jakub Narebskicd030c32007-06-08 13:33:28 +02003447 # for compact combined (--cc) format, with chunk and patch simpliciaction
3448 # patchset might be empty, but there might be unprocessed raw lines
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003449 for (++$patch_idx if $patch_number > 0;
Jakub Narebskicd030c32007-06-08 13:33:28 +02003450 $patch_idx < @$difftree;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003451 ++$patch_idx) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02003452 # read and prepare patch information
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003453 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02003454
3455 # generate anchor for "patch" links in difftree / whatchanged part
3456 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
3457 format_diff_cc_simplified($diffinfo, @hash_parents) .
3458 "</div>\n"; # class="patch"
3459
3460 $patch_number++;
3461 }
3462
Jakub Narebskid26c4262007-05-17 00:05:55 +02003463 if ($patch_number == 0) {
3464 if (@hash_parents > 1) {
3465 print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
3466 } else {
3467 print "<div class=\"diff nodifferences\">No differences found</div>\n";
3468 }
3469 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003470
3471 print "</div>\n"; # class="patchset"
3472}
3473
3474# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3475
Petr Baudise30496d2006-10-24 05:33:17 +02003476sub git_project_list_body {
3477 my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
3478
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08003479 my ($check_forks) = gitweb_check_feature('forks');
Petr Baudise30496d2006-10-24 05:33:17 +02003480
3481 my @projects;
3482 foreach my $pr (@$projlist) {
3483 my (@aa) = git_get_last_activity($pr->{'path'});
3484 unless (@aa) {
3485 next;
3486 }
3487 ($pr->{'age'}, $pr->{'age_string'}) = @aa;
3488 if (!defined $pr->{'descr'}) {
3489 my $descr = git_get_project_description($pr->{'path'}) || "";
Martin Koegler00f429a2007-06-03 17:42:44 +02003490 $pr->{'descr_long'} = to_utf8($descr);
Michael Hendricks55feb122007-07-04 18:36:48 -06003491 $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
Petr Baudise30496d2006-10-24 05:33:17 +02003492 }
3493 if (!defined $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02003494 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
Petr Baudise30496d2006-10-24 05:33:17 +02003495 }
3496 if ($check_forks) {
3497 my $pname = $pr->{'path'};
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003498 if (($pname =~ s/\.git$//) &&
3499 ($pname !~ /\/$/) &&
3500 (-d "$projectroot/$pname")) {
3501 $pr->{'forks'} = "-d $projectroot/$pname";
3502 }
3503 else {
3504 $pr->{'forks'} = 0;
3505 }
Petr Baudise30496d2006-10-24 05:33:17 +02003506 }
3507 push @projects, $pr;
3508 }
3509
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02003510 $order ||= $default_projects_order;
Petr Baudise30496d2006-10-24 05:33:17 +02003511 $from = 0 unless defined $from;
3512 $to = $#projects if (!defined $to || $#projects < $to);
3513
3514 print "<table class=\"project_list\">\n";
3515 unless ($no_header) {
3516 print "<tr>\n";
3517 if ($check_forks) {
3518 print "<th></th>\n";
3519 }
3520 if ($order eq "project") {
3521 @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
3522 print "<th>Project</th>\n";
3523 } else {
3524 print "<th>" .
3525 $cgi->a({-href => href(project=>undef, order=>'project'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003526 -class => "header"}, "Project") .
Petr Baudise30496d2006-10-24 05:33:17 +02003527 "</th>\n";
3528 }
3529 if ($order eq "descr") {
3530 @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
3531 print "<th>Description</th>\n";
3532 } else {
3533 print "<th>" .
3534 $cgi->a({-href => href(project=>undef, order=>'descr'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003535 -class => "header"}, "Description") .
Petr Baudise30496d2006-10-24 05:33:17 +02003536 "</th>\n";
3537 }
3538 if ($order eq "owner") {
3539 @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
3540 print "<th>Owner</th>\n";
3541 } else {
3542 print "<th>" .
3543 $cgi->a({-href => href(project=>undef, order=>'owner'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003544 -class => "header"}, "Owner") .
Petr Baudise30496d2006-10-24 05:33:17 +02003545 "</th>\n";
3546 }
3547 if ($order eq "age") {
3548 @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
3549 print "<th>Last Change</th>\n";
3550 } else {
3551 print "<th>" .
3552 $cgi->a({-href => href(project=>undef, order=>'age'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003553 -class => "header"}, "Last Change") .
Petr Baudise30496d2006-10-24 05:33:17 +02003554 "</th>\n";
3555 }
3556 print "<th></th>\n" .
3557 "</tr>\n";
3558 }
3559 my $alternate = 1;
3560 for (my $i = $from; $i <= $to; $i++) {
3561 my $pr = $projects[$i];
3562 if ($alternate) {
3563 print "<tr class=\"dark\">\n";
3564 } else {
3565 print "<tr class=\"light\">\n";
3566 }
3567 $alternate ^= 1;
3568 if ($check_forks) {
3569 print "<td>";
3570 if ($pr->{'forks'}) {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003571 print "<!-- $pr->{'forks'} -->\n";
Petr Baudise30496d2006-10-24 05:33:17 +02003572 print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
3573 }
3574 print "</td>\n";
3575 }
3576 print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
3577 -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003578 "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
3579 -class => "list", -title => $pr->{'descr_long'}},
3580 esc_html($pr->{'descr'})) . "</td>\n" .
David Symondsd3cd2492007-10-23 11:31:23 +10003581 "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
Petr Baudise30496d2006-10-24 05:33:17 +02003582 print "<td class=\"". age_class($pr->{'age'}) . "\">" .
Jakub Narebski785cdea2007-05-13 12:39:22 +02003583 (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
Petr Baudise30496d2006-10-24 05:33:17 +02003584 "<td class=\"link\">" .
3585 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
Alexandre Julliardfaa1bbf2006-11-15 21:37:50 +01003586 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
Petr Baudise30496d2006-10-24 05:33:17 +02003587 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
3588 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
3589 ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
3590 "</td>\n" .
3591 "</tr>\n";
3592 }
3593 if (defined $extra) {
3594 print "<tr>\n";
3595 if ($check_forks) {
3596 print "<td></td>\n";
3597 }
3598 print "<td colspan=\"5\">$extra</td>\n" .
3599 "</tr>\n";
3600 }
3601 print "</table>\n";
3602}
3603
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003604sub git_shortlog_body {
3605 # uses global variable $project
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00003606 my ($commitlist, $from, $to, $refs, $extra) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05303607
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003608 $from = 0 unless defined $from;
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00003609 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003610
Jakub Narebski591ebf62007-11-19 14:16:11 +01003611 print "<table class=\"shortlog\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003612 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003613 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00003614 my %co = %{$commitlist->[$i]};
3615 my $commit = $co{'id'};
Jakub Narebski847e01f2006-08-14 02:05:47 +02003616 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003617 if ($alternate) {
3618 print "<tr class=\"dark\">\n";
3619 } else {
3620 print "<tr class=\"light\">\n";
3621 }
3622 $alternate ^= 1;
David Symondsce58ec92007-10-23 11:31:22 +10003623 my $author = chop_and_escape_str($co{'author_name'}, 10);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003624 # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
3625 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10003626 "<td><i>" . $author . "</i></td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003627 "<td>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02003628 print format_subject_html($co{'title'}, $co{'title_short'},
3629 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003630 print "</td>\n" .
3631 "<td class=\"link\">" .
Petr Baudis4777b012006-10-24 05:36:10 +02003632 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
Petr Baudis35749ae2006-09-22 03:19:44 +02003633 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
Petr Baudis55ff35c2006-10-06 15:57:52 +02003634 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02003635 my $snapshot_links = format_snapshot_links($commit);
3636 if (defined $snapshot_links) {
3637 print " | " . $snapshot_links;
Petr Baudis55ff35c2006-10-06 15:57:52 +02003638 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05303639 print "</td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003640 "</tr>\n";
3641 }
3642 if (defined $extra) {
3643 print "<tr>\n" .
3644 "<td colspan=\"4\">$extra</td>\n" .
3645 "</tr>\n";
3646 }
3647 print "</table>\n";
3648}
3649
Jakub Narebski581860e2006-08-14 02:09:08 +02003650sub git_history_body {
3651 # Warning: assumes constant type (blob or tree) during history
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003652 my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
Jakub Narebski8be68352006-09-11 00:36:04 +02003653
3654 $from = 0 unless defined $from;
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003655 $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
Jakub Narebski581860e2006-08-14 02:09:08 +02003656
Jakub Narebski591ebf62007-11-19 14:16:11 +01003657 print "<table class=\"history\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003658 my $alternate = 1;
Jakub Narebski8be68352006-09-11 00:36:04 +02003659 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003660 my %co = %{$commitlist->[$i]};
Jakub Narebski581860e2006-08-14 02:09:08 +02003661 if (!%co) {
3662 next;
3663 }
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003664 my $commit = $co{'id'};
Jakub Narebski581860e2006-08-14 02:09:08 +02003665
3666 my $ref = format_ref_marker($refs, $commit);
3667
3668 if ($alternate) {
3669 print "<tr class=\"dark\">\n";
3670 } else {
3671 print "<tr class=\"light\">\n";
3672 }
3673 $alternate ^= 1;
David Symondse076a0e2007-10-22 10:28:03 +10003674 # shortlog uses chop_str($co{'author_name'}, 10)
David Symondsce58ec92007-10-23 11:31:22 +10003675 my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
Jakub Narebski581860e2006-08-14 02:09:08 +02003676 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10003677 "<td><i>" . $author . "</i></td>\n" .
Jakub Narebski581860e2006-08-14 02:09:08 +02003678 "<td>";
3679 # originally git_history used chop_str($co{'title'}, 50)
Jakub Narebski952c65f2006-08-22 16:52:50 +02003680 print format_subject_html($co{'title'}, $co{'title_short'},
3681 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski581860e2006-08-14 02:09:08 +02003682 print "</td>\n" .
3683 "<td class=\"link\">" .
Luben Tuikov6d81c5a2006-09-28 17:21:07 -07003684 $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
3685 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
Jakub Narebski581860e2006-08-14 02:09:08 +02003686
3687 if ($ftype eq 'blob') {
3688 my $blob_current = git_get_hash_by_path($hash_base, $file_name);
3689 my $blob_parent = git_get_hash_by_path($commit, $file_name);
3690 if (defined $blob_current && defined $blob_parent &&
3691 $blob_current ne $blob_parent) {
3692 print " | " .
Jakub Narebski420e92f2006-08-24 23:53:54 +02003693 $cgi->a({-href => href(action=>"blobdiff",
3694 hash=>$blob_current, hash_parent=>$blob_parent,
3695 hash_base=>$hash_base, hash_parent_base=>$commit,
3696 file_name=>$file_name)},
Jakub Narebski581860e2006-08-14 02:09:08 +02003697 "diff to current");
3698 }
3699 }
3700 print "</td>\n" .
3701 "</tr>\n";
3702 }
3703 if (defined $extra) {
3704 print "<tr>\n" .
3705 "<td colspan=\"4\">$extra</td>\n" .
3706 "</tr>\n";
3707 }
3708 print "</table>\n";
3709}
3710
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003711sub git_tags_body {
3712 # uses global variable $project
3713 my ($taglist, $from, $to, $extra) = @_;
3714 $from = 0 unless defined $from;
3715 $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
3716
Jakub Narebski591ebf62007-11-19 14:16:11 +01003717 print "<table class=\"tags\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003718 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003719 for (my $i = $from; $i <= $to; $i++) {
3720 my $entry = $taglist->[$i];
3721 my %tag = %$entry;
Jakub Narebskicd146402006-11-02 20:23:11 +01003722 my $comment = $tag{'subject'};
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003723 my $comment_short;
3724 if (defined $comment) {
3725 $comment_short = chop_str($comment, 30, 5);
3726 }
3727 if ($alternate) {
3728 print "<tr class=\"dark\">\n";
3729 } else {
3730 print "<tr class=\"light\">\n";
3731 }
3732 $alternate ^= 1;
Jakub Narebski27dd1a82007-01-03 22:54:29 +01003733 if (defined $tag{'age'}) {
3734 print "<td><i>$tag{'age'}</i></td>\n";
3735 } else {
3736 print "<td></td>\n";
3737 }
3738 print "<td>" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003739 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
Jakub Narebski63e42202006-08-22 12:38:59 +02003740 -class => "list name"}, esc_html($tag{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003741 "</td>\n" .
3742 "<td>";
3743 if (defined $comment) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02003744 print format_subject_html($comment, $comment_short,
3745 href(action=>"tag", hash=>$tag{'id'}));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003746 }
3747 print "</td>\n" .
3748 "<td class=\"selflink\">";
3749 if ($tag{'type'} eq "tag") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003750 print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003751 } else {
3752 print "&nbsp;";
3753 }
3754 print "</td>\n" .
3755 "<td class=\"link\">" . " | " .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003756 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003757 if ($tag{'reftype'} eq "commit") {
Jakub Narebskibf901f82007-12-15 15:40:28 +01003758 print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
3759 " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003760 } elsif ($tag{'reftype'} eq "blob") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003761 print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003762 }
3763 print "</td>\n" .
3764 "</tr>";
3765 }
3766 if (defined $extra) {
3767 print "<tr>\n" .
3768 "<td colspan=\"5\">$extra</td>\n" .
3769 "</tr>\n";
3770 }
3771 print "</table>\n";
3772}
3773
3774sub git_heads_body {
3775 # uses global variable $project
Jakub Narebski120ddde2006-09-19 14:33:22 +02003776 my ($headlist, $head, $from, $to, $extra) = @_;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003777 $from = 0 unless defined $from;
Jakub Narebski120ddde2006-09-19 14:33:22 +02003778 $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003779
Jakub Narebski591ebf62007-11-19 14:16:11 +01003780 print "<table class=\"heads\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003781 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003782 for (my $i = $from; $i <= $to; $i++) {
Jakub Narebski120ddde2006-09-19 14:33:22 +02003783 my $entry = $headlist->[$i];
Jakub Narebskicd146402006-11-02 20:23:11 +01003784 my %ref = %$entry;
3785 my $curr = $ref{'id'} eq $head;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003786 if ($alternate) {
3787 print "<tr class=\"dark\">\n";
3788 } else {
3789 print "<tr class=\"light\">\n";
3790 }
3791 $alternate ^= 1;
Jakub Narebskicd146402006-11-02 20:23:11 +01003792 print "<td><i>$ref{'age'}</i></td>\n" .
3793 ($curr ? "<td class=\"current_head\">" : "<td>") .
Jakub Narebskibf901f82007-12-15 15:40:28 +01003794 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
Jakub Narebskicd146402006-11-02 20:23:11 +01003795 -class => "list name"},esc_html($ref{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003796 "</td>\n" .
3797 "<td class=\"link\">" .
Jakub Narebskibf901f82007-12-15 15:40:28 +01003798 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
3799 $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
3800 $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003801 "</td>\n" .
3802 "</tr>";
3803 }
3804 if (defined $extra) {
3805 print "<tr>\n" .
3806 "<td colspan=\"3\">$extra</td>\n" .
3807 "</tr>\n";
3808 }
3809 print "</table>\n";
3810}
3811
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003812sub git_search_grep_body {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003813 my ($commitlist, $from, $to, $extra) = @_;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003814 $from = 0 unless defined $from;
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003815 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003816
Jakub Narebski591ebf62007-11-19 14:16:11 +01003817 print "<table class=\"commit_search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003818 my $alternate = 1;
3819 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003820 my %co = %{$commitlist->[$i]};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003821 if (!%co) {
3822 next;
3823 }
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003824 my $commit = $co{'id'};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003825 if ($alternate) {
3826 print "<tr class=\"dark\">\n";
3827 } else {
3828 print "<tr class=\"light\">\n";
3829 }
3830 $alternate ^= 1;
David Symondsce58ec92007-10-23 11:31:22 +10003831 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003832 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10003833 "<td><i>" . $author . "</i></td>\n" .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003834 "<td>" .
Junio C Hamanobe8b9062008-02-22 17:33:47 +01003835 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
3836 -class => "list subject"},
3837 chop_and_escape_str($co{'title'}, 50) . "<br/>");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003838 my $comment = $co{'comment'};
3839 foreach my $line (@$comment) {
Jakub Narebski6dfbb302008-03-02 16:57:14 +01003840 if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
Junio C Hamanobe8b9062008-02-22 17:33:47 +01003841 my ($lead, $match, $trail) = ($1, $2, $3);
Jakub Narebskib8d97d02008-02-25 21:07:57 +01003842 $match = chop_str($match, 70, 5, 'center');
3843 my $contextlen = int((80 - length($match))/2);
3844 $contextlen = 30 if ($contextlen > 30);
3845 $lead = chop_str($lead, $contextlen, 10, 'left');
3846 $trail = chop_str($trail, $contextlen, 10, 'right');
Junio C Hamanobe8b9062008-02-22 17:33:47 +01003847
3848 $lead = esc_html($lead);
3849 $match = esc_html($match);
3850 $trail = esc_html($trail);
3851
3852 print "$lead<span class=\"match\">$match</span>$trail<br />";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003853 }
3854 }
3855 print "</td>\n" .
3856 "<td class=\"link\">" .
3857 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
3858 " | " .
Denis Chengf1fe8f52007-11-26 20:42:06 +08003859 $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
3860 " | " .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003861 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
3862 print "</td>\n" .
3863 "</tr>\n";
3864 }
3865 if (defined $extra) {
3866 print "<tr>\n" .
3867 "<td colspan=\"3\">$extra</td>\n" .
3868 "</tr>\n";
3869 }
3870 print "</table>\n";
3871}
3872
Jakub Narebski717b8312006-07-31 21:22:15 +02003873## ======================================================================
3874## ======================================================================
3875## actions
3876
Jakub Narebski717b8312006-07-31 21:22:15 +02003877sub git_project_list {
Jakub Narebski6326b602006-08-01 02:59:12 +02003878 my $order = $cgi->param('o');
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02003879 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Jakub Narebskie2860ea2006-08-05 13:15:24 +02003880 die_error(undef, "Unknown order parameter");
Jakub Narebski6326b602006-08-01 02:59:12 +02003881 }
3882
Jakub Narebski847e01f2006-08-14 02:05:47 +02003883 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02003884 if (!@list) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02003885 die_error(undef, "No projects found");
Jakub Narebski717b8312006-07-31 21:22:15 +02003886 }
Jakub Narebski6326b602006-08-01 02:59:12 +02003887
Jakub Narebski717b8312006-07-31 21:22:15 +02003888 git_header_html();
3889 if (-f $home_text) {
3890 print "<div class=\"index_include\">\n";
3891 open (my $fd, $home_text);
3892 print <$fd>;
3893 close $fd;
3894 print "</div>\n";
3895 }
Petr Baudise30496d2006-10-24 05:33:17 +02003896 git_project_list_body(\@list, $order);
3897 git_footer_html();
3898}
3899
3900sub git_forks {
3901 my $order = $cgi->param('o');
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02003902 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Petr Baudise30496d2006-10-24 05:33:17 +02003903 die_error(undef, "Unknown order parameter");
Jakub Narebski717b8312006-07-31 21:22:15 +02003904 }
Petr Baudise30496d2006-10-24 05:33:17 +02003905
3906 my @list = git_get_projects_list($project);
3907 if (!@list) {
3908 die_error(undef, "No forks found");
Jakub Narebski717b8312006-07-31 21:22:15 +02003909 }
Petr Baudise30496d2006-10-24 05:33:17 +02003910
3911 git_header_html();
3912 git_print_page_nav('','');
3913 git_print_header_div('summary', "$project forks");
3914 git_project_list_body(\@list, $order);
Jakub Narebski717b8312006-07-31 21:22:15 +02003915 git_footer_html();
3916}
3917
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003918sub git_project_index {
Petr Baudise30496d2006-10-24 05:33:17 +02003919 my @projects = git_get_projects_list($project);
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003920
3921 print $cgi->header(
3922 -type => 'text/plain',
3923 -charset => 'utf-8',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02003924 -content_disposition => 'inline; filename="index.aux"');
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003925
3926 foreach my $pr (@projects) {
3927 if (!exists $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02003928 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003929 }
3930
3931 my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
3932 # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
3933 $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
3934 $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
3935 $path =~ s/ /\+/g;
3936 $owner =~ s/ /\+/g;
3937
3938 print "$path $owner\n";
3939 }
3940}
3941
Kay Sieversede5e102005-08-07 20:23:12 +02003942sub git_summary {
Jakub Narebski847e01f2006-08-14 02:05:47 +02003943 my $descr = git_get_project_description($project) || "none";
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00003944 my %co = parse_commit("HEAD");
Jakub Narebski785cdea2007-05-13 12:39:22 +02003945 my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00003946 my $head = $co{'id'};
Kay Sieversede5e102005-08-07 20:23:12 +02003947
Jakub Narebski1e0cf032006-08-14 02:10:06 +02003948 my $owner = git_get_project_owner($project);
Kay Sieversede5e102005-08-07 20:23:12 +02003949
Jakub Narebskicd146402006-11-02 20:23:11 +01003950 my $refs = git_get_references();
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00003951 # These get_*_list functions return one more to allow us to see if
3952 # there are more ...
3953 my @taglist = git_get_tags_list(16);
3954 my @headlist = git_get_heads_list(16);
Petr Baudise30496d2006-10-24 05:33:17 +02003955 my @forklist;
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08003956 my ($check_forks) = gitweb_check_feature('forks');
3957
3958 if ($check_forks) {
Petr Baudise30496d2006-10-24 05:33:17 +02003959 @forklist = git_get_projects_list($project);
3960 }
Jakub Narebski120ddde2006-09-19 14:33:22 +02003961
Kay Sieversede5e102005-08-07 20:23:12 +02003962 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02003963 git_print_page_nav('summary','', $head);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003964
Kay Sievers19806692005-08-07 20:26:27 +02003965 print "<div class=\"title\">&nbsp;</div>\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01003966 print "<table class=\"projects_list\">\n" .
Kay Sievers40c13812005-11-19 17:41:29 +01003967 "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
Jakub Narebskice312af2007-08-28 16:05:43 +02003968 "<tr><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02003969 if (defined $cd{'rfc2822'}) {
3970 print "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
3971 }
3972
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02003973 # use per project git URL list in $projectroot/$project/cloneurl
3974 # or make project git URL from git base URL and project name
Jakub Narebski19a87212006-08-15 23:03:17 +02003975 my $url_tag = "URL";
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02003976 my @url_list = git_get_project_url_list($project);
3977 @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
3978 foreach my $git_url (@url_list) {
3979 next unless $git_url;
3980 print "<tr><td>$url_tag</td><td>$git_url</td></tr>\n";
Jakub Narebski19a87212006-08-15 23:03:17 +02003981 $url_tag = "";
3982 }
3983 print "</table>\n";
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003984
Petr Baudis447ef092006-10-24 05:23:46 +02003985 if (-s "$projectroot/$project/README.html") {
3986 if (open my $fd, "$projectroot/$project/README.html") {
Jakub Narebski9d066742007-11-19 14:16:12 +01003987 print "<div class=\"title\">readme</div>\n" .
3988 "<div class=\"readme\">\n";
Petr Baudis447ef092006-10-24 05:23:46 +02003989 print $_ while (<$fd>);
Jakub Narebski9d066742007-11-19 14:16:12 +01003990 print "\n</div>\n"; # class="readme"
Petr Baudis447ef092006-10-24 05:23:46 +02003991 close $fd;
3992 }
3993 }
3994
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00003995 # we need to request one more than 16 (0..15) to check if
3996 # those 16 are all
Jakub Narebski785cdea2007-05-13 12:39:22 +02003997 my @commitlist = $head ? parse_commits($head, 17) : ();
3998 if (@commitlist) {
3999 git_print_header_div('shortlog');
4000 git_shortlog_body(\@commitlist, 0, 15, $refs,
4001 $#commitlist <= 15 ? undef :
4002 $cgi->a({-href => href(action=>"shortlog")}, "..."));
4003 }
Kay Sieversede5e102005-08-07 20:23:12 +02004004
Jakub Narebski120ddde2006-09-19 14:33:22 +02004005 if (@taglist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004006 git_print_header_div('tags');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004007 git_tags_body(\@taglist, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004008 $#taglist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004009 $cgi->a({-href => href(action=>"tags")}, "..."));
Kay Sieversede5e102005-08-07 20:23:12 +02004010 }
Kay Sievers0db37972005-08-07 20:24:35 +02004011
Jakub Narebski120ddde2006-09-19 14:33:22 +02004012 if (@headlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004013 git_print_header_div('heads');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004014 git_heads_body(\@headlist, $head, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004015 $#headlist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004016 $cgi->a({-href => href(action=>"heads")}, "..."));
Kay Sievers0db37972005-08-07 20:24:35 +02004017 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004018
Petr Baudise30496d2006-10-24 05:33:17 +02004019 if (@forklist) {
4020 git_print_header_div('forks');
4021 git_project_list_body(\@forklist, undef, 0, 15,
Robert Fitzsimonsaaca9672006-12-22 19:38:12 +00004022 $#forklist <= 15 ? undef :
Petr Baudise30496d2006-10-24 05:33:17 +02004023 $cgi->a({-href => href(action=>"forks")}, "..."),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004024 'noheader');
Petr Baudise30496d2006-10-24 05:33:17 +02004025 }
4026
Kay Sieversede5e102005-08-07 20:23:12 +02004027 git_footer_html();
4028}
4029
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004030sub git_tag {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004031 my $head = git_get_head_hash($project);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004032 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004033 git_print_page_nav('','', $head,undef,$head);
4034 my %tag = parse_tag($hash);
Jakub Narebski198a2a82007-05-12 21:16:34 +02004035
4036 if (! %tag) {
4037 die_error(undef, "Unknown tag object");
4038 }
4039
Jakub Narebski847e01f2006-08-14 02:05:47 +02004040 git_print_header_div('commit', esc_html($tag{'name'}), $hash);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004041 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01004042 "<table class=\"object_header\">\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004043 "<tr>\n" .
4044 "<td>object</td>\n" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004045 "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4046 $tag{'object'}) . "</td>\n" .
4047 "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4048 $tag{'type'}) . "</td>\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004049 "</tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004050 if (defined($tag{'author'})) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004051 my %ad = parse_date($tag{'epoch'}, $tag{'tz'});
Kay Sievers40c13812005-11-19 17:41:29 +01004052 print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004053 print "<tr><td></td><td>" . $ad{'rfc2822'} .
4054 sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) .
4055 "</td></tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004056 }
4057 print "</table>\n\n" .
4058 "</div>\n";
4059 print "<div class=\"page_body\">";
4060 my $comment = $tag{'comment'};
4061 foreach my $line (@$comment) {
Junio C Hamano70022432006-11-24 14:04:01 -08004062 chomp $line;
Jakub Narebski793c4002006-11-24 22:25:50 +01004063 print esc_html($line, -nbsp=>1) . "<br/>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004064 }
4065 print "</div>\n";
4066 git_footer_html();
4067}
4068
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004069sub git_blame2 {
4070 my $fd;
4071 my $ftype;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304072
Aneesh Kumar K.V1d3fc682006-09-01 09:13:32 +05304073 my ($have_blame) = gitweb_check_feature('blame');
4074 if (!$have_blame) {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304075 die_error('403 Permission denied', "Permission denied");
4076 }
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004077 die_error('404 Not Found', "File name not defined") if (!$file_name);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004078 $hash_base ||= git_get_head_hash($project);
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004079 die_error(undef, "Couldn't find base commit") unless ($hash_base);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004080 my %co = parse_commit($hash_base)
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004081 or die_error(undef, "Reading commit failed");
4082 if (!defined $hash) {
4083 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
4084 or die_error(undef, "Error looking up file");
4085 }
4086 $ftype = git_get_type($hash);
4087 if ($ftype !~ "blob") {
Jakub Narebskif73bbb22007-03-27 02:07:11 +02004088 die_error('400 Bad Request', "Object is not a blob");
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004089 }
Junio C Hamano48fd6882006-10-12 00:47:03 -07004090 open ($fd, "-|", git_cmd(), "blame", '-p', '--',
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004091 $file_name, $hash_base)
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004092 or die_error(undef, "Open git-blame failed");
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004093 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004094 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01004095 $cgi->a({-href => href(action=>"blob", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02004096 "blob") .
4097 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01004098 $cgi->a({-href => href(action=>"history", -replay=>1)},
4099 "history") .
Petr Baudiscae18622006-09-22 03:19:41 +02004100 " | " .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004101 $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004102 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004103 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4104 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004105 git_print_page_path($file_name, $ftype, $hash_base);
Luben Tuikov82f930d2006-08-04 15:09:59 -07004106 my @rev_color = (qw(light2 dark2));
Luben Tuikovcc1bf972006-07-23 13:37:53 -07004107 my $num_colors = scalar(@rev_color);
4108 my $current_color = 0;
4109 my $last_rev;
Jakub Narebski59b9f612006-08-22 23:42:53 +02004110 print <<HTML;
4111<div class="page_body">
4112<table class="blame">
4113<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
4114HTML
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004115 my %metainfo = ();
4116 while (1) {
4117 $_ = <$fd>;
4118 last unless defined $_;
Luben Tuikovd15c55a2006-10-11 00:30:05 -07004119 my ($full_rev, $orig_lineno, $lineno, $group_size) =
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004120 /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
4121 if (!exists $metainfo{$full_rev}) {
4122 $metainfo{$full_rev} = {};
4123 }
4124 my $meta = $metainfo{$full_rev};
4125 while (<$fd>) {
4126 last if (s/^\t//);
4127 if (/^(\S+) (.*)$/) {
4128 $meta->{$1} = $2;
4129 }
4130 }
4131 my $data = $_;
Junio C Hamano70022432006-11-24 14:04:01 -08004132 chomp $data;
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004133 my $rev = substr($full_rev, 0, 8);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004134 my $author = $meta->{'author'};
4135 my %date = parse_date($meta->{'author-time'},
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004136 $meta->{'author-tz'});
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004137 my $date = $date{'iso-tz'};
4138 if ($group_size) {
Luben Tuikovcc1bf972006-07-23 13:37:53 -07004139 $current_color = ++$current_color % $num_colors;
4140 }
4141 print "<tr class=\"$rev_color[$current_color]\">\n";
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004142 if ($group_size) {
4143 print "<td class=\"sha1\"";
Luben Tuikov5ad08282006-10-30 12:37:54 -08004144 print " title=\"". esc_html($author) . ", $date\"";
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004145 print " rowspan=\"$group_size\"" if ($group_size > 1);
4146 print ">";
4147 print $cgi->a({-href => href(action=>"commit",
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004148 hash=>$full_rev,
4149 file_name=>$file_name)},
4150 esc_html($rev));
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004151 print "</td>\n";
Luben Tuikov9dc5f8c2006-10-04 00:12:17 -07004152 }
Luben Tuikov244a70e2007-01-04 18:37:45 -08004153 open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
Jakub Narebskif73bbb22007-03-27 02:07:11 +02004154 or die_error(undef, "Open git-rev-parse failed");
Luben Tuikov244a70e2007-01-04 18:37:45 -08004155 my $parent_commit = <$dd>;
4156 close $dd;
4157 chomp($parent_commit);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004158 my $blamed = href(action => 'blame',
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004159 file_name => $meta->{'filename'},
4160 hash_base => $parent_commit);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004161 print "<td class=\"linenr\">";
4162 print $cgi->a({ -href => "$blamed#l$orig_lineno",
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004163 -id => "l$lineno",
4164 -class => "linenr" },
4165 esc_html($lineno));
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004166 print "</td>";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004167 print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
4168 print "</tr>\n";
4169 }
4170 print "</table>\n";
4171 print "</div>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004172 close $fd
4173 or print "Reading blob failed\n";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004174 git_footer_html();
4175}
4176
Florian Forstere34ef622006-06-11 17:45:19 +02004177sub git_blame {
4178 my $fd;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304179
Aneesh Kumar K.V1d3fc682006-09-01 09:13:32 +05304180 my ($have_blame) = gitweb_check_feature('blame');
4181 if (!$have_blame) {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304182 die_error('403 Permission denied', "Permission denied");
4183 }
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004184 die_error('404 Not Found', "File name not defined") if (!$file_name);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004185 $hash_base ||= git_get_head_hash($project);
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004186 die_error(undef, "Couldn't find base commit") unless ($hash_base);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004187 my %co = parse_commit($hash_base)
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004188 or die_error(undef, "Reading commit failed");
Florian Forstere34ef622006-06-11 17:45:19 +02004189 if (!defined $hash) {
4190 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004191 or die_error(undef, "Error lookup file");
Florian Forstere34ef622006-06-11 17:45:19 +02004192 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004193 open ($fd, "-|", git_cmd(), "annotate", '-l', '-t', '-r', $file_name, $hash_base)
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004194 or die_error(undef, "Open git-annotate failed");
Florian Forstere34ef622006-06-11 17:45:19 +02004195 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004196 my $formats_nav =
Jakub Narebski952c65f2006-08-22 16:52:50 +02004197 $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
4198 "blob") .
4199 " | " .
Petr Baudiscae18622006-09-22 03:19:41 +02004200 $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
4201 "history") .
4202 " | " .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004203 $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004204 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004205 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4206 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004207 git_print_page_path($file_name, 'blob', $hash_base);
Florian Forstere34ef622006-06-11 17:45:19 +02004208 print "<div class=\"page_body\">\n";
4209 print <<HTML;
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004210<table class="blame">
Florian Forstere34ef622006-06-11 17:45:19 +02004211 <tr>
4212 <th>Commit</th>
4213 <th>Age</th>
4214 <th>Author</th>
4215 <th>Line</th>
4216 <th>Data</th>
4217 </tr>
4218HTML
4219 my @line_class = (qw(light dark));
4220 my $line_class_len = scalar (@line_class);
4221 my $line_class_num = $#line_class;
4222 while (my $line = <$fd>) {
4223 my $long_rev;
4224 my $short_rev;
4225 my $author;
4226 my $time;
4227 my $lineno;
4228 my $data;
4229 my $age;
4230 my $age_str;
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004231 my $age_class;
Florian Forstere34ef622006-06-11 17:45:19 +02004232
4233 chomp $line;
4234 $line_class_num = ($line_class_num + 1) % $line_class_len;
4235
Jakub Narebski030b5202006-08-26 02:13:05 +02004236 if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) [+-]\d\d\d\d\t(\d+)\)(.*)$/) {
Florian Forstere34ef622006-06-11 17:45:19 +02004237 $long_rev = $1;
4238 $author = $2;
4239 $time = $3;
4240 $lineno = $4;
4241 $data = $5;
4242 } else {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004243 print qq( <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
Florian Forstere34ef622006-06-11 17:45:19 +02004244 next;
4245 }
4246 $short_rev = substr ($long_rev, 0, 8);
4247 $age = time () - $time;
4248 $age_str = age_string ($age);
4249 $age_str =~ s/ /&nbsp;/g;
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004250 $age_class = age_class($age);
Florian Forstere34ef622006-06-11 17:45:19 +02004251 $author = esc_html ($author);
4252 $author =~ s/ /&nbsp;/g;
Jakub Narebskif16db172006-08-06 02:08:31 +02004253
4254 $data = untabify($data);
Florian Forstere34ef622006-06-11 17:45:19 +02004255 $data = esc_html ($data);
Florian Forstere34ef622006-06-11 17:45:19 +02004256
4257 print <<HTML;
4258 <tr class="$line_class[$line_class_num]">
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004259 <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td>
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004260 <td class="$age_class">$age_str</td>
Florian Forstere34ef622006-06-11 17:45:19 +02004261 <td>$author</td>
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004262 <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
4263 <td class="pre">$data</td>
Florian Forstere34ef622006-06-11 17:45:19 +02004264 </tr>
4265HTML
4266 } # while (my $line = <$fd>)
4267 print "</table>\n\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004268 close $fd
4269 or print "Reading blob failed.\n";
Florian Forstere34ef622006-06-11 17:45:19 +02004270 print "</div>";
4271 git_footer_html();
4272}
4273
Kay Sieversede5e102005-08-07 20:23:12 +02004274sub git_tags {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004275 my $head = git_get_head_hash($project);
Kay Sieversede5e102005-08-07 20:23:12 +02004276 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004277 git_print_page_nav('','', $head,undef,$head);
4278 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004279
Jakub Narebskicd146402006-11-02 20:23:11 +01004280 my @tagslist = git_get_tags_list();
4281 if (@tagslist) {
4282 git_tags_body(\@tagslist);
Kay Sieversede5e102005-08-07 20:23:12 +02004283 }
Kay Sieversede5e102005-08-07 20:23:12 +02004284 git_footer_html();
4285}
4286
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02004287sub git_heads {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004288 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02004289 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004290 git_print_page_nav('','', $head,undef,$head);
4291 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004292
Jakub Narebskicd146402006-11-02 20:23:11 +01004293 my @headslist = git_get_heads_list();
4294 if (@headslist) {
4295 git_heads_body(\@headslist, $head);
Kay Sievers0db37972005-08-07 20:24:35 +02004296 }
Kay Sievers0db37972005-08-07 20:24:35 +02004297 git_footer_html();
4298}
4299
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004300sub git_blob_plain {
Jakub Narebskif2e73302006-08-26 19:14:25 +02004301 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004302
Luben Tuikovcff07712006-07-23 13:28:55 -07004303 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004304 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004305 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004306 $hash = git_get_hash_by_path($base, $file_name, "blob")
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004307 or die_error(undef, "Error lookup file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004308 } else {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004309 die_error(undef, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004310 }
Martin Waitz800764c2006-09-16 23:09:02 +02004311 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4312 # blobs defined by non-textual hash id's can be cached
4313 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004314 }
Martin Waitz800764c2006-09-16 23:09:02 +02004315
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004316 my $type = shift;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004317 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Jakub Narebskicfd82662006-08-05 12:56:04 +02004318 or die_error(undef, "Couldn't cat $file_name, $hash");
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004319
Jakub Narebski847e01f2006-08-14 02:05:47 +02004320 $type ||= blob_mimetype($fd, $file_name);
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004321
4322 # save as filename, even when no $file_name is given
4323 my $save_as = "$hash";
4324 if (defined $file_name) {
4325 $save_as = $file_name;
4326 } elsif ($type =~ m/^text\//) {
4327 $save_as .= '.txt';
4328 }
4329
Jakub Narebskif2e73302006-08-26 19:14:25 +02004330 print $cgi->header(
4331 -type => "$type",
4332 -expires=>$expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07004333 -content_disposition => 'inline; filename="' . "$save_as" . '"');
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004334 undef $/;
4335 binmode STDOUT, ':raw';
4336 print <$fd>;
4337 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
4338 $/ = "\n";
4339 close $fd;
4340}
4341
Kay Sievers09bd7892005-08-07 20:21:23 +02004342sub git_blob {
Jakub Narebskif2e73302006-08-26 19:14:25 +02004343 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004344
Luben Tuikovcff07712006-07-23 13:28:55 -07004345 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004346 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004347 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004348 $hash = git_get_hash_by_path($base, $file_name, "blob")
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004349 or die_error(undef, "Error lookup file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004350 } else {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004351 die_error(undef, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004352 }
Martin Waitz800764c2006-09-16 23:09:02 +02004353 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4354 # blobs defined by non-textual hash id's can be cached
4355 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004356 }
Martin Waitz800764c2006-09-16 23:09:02 +02004357
Aneesh Kumar K.V1d3fc682006-09-01 09:13:32 +05304358 my ($have_blame) = gitweb_check_feature('blame');
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004359 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004360 or die_error(undef, "Couldn't cat $file_name, $hash");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004361 my $mimetype = blob_mimetype($fd, $file_name);
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004362 if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004363 close $fd;
4364 return git_blob_plain($mimetype);
4365 }
Jakub Narebski5a4cf332006-12-04 23:47:22 +01004366 # we can have blame only for text/* mimetype
4367 $have_blame &&= ($mimetype =~ m!^text/!);
4368
Jakub Narebskif2e73302006-08-26 19:14:25 +02004369 git_header_html(undef, $expires);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004370 my $formats_nav = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02004371 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Kay Sievers93129442005-10-17 03:27:54 +02004372 if (defined $file_name) {
Florian Forster5996ca02006-06-12 10:31:57 +02004373 if ($have_blame) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004374 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004375 $cgi->a({-href => href(action=>"blame", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02004376 "blame") .
4377 " | ";
Florian Forster5996ca02006-06-12 10:31:57 +02004378 }
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004379 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004380 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02004381 "history") .
4382 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01004383 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02004384 "raw") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004385 " | " .
4386 $cgi->a({-href => href(action=>"blob",
4387 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004388 "HEAD");
Kay Sievers93129442005-10-17 03:27:54 +02004389 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004390 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004391 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
4392 "raw");
Kay Sievers93129442005-10-17 03:27:54 +02004393 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004394 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4395 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02004396 } else {
4397 print "<div class=\"page_nav\">\n" .
4398 "<br/><br/></div>\n" .
4399 "<div class=\"title\">$hash</div>\n";
4400 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004401 git_print_page_path($file_name, "blob", $hash_base);
Kay Sieversc07ad4b2005-08-07 20:22:44 +02004402 print "<div class=\"page_body\">\n";
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004403 if ($mimetype =~ m!^image/!) {
Jakub Narebski5a4cf332006-12-04 23:47:22 +01004404 print qq!<img type="$mimetype"!;
4405 if ($file_name) {
4406 print qq! alt="$file_name" title="$file_name"!;
4407 }
4408 print qq! src="! .
4409 href(action=>"blob_plain", hash=>$hash,
4410 hash_base=>$hash_base, file_name=>$file_name) .
4411 qq!" />\n!;
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004412 } else {
4413 my $nr;
4414 while (my $line = <$fd>) {
4415 chomp $line;
4416 $nr++;
4417 $line = untabify($line);
4418 printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
4419 $nr, $nr, $nr, esc_html($line, -nbsp=>1);
4420 }
Kay Sievers161332a2005-08-07 19:49:46 +02004421 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02004422 close $fd
4423 or print "Reading blob failed.\n";
Kay Sieversfbb592a2005-08-07 20:12:11 +02004424 print "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02004425 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004426}
4427
4428sub git_tree {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004429 if (!defined $hash_base) {
4430 $hash_base = "HEAD";
4431 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02004432 if (!defined $hash) {
Kay Sievers09bd7892005-08-07 20:21:23 +02004433 if (defined $file_name) {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004434 $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
4435 } else {
4436 $hash = $hash_base;
Kay Sievers10dba282005-08-07 20:25:27 +02004437 }
Kay Sieverse925f382005-08-07 20:23:35 +02004438 }
Kay Sievers232ff552005-11-24 16:56:55 +01004439 $/ = "\0";
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004440 open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004441 or die_error(undef, "Open git-ls-tree failed");
Jakub Narebski0881d2d2006-07-30 14:58:11 +02004442 my @entries = map { chomp; $_ } <$fd>;
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004443 close $fd or die_error(undef, "Reading tree failed");
Kay Sievers232ff552005-11-24 16:56:55 +01004444 $/ = "\n";
Kay Sieversd63577d2005-08-07 20:18:13 +02004445
Jakub Narebski847e01f2006-08-14 02:05:47 +02004446 my $refs = git_get_references();
4447 my $ref = format_ref_marker($refs, $hash_base);
Kay Sievers12a88f22005-08-07 20:02:47 +02004448 git_header_html();
Jakub Narebski300454f2006-10-21 17:53:09 +02004449 my $basedir = '';
Aneesh Kumar K.V1d3fc682006-09-01 09:13:32 +05304450 my ($have_blame) = gitweb_check_feature('blame');
Jakub Narebski847e01f2006-08-14 02:05:47 +02004451 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Petr Baudiscae18622006-09-22 03:19:41 +02004452 my @views_nav = ();
4453 if (defined $file_name) {
4454 push @views_nav,
Jakub Narebskia3823e52007-11-01 13:06:29 +01004455 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02004456 "history"),
4457 $cgi->a({-href => href(action=>"tree",
4458 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004459 "HEAD"),
Petr Baudiscae18622006-09-22 03:19:41 +02004460 }
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004461 my $snapshot_links = format_snapshot_links($hash);
4462 if (defined $snapshot_links) {
Petr Baudiscae18622006-09-22 03:19:41 +02004463 # FIXME: Should be available when we have no hash base as well.
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004464 push @views_nav, $snapshot_links;
Petr Baudiscae18622006-09-22 03:19:41 +02004465 }
4466 git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
Jakub Narebski847e01f2006-08-14 02:05:47 +02004467 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
Kay Sieversd63577d2005-08-07 20:18:13 +02004468 } else {
Jakub Narebskifa702002006-08-31 00:35:07 +02004469 undef $hash_base;
Kay Sieversd63577d2005-08-07 20:18:13 +02004470 print "<div class=\"page_nav\">\n";
4471 print "<br/><br/></div>\n";
4472 print "<div class=\"title\">$hash</div>\n";
4473 }
Kay Sievers09bd7892005-08-07 20:21:23 +02004474 if (defined $file_name) {
Jakub Narebski300454f2006-10-21 17:53:09 +02004475 $basedir = $file_name;
4476 if ($basedir ne '' && substr($basedir, -1) ne '/') {
4477 $basedir .= '/';
4478 }
Kay Sievers09bd7892005-08-07 20:21:23 +02004479 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004480 git_print_page_path($file_name, 'tree', $hash_base);
Kay Sieversfbb592a2005-08-07 20:12:11 +02004481 print "<div class=\"page_body\">\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01004482 print "<table class=\"tree\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004483 my $alternate = 1;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02004484 # '..' (top directory) link if possible
4485 if (defined $hash_base &&
4486 defined $file_name && $file_name =~ m![^/]+$!) {
4487 if ($alternate) {
4488 print "<tr class=\"dark\">\n";
4489 } else {
4490 print "<tr class=\"light\">\n";
4491 }
4492 $alternate ^= 1;
4493
4494 my $up = $file_name;
4495 $up =~ s!/?[^/]+$!!;
4496 undef $up unless $up;
4497 # based on git_print_tree_entry
4498 print '<td class="mode">' . mode_str('040000') . "</td>\n";
4499 print '<td class="list">';
4500 print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
4501 file_name=>$up)},
4502 "..");
4503 print "</td>\n";
4504 print "<td class=\"link\"></td>\n";
4505
4506 print "</tr>\n";
4507 }
Kay Sievers161332a2005-08-07 19:49:46 +02004508 foreach my $line (@entries) {
Jakub Narebskicb849b42006-08-31 00:32:15 +02004509 my %t = parse_ls_tree_line($line, -z => 1);
4510
Kay Sieversbddec012005-08-07 20:25:42 +02004511 if ($alternate) {
Kay Sieversc994d622005-08-07 20:27:18 +02004512 print "<tr class=\"dark\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004513 } else {
Kay Sieversc994d622005-08-07 20:27:18 +02004514 print "<tr class=\"light\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004515 }
4516 $alternate ^= 1;
Jakub Narebskicb849b42006-08-31 00:32:15 +02004517
Jakub Narebski300454f2006-10-21 17:53:09 +02004518 git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
Jakub Narebskifa702002006-08-31 00:35:07 +02004519
Kay Sievers42f7eb92005-08-07 20:21:46 +02004520 print "</tr>\n";
Kay Sievers161332a2005-08-07 19:49:46 +02004521 }
Kay Sievers42f7eb92005-08-07 20:21:46 +02004522 print "</table>\n" .
4523 "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02004524 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004525}
4526
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304527sub git_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004528 my @supported_fmts = gitweb_check_feature('snapshot');
Jakub Narebskia7817852007-07-22 23:41:20 +02004529 @supported_fmts = filter_snapshot_fmts(@supported_fmts);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004530
4531 my $format = $cgi->param('sf');
Jakub Narebski3473e7d2007-07-25 01:19:58 +02004532 if (!@supported_fmts) {
4533 die_error('403 Permission denied', "Permission denied");
4534 }
4535 # default to first supported snapshot format
4536 $format ||= $supported_fmts[0];
4537 if ($format !~ m/^[a-z0-9]+$/) {
4538 die_error(undef, "Invalid snapshot format parameter");
4539 } elsif (!exists($known_snapshot_formats{$format})) {
4540 die_error(undef, "Unknown snapshot format");
4541 } elsif (!grep($_ eq $format, @supported_fmts)) {
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004542 die_error(undef, "Unsupported snapshot format");
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304543 }
4544
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304545 if (!defined $hash) {
4546 $hash = git_get_head_hash($project);
4547 }
4548
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004549 my $git_command = git_cmd_str();
Mark Levedahl072570e2007-05-20 11:46:46 -04004550 my $name = $project;
Matthias Lederhofer9a7d9412007-06-07 11:27:08 +02004551 $name =~ s,([^/])/*\.git$,$1,;
4552 $name = basename($name);
4553 my $filename = to_utf8($name);
Mark Levedahl072570e2007-05-20 11:46:46 -04004554 $name =~ s/\047/\047\\\047\047/g;
Mark Levedahl072570e2007-05-20 11:46:46 -04004555 my $cmd;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004556 $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
4557 $cmd = "$git_command archive " .
Junio C Hamano887c5262007-07-25 15:49:55 -07004558 "--format=$known_snapshot_formats{$format}{'format'} " .
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004559 "--prefix=\'$name\'/ $hash";
4560 if (exists $known_snapshot_formats{$format}{'compressor'}) {
4561 $cmd .= ' | ' . join ' ', @{$known_snapshot_formats{$format}{'compressor'}};
Mark Levedahl072570e2007-05-20 11:46:46 -04004562 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304563
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02004564 print $cgi->header(
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004565 -type => $known_snapshot_formats{$format}{'type'},
Luben Tuikova2a3bf72006-09-28 16:51:43 -07004566 -content_disposition => 'inline; filename="' . "$filename" . '"',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02004567 -status => '200 OK');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304568
Mark Levedahl072570e2007-05-20 11:46:46 -04004569 open my $fd, "-|", $cmd
4570 or die_error(undef, "Execute git-archive failed");
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304571 binmode STDOUT, ':raw';
4572 print <$fd>;
4573 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
4574 close $fd;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304575}
4576
Kay Sievers09bd7892005-08-07 20:21:23 +02004577sub git_log {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004578 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02004579 if (!defined $hash) {
Kay Sievers19806692005-08-07 20:26:27 +02004580 $hash = $head;
Kay Sievers0db37972005-08-07 20:24:35 +02004581 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02004582 if (!defined $page) {
4583 $page = 0;
Kay Sieversb87d78d2005-08-07 20:21:04 +02004584 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004585 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02004586
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004587 my @commitlist = parse_commits($hash, 101, (100 * $page));
Kay Sieversea4a6df2005-08-07 20:26:49 +02004588
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004589 my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1)));
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004590
4591 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004592 git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004593
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004594 if (!@commitlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004595 my %co = parse_commit($hash);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004596
Jakub Narebski847e01f2006-08-14 02:05:47 +02004597 git_print_header_div('summary', $project);
Kay Sieverse925f382005-08-07 20:23:35 +02004598 print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
Kay Sievers034df392005-08-07 20:20:07 +02004599 }
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004600 my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
4601 for (my $i = 0; $i <= $to; $i++) {
4602 my %co = %{$commitlist[$i]};
Kay Sieversb87d78d2005-08-07 20:21:04 +02004603 next if !%co;
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004604 my $commit = $co{'id'};
4605 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004606 my %ad = parse_date($co{'author_epoch'});
4607 git_print_header_div('commit',
Jakub Narebski26298b52006-08-10 12:38:47 +02004608 "<span class=\"age\">$co{'age_string'}</span>" .
4609 esc_html($co{'title'}) . $ref,
4610 $commit);
Kay Sievers034df392005-08-07 20:20:07 +02004611 print "<div class=\"title_text\">\n" .
4612 "<div class=\"log_link\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004613 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004614 " | " .
4615 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
Petr Baudis6ef4cb22006-09-22 03:19:48 +02004616 " | " .
Petr Baudisd7267202006-09-22 16:56:43 -07004617 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
Kay Sieverseb282402005-08-07 20:21:34 +02004618 "<br/>\n" .
Kay Sievers034df392005-08-07 20:20:07 +02004619 "</div>\n" .
Kay Sievers40c13812005-11-19 17:41:29 +01004620 "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" .
Jakub Narebskid16d0932006-08-17 11:21:23 +02004621 "</div>\n";
4622
4623 print "<div class=\"log_body\">\n";
Jakub Narebskif2069412006-10-24 13:52:46 +02004624 git_print_log($co{'comment'}, -final_empty_line=> 1);
Kay Sievers09bd7892005-08-07 20:21:23 +02004625 print "</div>\n";
Kay Sievers034df392005-08-07 20:20:07 +02004626 }
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004627 if ($#commitlist >= 100) {
4628 print "<div class=\"page_nav\">\n";
Jakub Narebski7afd77b2007-11-01 13:06:28 +01004629 print $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004630 -accesskey => "n", -title => "Alt-n"}, "next");
4631 print "</div>\n";
4632 }
Kay Sievers034df392005-08-07 20:20:07 +02004633 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004634}
4635
4636sub git_commit {
Jakub Narebski9954f772006-11-18 23:35:41 +01004637 $hash ||= $hash_base || "HEAD";
Jakub Narebski847e01f2006-08-14 02:05:47 +02004638 my %co = parse_commit($hash);
Kay Sievers034df392005-08-07 20:20:07 +02004639 if (!%co) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004640 die_error(undef, "Unknown commit object");
Kay Sieversd63577d2005-08-07 20:18:13 +02004641 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004642 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
4643 my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
Kay Sievers161332a2005-08-07 19:49:46 +02004644
Jakub Narebskic9d193d2006-12-15 21:57:16 +01004645 my $parent = $co{'parent'};
4646 my $parents = $co{'parents'}; # listref
4647
4648 # we need to prepare $formats_nav before any parameter munging
4649 my $formats_nav;
4650 if (!defined $parent) {
4651 # --root commitdiff
4652 $formats_nav .= '(initial)';
4653 } elsif (@$parents == 1) {
4654 # single parent commit
4655 $formats_nav .=
4656 '(parent: ' .
4657 $cgi->a({-href => href(action=>"commit",
4658 hash=>$parent)},
4659 esc_html(substr($parent, 0, 7))) .
4660 ')';
4661 } else {
4662 # merge commit
4663 $formats_nav .=
4664 '(merge: ' .
4665 join(' ', map {
Jakub Narebskif9308a12007-03-23 21:04:31 +01004666 $cgi->a({-href => href(action=>"commit",
Jakub Narebskic9d193d2006-12-15 21:57:16 +01004667 hash=>$_)},
4668 esc_html(substr($_, 0, 7)));
4669 } @$parents ) .
4670 ')';
4671 }
4672
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004673 if (!defined $parent) {
Jakub Narebskib9182982006-07-30 18:28:34 -07004674 $parent = "--root";
Kay Sievers6191f8e2005-08-07 20:19:56 +02004675 }
Jakub Narebski549ab4a2006-12-15 17:53:45 +01004676 my @difftree;
Jakub Narebski208ecb22007-05-07 01:10:08 +02004677 open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
4678 @diff_opts,
4679 (@$parents <= 1 ? $parent : '-c'),
4680 $hash, "--"
4681 or die_error(undef, "Open git-diff-tree failed");
4682 @difftree = map { chomp; $_ } <$fd>;
4683 close $fd or die_error(undef, "Reading git-diff-tree failed");
Kay Sievers11044292005-10-19 03:18:45 +02004684
4685 # non-textual hash id's can be cached
4686 my $expires;
4687 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4688 $expires = "+1d";
4689 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004690 my $refs = git_get_references();
4691 my $ref = format_ref_marker($refs, $co{'id'});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304692
Jakub Narebski594e2122006-07-31 02:21:52 +02004693 git_header_html(undef, $expires);
Petr Baudisa1441542006-10-06 18:59:33 +02004694 git_print_page_nav('commit', '',
Jakub Narebski952c65f2006-08-22 16:52:50 +02004695 $hash, $co{'tree'}, $hash,
Jakub Narebskic9d193d2006-12-15 21:57:16 +01004696 $formats_nav);
Luben Tuikov4f7b34c2006-07-23 13:36:32 -07004697
Kay Sieversb87d78d2005-08-07 20:21:04 +02004698 if (defined $co{'parent'}) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004699 git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02004700 } else {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004701 git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02004702 }
Kay Sievers6191f8e2005-08-07 20:19:56 +02004703 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01004704 "<table class=\"object_header\">\n";
Kay Sievers40c13812005-11-19 17:41:29 +01004705 print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
Kay Sieversbddec012005-08-07 20:25:42 +02004706 "<tr>" .
4707 "<td></td><td> $ad{'rfc2822'}";
Kay Sievers927dcec2005-08-07 20:18:44 +02004708 if ($ad{'hour_local'} < 6) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004709 printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
4710 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
Kay Sieversb87d78d2005-08-07 20:21:04 +02004711 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004712 printf(" (%02d:%02d %s)",
4713 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
Kay Sievers927dcec2005-08-07 20:18:44 +02004714 }
Kay Sieversbddec012005-08-07 20:25:42 +02004715 print "</td>" .
4716 "</tr>\n";
Kay Sievers40c13812005-11-19 17:41:29 +01004717 print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004718 print "<tr><td></td><td> $cd{'rfc2822'}" .
4719 sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) .
4720 "</td></tr>\n";
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004721 print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004722 print "<tr>" .
4723 "<td>tree</td>" .
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004724 "<td class=\"sha1\">" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004725 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
4726 class => "list"}, $co{'tree'}) .
Kay Sievers19806692005-08-07 20:26:27 +02004727 "</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004728 "<td class=\"link\">" .
4729 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
4730 "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004731 my $snapshot_links = format_snapshot_links($hash);
4732 if (defined $snapshot_links) {
4733 print " | " . $snapshot_links;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304734 }
4735 print "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02004736 "</tr>\n";
Jakub Narebski549ab4a2006-12-15 17:53:45 +01004737
Kay Sievers3e029292005-08-07 20:05:15 +02004738 foreach my $par (@$parents) {
Kay Sieversbddec012005-08-07 20:25:42 +02004739 print "<tr>" .
4740 "<td>parent</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004741 "<td class=\"sha1\">" .
4742 $cgi->a({-href => href(action=>"commit", hash=>$par),
4743 class => "list"}, $par) .
4744 "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02004745 "<td class=\"link\">" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004746 $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004747 " | " .
Jakub Narebskif2e60942006-09-03 23:43:03 +02004748 $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
Kay Sieversbddec012005-08-07 20:25:42 +02004749 "</td>" .
4750 "</tr>\n";
Kay Sievers3e029292005-08-07 20:05:15 +02004751 }
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02004752 print "</table>".
Kay Sieversb87d78d2005-08-07 20:21:04 +02004753 "</div>\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02004754
Kay Sieversfbb592a2005-08-07 20:12:11 +02004755 print "<div class=\"page_body\">\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02004756 git_print_log($co{'comment'});
Kay Sievers927dcec2005-08-07 20:18:44 +02004757 print "</div>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004758
Jakub Narebski208ecb22007-05-07 01:10:08 +02004759 git_difftree_body(\@difftree, $hash, @$parents);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004760
Kay Sievers12a88f22005-08-07 20:02:47 +02004761 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004762}
4763
Jakub Narebskica946012006-12-10 13:25:47 +01004764sub git_object {
4765 # object is defined by:
4766 # - hash or hash_base alone
4767 # - hash_base and file_name
4768 my $type;
4769
4770 # - hash or hash_base alone
4771 if ($hash || ($hash_base && !defined $file_name)) {
4772 my $object_id = $hash || $hash_base;
4773
4774 my $git_command = git_cmd_str();
4775 open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null"
4776 or die_error('404 Not Found', "Object does not exist");
4777 $type = <$fd>;
4778 chomp $type;
4779 close $fd
4780 or die_error('404 Not Found', "Object does not exist");
4781
4782 # - hash_base and file_name
4783 } elsif ($hash_base && defined $file_name) {
4784 $file_name =~ s,/+$,,;
4785
4786 system(git_cmd(), "cat-file", '-e', $hash_base) == 0
4787 or die_error('404 Not Found', "Base object does not exist");
4788
4789 # here errors should not hapen
4790 open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
4791 or die_error(undef, "Open git-ls-tree failed");
4792 my $line = <$fd>;
4793 close $fd;
4794
4795 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
4796 unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
4797 die_error('404 Not Found', "File or directory for given base does not exist");
4798 }
4799 $type = $2;
4800 $hash = $3;
4801 } else {
4802 die_error('404 Not Found', "Not enough information to find object");
4803 }
4804
4805 print $cgi->redirect(-uri => href(action=>$type, -full=>1,
4806 hash=>$hash, hash_base=>$hash_base,
4807 file_name=>$file_name),
4808 -status => '302 Found');
4809}
4810
Kay Sievers09bd7892005-08-07 20:21:23 +02004811sub git_blobdiff {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004812 my $format = shift || 'html';
4813
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004814 my $fd;
4815 my @difftree;
4816 my %diffinfo;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004817 my $expires;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004818
4819 # preparing $fd and %diffinfo for git_patchset_body
4820 # new style URI
4821 if (defined $hash_base && defined $hash_parent_base) {
4822 if (defined $file_name) {
4823 # read raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01004824 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
4825 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02004826 "--", (defined $file_parent ? $file_parent : ()), $file_name
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004827 or die_error(undef, "Open git-diff-tree failed");
4828 @difftree = map { chomp; $_ } <$fd>;
4829 close $fd
4830 or die_error(undef, "Reading git-diff-tree failed");
4831 @difftree
4832 or die_error('404 Not Found', "Blob diff not found");
4833
Jakub Narebski0aea3372006-08-27 23:45:26 +02004834 } elsif (defined $hash &&
4835 $hash =~ /[0-9a-fA-F]{40}/) {
4836 # try to find filename from $hash
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004837
4838 # read filtered raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01004839 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
4840 $hash_parent_base, $hash_base, "--"
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004841 or die_error(undef, "Open git-diff-tree failed");
4842 @difftree =
4843 # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
4844 # $hash == to_id
4845 grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
4846 map { chomp; $_ } <$fd>;
4847 close $fd
4848 or die_error(undef, "Reading git-diff-tree failed");
4849 @difftree
4850 or die_error('404 Not Found', "Blob diff not found");
4851
4852 } else {
4853 die_error('404 Not Found', "Missing one of the blob diff parameters");
4854 }
4855
4856 if (@difftree > 1) {
4857 die_error('404 Not Found', "Ambiguous blob diff specification");
4858 }
4859
4860 %diffinfo = parse_difftree_raw_line($difftree[0]);
Jakub Narebski9d301452007-11-01 12:38:08 +01004861 $file_parent ||= $diffinfo{'from_file'} || $file_name;
4862 $file_name ||= $diffinfo{'to_file'};
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004863
4864 $hash_parent ||= $diffinfo{'from_id'};
4865 $hash ||= $diffinfo{'to_id'};
4866
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004867 # non-textual hash id's can be cached
4868 if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
4869 $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
4870 $expires = '+1d';
4871 }
4872
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004873 # open patch output
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004874 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski957d6ea2007-04-05 13:45:41 +02004875 '-p', ($format eq 'html' ? "--full-index" : ()),
4876 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02004877 "--", (defined $file_parent ? $file_parent : ()), $file_name
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004878 or die_error(undef, "Open git-diff-tree failed");
4879 }
4880
4881 # old/legacy style URI
4882 if (!%diffinfo && # if new style URI failed
4883 defined $hash && defined $hash_parent) {
4884 # fake git-diff-tree raw output
4885 $diffinfo{'from_mode'} = $diffinfo{'to_mode'} = "blob";
4886 $diffinfo{'from_id'} = $hash_parent;
4887 $diffinfo{'to_id'} = $hash;
4888 if (defined $file_name) {
4889 if (defined $file_parent) {
4890 $diffinfo{'status'} = '2';
Petr Baudis83915482006-09-24 14:57:40 -07004891 $diffinfo{'from_file'} = $file_parent;
4892 $diffinfo{'to_file'} = $file_name;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004893 } else { # assume not renamed
4894 $diffinfo{'status'} = '1';
Petr Baudis83915482006-09-24 14:57:40 -07004895 $diffinfo{'from_file'} = $file_name;
4896 $diffinfo{'to_file'} = $file_name;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004897 }
4898 } else { # no filename given
4899 $diffinfo{'status'} = '2';
4900 $diffinfo{'from_file'} = $hash_parent;
4901 $diffinfo{'to_file'} = $hash;
4902 }
4903
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004904 # non-textual hash id's can be cached
4905 if ($hash =~ m/^[0-9a-fA-F]{40}$/ &&
4906 $hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
4907 $expires = '+1d';
4908 }
4909
4910 # open patch output
Jakub Narebski957d6ea2007-04-05 13:45:41 +02004911 open $fd, "-|", git_cmd(), "diff", @diff_opts,
4912 '-p', ($format eq 'html' ? "--full-index" : ()),
Jakub Narebski45bd0c82006-10-30 22:29:06 +01004913 $hash_parent, $hash, "--"
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004914 or die_error(undef, "Open git-diff failed");
4915 } else {
4916 die_error('404 Not Found', "Missing one of the blob diff parameters")
4917 unless %diffinfo;
4918 }
4919
4920 # header
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004921 if ($format eq 'html') {
4922 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01004923 $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02004924 "raw");
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004925 git_header_html(undef, $expires);
4926 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
4927 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4928 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
4929 } else {
4930 print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
4931 print "<div class=\"title\">$hash vs $hash_parent</div>\n";
4932 }
4933 if (defined $file_name) {
4934 git_print_page_path($file_name, "blob", $hash_base);
4935 } else {
4936 print "<div class=\"page_path\"></div>\n";
4937 }
4938
4939 } elsif ($format eq 'plain') {
4940 print $cgi->header(
4941 -type => 'text/plain',
4942 -charset => 'utf-8',
4943 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07004944 -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004945
4946 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
4947
Kay Sievers09bd7892005-08-07 20:21:23 +02004948 } else {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004949 die_error(undef, "Unknown blobdiff format");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004950 }
4951
4952 # patch
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004953 if ($format eq 'html') {
4954 print "<div class=\"page_body\">\n";
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004955
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004956 git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
4957 close $fd;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004958
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004959 print "</div>\n"; # class="page_body"
4960 git_footer_html();
4961
4962 } else {
4963 while (my $line = <$fd>) {
Jakub Narebski403d0902006-11-08 11:48:56 +01004964 $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
4965 $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004966
4967 print $line;
4968
4969 last if $line =~ m!^\+\+\+!;
4970 }
4971 local $/ = undef;
4972 print <$fd>;
4973 close $fd;
4974 }
Kay Sievers09bd7892005-08-07 20:21:23 +02004975}
4976
Kay Sievers19806692005-08-07 20:26:27 +02004977sub git_blobdiff_plain {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004978 git_blobdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02004979}
4980
Kay Sievers09bd7892005-08-07 20:21:23 +02004981sub git_commitdiff {
Jakub Narebskieee08902006-08-24 00:15:14 +02004982 my $format = shift || 'html';
Jakub Narebski9954f772006-11-18 23:35:41 +01004983 $hash ||= $hash_base || "HEAD";
Jakub Narebski847e01f2006-08-14 02:05:47 +02004984 my %co = parse_commit($hash);
Kay Sievers034df392005-08-07 20:20:07 +02004985 if (!%co) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004986 die_error(undef, "Unknown commit object");
Kay Sieversd63577d2005-08-07 20:18:13 +02004987 }
Jakub Narebski151602d2006-10-23 00:37:56 +02004988
Jakub Narebskicd030c32007-06-08 13:33:28 +02004989 # choose format for commitdiff for merge
4990 if (! defined $hash_parent && @{$co{'parents'}} > 1) {
4991 $hash_parent = '--cc';
4992 }
4993 # we need to prepare $formats_nav before almost any parameter munging
Jakub Narebski151602d2006-10-23 00:37:56 +02004994 my $formats_nav;
4995 if ($format eq 'html') {
4996 $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01004997 $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
Jakub Narebski151602d2006-10-23 00:37:56 +02004998 "raw");
4999
Jakub Narebskicd030c32007-06-08 13:33:28 +02005000 if (defined $hash_parent &&
5001 $hash_parent ne '-c' && $hash_parent ne '--cc') {
Jakub Narebski151602d2006-10-23 00:37:56 +02005002 # commitdiff with two commits given
5003 my $hash_parent_short = $hash_parent;
5004 if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
5005 $hash_parent_short = substr($hash_parent, 0, 7);
5006 }
5007 $formats_nav .=
Jakub Narebskiada3e1f2007-06-08 13:26:31 +02005008 ' (from';
5009 for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
5010 if ($co{'parents'}[$i] eq $hash_parent) {
5011 $formats_nav .= ' parent ' . ($i+1);
5012 last;
5013 }
5014 }
5015 $formats_nav .= ': ' .
Jakub Narebski151602d2006-10-23 00:37:56 +02005016 $cgi->a({-href => href(action=>"commitdiff",
5017 hash=>$hash_parent)},
5018 esc_html($hash_parent_short)) .
5019 ')';
5020 } elsif (!$co{'parent'}) {
5021 # --root commitdiff
5022 $formats_nav .= ' (initial)';
5023 } elsif (scalar @{$co{'parents'}} == 1) {
5024 # single parent commit
5025 $formats_nav .=
5026 ' (parent: ' .
5027 $cgi->a({-href => href(action=>"commitdiff",
5028 hash=>$co{'parent'})},
5029 esc_html(substr($co{'parent'}, 0, 7))) .
5030 ')';
5031 } else {
5032 # merge commit
Jakub Narebskicd030c32007-06-08 13:33:28 +02005033 if ($hash_parent eq '--cc') {
5034 $formats_nav .= ' | ' .
5035 $cgi->a({-href => href(action=>"commitdiff",
5036 hash=>$hash, hash_parent=>'-c')},
5037 'combined');
5038 } else { # $hash_parent eq '-c'
5039 $formats_nav .= ' | ' .
5040 $cgi->a({-href => href(action=>"commitdiff",
5041 hash=>$hash, hash_parent=>'--cc')},
5042 'compact');
5043 }
Jakub Narebski151602d2006-10-23 00:37:56 +02005044 $formats_nav .=
5045 ' (merge: ' .
5046 join(' ', map {
5047 $cgi->a({-href => href(action=>"commitdiff",
5048 hash=>$_)},
5049 esc_html(substr($_, 0, 7)));
5050 } @{$co{'parents'}} ) .
5051 ')';
5052 }
5053 }
5054
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005055 my $hash_parent_param = $hash_parent;
Jakub Narebskicd030c32007-06-08 13:33:28 +02005056 if (!defined $hash_parent_param) {
5057 # --cc for multiple parents, --root for parentless
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005058 $hash_parent_param =
Jakub Narebskicd030c32007-06-08 13:33:28 +02005059 @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
Kay Sieversbddec012005-08-07 20:25:42 +02005060 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005061
5062 # read commitdiff
5063 my $fd;
5064 my @difftree;
Jakub Narebskieee08902006-08-24 00:15:14 +02005065 if ($format eq 'html') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005066 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski45bd0c82006-10-30 22:29:06 +01005067 "--no-commit-id", "--patch-with-raw", "--full-index",
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005068 $hash_parent_param, $hash, "--"
Jakub Narebskieee08902006-08-24 00:15:14 +02005069 or die_error(undef, "Open git-diff-tree failed");
5070
Jakub Narebski04408c32006-11-18 23:35:38 +01005071 while (my $line = <$fd>) {
5072 chomp $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02005073 # empty line ends raw part of diff-tree output
5074 last unless $line;
Jakub Narebski493e01d2007-05-07 01:10:06 +02005075 push @difftree, scalar parse_difftree_raw_line($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02005076 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005077
Jakub Narebskieee08902006-08-24 00:15:14 +02005078 } elsif ($format eq 'plain') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005079 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005080 '-p', $hash_parent_param, $hash, "--"
Jakub Narebskieee08902006-08-24 00:15:14 +02005081 or die_error(undef, "Open git-diff-tree failed");
Jakub Narebski157e43b2006-08-24 19:34:36 +02005082
Jakub Narebskieee08902006-08-24 00:15:14 +02005083 } else {
5084 die_error(undef, "Unknown commitdiff format");
5085 }
Kay Sievers4c02e3c2005-08-07 19:52:52 +02005086
Kay Sievers11044292005-10-19 03:18:45 +02005087 # non-textual hash id's can be cached
5088 my $expires;
5089 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5090 $expires = "+1d";
5091 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005092
Jakub Narebskieee08902006-08-24 00:15:14 +02005093 # write commit message
5094 if ($format eq 'html') {
5095 my $refs = git_get_references();
5096 my $ref = format_ref_marker($refs, $co{'id'});
Kay Sievers19806692005-08-07 20:26:27 +02005097
Jakub Narebskieee08902006-08-24 00:15:14 +02005098 git_header_html(undef, $expires);
5099 git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
5100 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
Jakub Narebski6fd92a22006-08-28 14:48:12 +02005101 git_print_authorship(\%co);
Jakub Narebskieee08902006-08-24 00:15:14 +02005102 print "<div class=\"page_body\">\n";
Jakub Narebski82560982006-10-24 13:55:33 +02005103 if (@{$co{'comment'}} > 1) {
5104 print "<div class=\"log\">\n";
5105 git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
5106 print "</div>\n"; # class="log"
5107 }
Kay Sievers1b1cd422005-08-07 20:28:01 +02005108
Jakub Narebskieee08902006-08-24 00:15:14 +02005109 } elsif ($format eq 'plain') {
5110 my $refs = git_get_references("tags");
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005111 my $tagname = git_get_rev_name_tags($hash);
Jakub Narebskieee08902006-08-24 00:15:14 +02005112 my $filename = basename($project) . "-$hash.patch";
5113
5114 print $cgi->header(
5115 -type => 'text/plain',
5116 -charset => 'utf-8',
5117 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07005118 -content_disposition => 'inline; filename="' . "$filename" . '"');
Jakub Narebskieee08902006-08-24 00:15:14 +02005119 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
Yasushi SHOJI77202242008-01-29 21:16:02 +09005120 print "From: " . to_utf8($co{'author'}) . "\n";
5121 print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
5122 print "Subject: " . to_utf8($co{'title'}) . "\n";
5123
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005124 print "X-Git-Tag: $tagname\n" if $tagname;
Jakub Narebskieee08902006-08-24 00:15:14 +02005125 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005126
Jakub Narebskieee08902006-08-24 00:15:14 +02005127 foreach my $line (@{$co{'comment'}}) {
Yasushi SHOJI77202242008-01-29 21:16:02 +09005128 print to_utf8($line) . "\n";
Kay Sievers19806692005-08-07 20:26:27 +02005129 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005130 print "---\n\n";
Kay Sievers19806692005-08-07 20:26:27 +02005131 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005132
5133 # write patch
5134 if ($format eq 'html') {
Jakub Narebskicd030c32007-06-08 13:33:28 +02005135 my $use_parents = !defined $hash_parent ||
5136 $hash_parent eq '-c' || $hash_parent eq '--cc';
5137 git_difftree_body(\@difftree, $hash,
5138 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebskib4657e72006-08-28 14:48:14 +02005139 print "<br/>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02005140
Jakub Narebskicd030c32007-06-08 13:33:28 +02005141 git_patchset_body($fd, \@difftree, $hash,
5142 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebski157e43b2006-08-24 19:34:36 +02005143 close $fd;
Jakub Narebskieee08902006-08-24 00:15:14 +02005144 print "</div>\n"; # class="page_body"
5145 git_footer_html();
5146
5147 } elsif ($format eq 'plain') {
5148 local $/ = undef;
5149 print <$fd>;
5150 close $fd
5151 or print "Reading git-diff-tree failed\n";
5152 }
5153}
5154
5155sub git_commitdiff_plain {
5156 git_commitdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02005157}
5158
Kay Sievers09bd7892005-08-07 20:21:23 +02005159sub git_history {
Luben Tuikovc6e1d9e2006-07-23 13:26:30 -07005160 if (!defined $hash_base) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005161 $hash_base = git_get_head_hash($project);
Kay Sievers09bd7892005-08-07 20:21:23 +02005162 }
Jakub Narebski8be68352006-09-11 00:36:04 +02005163 if (!defined $page) {
5164 $page = 0;
5165 }
Luben Tuikov63433102006-07-23 13:31:15 -07005166 my $ftype;
Jakub Narebski847e01f2006-08-14 02:05:47 +02005167 my %co = parse_commit($hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02005168 if (!%co) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02005169 die_error(undef, "Unknown commit object");
Kay Sievers2ae100d2005-08-07 20:17:00 +02005170 }
Jakub Narebski8be68352006-09-11 00:36:04 +02005171
Jakub Narebski847e01f2006-08-14 02:05:47 +02005172 my $refs = git_get_references();
Jakub Narebski8be68352006-09-11 00:36:04 +02005173 my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
5174
Luben Tuikov93d5f062006-07-23 13:30:08 -07005175 if (!defined $hash && defined $file_name) {
5176 $hash = git_get_hash_by_path($hash_base, $file_name);
5177 }
Luben Tuikovcff07712006-07-23 13:28:55 -07005178 if (defined $hash) {
Luben Tuikov63433102006-07-23 13:31:15 -07005179 $ftype = git_get_type($hash);
Luben Tuikovcff07712006-07-23 13:28:55 -07005180 }
Kay Sievers10dba282005-08-07 20:25:27 +02005181
Jakub Narebski311e5522008-02-26 13:22:06 +01005182 my @commitlist = parse_commits($hash_base, 101, (100 * $page), $file_name, "--full-history");
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005183
Jakub Narebski8be68352006-09-11 00:36:04 +02005184 my $paging_nav = '';
5185 if ($page > 0) {
5186 $paging_nav .=
5187 $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
5188 file_name=>$file_name)},
5189 "first");
5190 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005191 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski8be68352006-09-11 00:36:04 +02005192 -accesskey => "p", -title => "Alt-p"}, "prev");
5193 } else {
5194 $paging_nav .= "first";
5195 $paging_nav .= " &sdot; prev";
5196 }
Jakub Narebski8be68352006-09-11 00:36:04 +02005197 my $next_link = '';
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005198 if ($#commitlist >= 100) {
Jakub Narebski8be68352006-09-11 00:36:04 +02005199 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005200 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005201 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005202 $paging_nav .= " &sdot; $next_link";
5203 } else {
5204 $paging_nav .= " &sdot; next";
Jakub Narebski8be68352006-09-11 00:36:04 +02005205 }
Jakub Narebski581860e2006-08-14 02:09:08 +02005206
Jakub Narebski8be68352006-09-11 00:36:04 +02005207 git_header_html();
5208 git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
5209 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
5210 git_print_page_path($file_name, $ftype, $hash_base);
5211
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005212 git_history_body(\@commitlist, 0, 99,
Jakub Narebski8be68352006-09-11 00:36:04 +02005213 $refs, $hash_base, $ftype, $next_link);
5214
Kay Sieversd51e9022005-08-07 20:16:07 +02005215 git_footer_html();
Kay Sievers161332a2005-08-07 19:49:46 +02005216}
Kay Sievers19806692005-08-07 20:26:27 +02005217
5218sub git_search {
Robert Fitzsimons6be93512006-12-23 03:35:16 +00005219 my ($have_search) = gitweb_check_feature('search');
5220 if (!$have_search) {
5221 die_error('403 Permission denied', "Permission denied");
5222 }
Kay Sievers19806692005-08-07 20:26:27 +02005223 if (!defined $searchtext) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02005224 die_error(undef, "Text field empty");
Kay Sievers19806692005-08-07 20:26:27 +02005225 }
5226 if (!defined $hash) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005227 $hash = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02005228 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005229 my %co = parse_commit($hash);
Kay Sievers19806692005-08-07 20:26:27 +02005230 if (!%co) {
Jakub Narebskicac4bd92006-08-05 13:13:53 +02005231 die_error(undef, "Unknown commit object");
Kay Sievers19806692005-08-07 20:26:27 +02005232 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005233 if (!defined $page) {
5234 $page = 0;
5235 }
Jakub Narebski04f7a942006-09-11 00:29:27 +02005236
Petr Baudis88ad7292006-10-24 05:15:46 +02005237 $searchtype ||= 'commit';
5238 if ($searchtype eq 'pickaxe') {
Jakub Narebski04f7a942006-09-11 00:29:27 +02005239 # pickaxe may take all resources of your box and run for several minutes
5240 # with every query - so decide by yourself how public you make this feature
5241 my ($have_pickaxe) = gitweb_check_feature('pickaxe');
5242 if (!$have_pickaxe) {
5243 die_error('403 Permission denied', "Permission denied");
5244 }
Kay Sieversc994d622005-08-07 20:27:18 +02005245 }
Petr Baudise7738552007-05-17 04:31:12 +02005246 if ($searchtype eq 'grep') {
5247 my ($have_grep) = gitweb_check_feature('grep');
5248 if (!$have_grep) {
5249 die_error('403 Permission denied', "Permission denied");
5250 }
5251 }
Petr Baudis88ad7292006-10-24 05:15:46 +02005252
Kay Sievers19806692005-08-07 20:26:27 +02005253 git_header_html();
Kay Sievers19806692005-08-07 20:26:27 +02005254
Petr Baudis88ad7292006-10-24 05:15:46 +02005255 if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
Robert Fitzsimons8e574fb2006-12-23 03:35:14 +00005256 my $greptype;
5257 if ($searchtype eq 'commit') {
5258 $greptype = "--grep=";
5259 } elsif ($searchtype eq 'author') {
5260 $greptype = "--author=";
5261 } elsif ($searchtype eq 'committer') {
5262 $greptype = "--committer=";
5263 }
Jakub Narebski0270cd02008-02-26 13:22:07 +01005264 $greptype .= $searchtext;
5265 my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
Petr Baudis0e559912008-02-26 13:22:08 +01005266 $greptype, '--regexp-ignore-case',
5267 $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005268
5269 my $paging_nav = '';
5270 if ($page > 0) {
5271 $paging_nav .=
5272 $cgi->a({-href => href(action=>"search", hash=>$hash,
Jakub Narebski0270cd02008-02-26 13:22:07 +01005273 searchtext=>$searchtext,
5274 searchtype=>$searchtype)},
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005275 "first");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005276 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005277 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005278 -accesskey => "p", -title => "Alt-p"}, "prev");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005279 } else {
5280 $paging_nav .= "first";
5281 $paging_nav .= " &sdot; prev";
5282 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005283 my $next_link = '';
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00005284 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005285 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005286 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005287 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005288 $paging_nav .= " &sdot; $next_link";
5289 } else {
5290 $paging_nav .= " &sdot; next";
5291 }
5292
5293 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005294 }
5295
5296 git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
5297 git_print_header_div('commit', esc_html($co{'title'}), $hash);
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00005298 git_search_grep_body(\@commitlist, 0, 99, $next_link);
Kay Sieversc994d622005-08-07 20:27:18 +02005299 }
5300
Petr Baudis88ad7292006-10-24 05:15:46 +02005301 if ($searchtype eq 'pickaxe') {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005302 git_print_page_nav('','', $hash,$co{'tree'},$hash);
5303 git_print_header_div('commit', esc_html($co{'title'}), $hash);
5304
Jakub Narebski591ebf62007-11-19 14:16:11 +01005305 print "<table class=\"pickaxe search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005306 my $alternate = 1;
Kay Sieversc994d622005-08-07 20:27:18 +02005307 $/ = "\n";
Jakub Narebskic582aba2008-03-05 09:31:55 +01005308 open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
5309 '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
5310 ($search_use_regexp ? '--pickaxe-regex' : ());
Kay Sieversc994d622005-08-07 20:27:18 +02005311 undef %co;
5312 my @files;
5313 while (my $line = <$fd>) {
Jakub Narebskic582aba2008-03-05 09:31:55 +01005314 chomp $line;
5315 next unless $line;
5316
5317 my %set = parse_difftree_raw_line($line);
5318 if (defined $set{'commit'}) {
5319 # finish previous commit
Kay Sieversc994d622005-08-07 20:27:18 +02005320 if (%co) {
Kay Sieversc994d622005-08-07 20:27:18 +02005321 print "</td>\n" .
5322 "<td class=\"link\">" .
Martin Waitz756d2f02006-08-17 00:28:36 +02005323 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005324 " | " .
5325 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
Kay Sieversc994d622005-08-07 20:27:18 +02005326 print "</td>\n" .
5327 "</tr>\n";
5328 }
Jakub Narebskic582aba2008-03-05 09:31:55 +01005329
5330 if ($alternate) {
5331 print "<tr class=\"dark\">\n";
5332 } else {
5333 print "<tr class=\"light\">\n";
5334 }
5335 $alternate ^= 1;
5336 %co = parse_commit($set{'commit'});
5337 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
5338 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
5339 "<td><i>$author</i></td>\n" .
5340 "<td>" .
5341 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
5342 -class => "list subject"},
5343 chop_and_escape_str($co{'title'}, 50) . "<br/>");
5344 } elsif (defined $set{'to_id'}) {
5345 next if ($set{'to_id'} =~ m/^0{40}$/);
5346
5347 print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
5348 hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
5349 -class => "list"},
5350 "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
5351 "<br/>\n";
Kay Sieversc994d622005-08-07 20:27:18 +02005352 }
5353 }
5354 close $fd;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005355
Jakub Narebskic582aba2008-03-05 09:31:55 +01005356 # finish last commit (warning: repetition!)
5357 if (%co) {
5358 print "</td>\n" .
5359 "<td class=\"link\">" .
5360 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
5361 " | " .
5362 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
5363 print "</td>\n" .
5364 "</tr>\n";
5365 }
5366
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005367 print "</table>\n";
Kay Sievers19806692005-08-07 20:26:27 +02005368 }
Petr Baudise7738552007-05-17 04:31:12 +02005369
5370 if ($searchtype eq 'grep') {
5371 git_print_page_nav('','', $hash,$co{'tree'},$hash);
5372 git_print_header_div('commit', esc_html($co{'title'}), $hash);
5373
Jakub Narebski591ebf62007-11-19 14:16:11 +01005374 print "<table class=\"grep_search\">\n";
Petr Baudise7738552007-05-17 04:31:12 +02005375 my $alternate = 1;
5376 my $matches = 0;
5377 $/ = "\n";
Petr Baudis0e559912008-02-26 13:22:08 +01005378 open my $fd, "-|", git_cmd(), 'grep', '-n',
5379 $search_use_regexp ? ('-E', '-i') : '-F',
5380 $searchtext, $co{'tree'};
Petr Baudise7738552007-05-17 04:31:12 +02005381 my $lastfile = '';
5382 while (my $line = <$fd>) {
5383 chomp $line;
5384 my ($file, $lno, $ltext, $binary);
5385 last if ($matches++ > 1000);
5386 if ($line =~ /^Binary file (.+) matches$/) {
5387 $file = $1;
5388 $binary = 1;
5389 } else {
5390 (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
5391 }
5392 if ($file ne $lastfile) {
5393 $lastfile and print "</td></tr>\n";
5394 if ($alternate++) {
5395 print "<tr class=\"dark\">\n";
5396 } else {
5397 print "<tr class=\"light\">\n";
5398 }
5399 print "<td class=\"list\">".
5400 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
5401 file_name=>"$file"),
5402 -class => "list"}, esc_path($file));
5403 print "</td><td>\n";
5404 $lastfile = $file;
5405 }
5406 if ($binary) {
5407 print "<div class=\"binary\">Binary file</div>\n";
5408 } else {
5409 $ltext = untabify($ltext);
Petr Baudis0e559912008-02-26 13:22:08 +01005410 if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
Petr Baudise7738552007-05-17 04:31:12 +02005411 $ltext = esc_html($1, -nbsp=>1);
5412 $ltext .= '<span class="match">';
5413 $ltext .= esc_html($2, -nbsp=>1);
5414 $ltext .= '</span>';
5415 $ltext .= esc_html($3, -nbsp=>1);
5416 } else {
5417 $ltext = esc_html($ltext, -nbsp=>1);
5418 }
5419 print "<div class=\"pre\">" .
5420 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
5421 file_name=>"$file").'#l'.$lno,
5422 -class => "linenr"}, sprintf('%4i', $lno))
5423 . ' ' . $ltext . "</div>\n";
5424 }
5425 }
5426 if ($lastfile) {
5427 print "</td></tr>\n";
5428 if ($matches > 1000) {
5429 print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
5430 }
5431 } else {
5432 print "<div class=\"diff nodifferences\">No matches found</div>\n";
5433 }
5434 close $fd;
5435
5436 print "</table>\n";
5437 }
Kay Sievers19806692005-08-07 20:26:27 +02005438 git_footer_html();
5439}
5440
Petr Baudis88ad7292006-10-24 05:15:46 +02005441sub git_search_help {
5442 git_header_html();
5443 git_print_page_nav('','', $hash,$hash,$hash);
5444 print <<EOT;
Petr Baudis0e559912008-02-26 13:22:08 +01005445<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
5446regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
5447the pattern entered is recognized as the POSIX extended
5448<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
5449insensitive).</p>
Petr Baudis88ad7292006-10-24 05:15:46 +02005450<dl>
5451<dt><b>commit</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005452<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02005453EOT
5454 my ($have_grep) = gitweb_check_feature('grep');
5455 if ($have_grep) {
5456 print <<EOT;
5457<dt><b>grep</b></dt>
5458<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
Petr Baudis0e559912008-02-26 13:22:08 +01005459 a different one) are searched for the given pattern. On large trees, this search can take
5460a while and put some strain on the server, so please use it with some consideration. Note that
5461due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
5462case-sensitive.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02005463EOT
5464 }
5465 print <<EOT;
Petr Baudis88ad7292006-10-24 05:15:46 +02005466<dt><b>author</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005467<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given pattern.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02005468<dt><b>committer</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005469<dd>Name and e-mail of the committer and date of commit will be scanned for the given pattern.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02005470EOT
5471 my ($have_pickaxe) = gitweb_check_feature('pickaxe');
5472 if ($have_pickaxe) {
5473 print <<EOT;
5474<dt><b>pickaxe</b></dt>
5475<dd>All commits that caused the string to appear or disappear from any file (changes that
5476added, removed or "modified" the string) will be listed. This search can take a while and
Petr Baudis0e559912008-02-26 13:22:08 +01005477takes a lot of strain on the server, so please use it wisely. Note that since you may be
5478interested even in changes just changing the case as well, this search is case sensitive.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02005479EOT
5480 }
5481 print "</dl>\n";
5482 git_footer_html();
5483}
5484
Kay Sievers19806692005-08-07 20:26:27 +02005485sub git_shortlog {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005486 my $head = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02005487 if (!defined $hash) {
5488 $hash = $head;
5489 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02005490 if (!defined $page) {
5491 $page = 0;
5492 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005493 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02005494
Robert Fitzsimonsd2229842007-01-03 20:25:55 +00005495 my @commitlist = parse_commits($hash, 101, (100 * $page));
Kay Sieversea4a6df2005-08-07 20:26:49 +02005496
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005497 my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1)));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005498 my $next_link = '';
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005499 if ($#commitlist >= 100) {
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005500 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005501 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005502 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005503 }
5504
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005505 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005506 git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
5507 git_print_header_div('summary', $project);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005508
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005509 git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005510
Kay Sievers19806692005-08-07 20:26:27 +02005511 git_footer_html();
5512}
Jakub Narebski717b8312006-07-31 21:22:15 +02005513
5514## ......................................................................
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005515## feeds (RSS, Atom; OPML)
Jakub Narebski717b8312006-07-31 21:22:15 +02005516
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005517sub git_feed {
5518 my $format = shift || 'atom';
5519 my ($have_blame) = gitweb_check_feature('blame');
5520
5521 # Atom: http://www.atomenabled.org/developers/syndication/
5522 # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
5523 if ($format ne 'rss' && $format ne 'atom') {
5524 die_error(undef, "Unknown web feed format");
5525 }
5526
5527 # log/feed of current (HEAD) branch, log of given branch, history of file/directory
5528 my $head = $hash || 'HEAD';
Jakub Narebski311e5522008-02-26 13:22:06 +01005529 my @commitlist = parse_commits($head, 150, 0, $file_name);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005530
5531 my %latest_commit;
5532 my %latest_date;
5533 my $content_type = "application/$format+xml";
5534 if (defined $cgi->http('HTTP_ACCEPT') &&
5535 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
5536 # browser (feed reader) prefers text/xml
5537 $content_type = 'text/xml';
5538 }
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00005539 if (defined($commitlist[0])) {
5540 %latest_commit = %{$commitlist[0]};
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01005541 %latest_date = parse_date($latest_commit{'author_epoch'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005542 print $cgi->header(
5543 -type => $content_type,
5544 -charset => 'utf-8',
5545 -last_modified => $latest_date{'rfc2822'});
5546 } else {
5547 print $cgi->header(
5548 -type => $content_type,
5549 -charset => 'utf-8');
5550 }
5551
5552 # Optimization: skip generating the body if client asks only
5553 # for Last-Modified date.
5554 return if ($cgi->request_method() eq 'HEAD');
5555
5556 # header variables
5557 my $title = "$site_name - $project/$action";
5558 my $feed_type = 'log';
5559 if (defined $hash) {
5560 $title .= " - '$hash'";
5561 $feed_type = 'branch log';
5562 if (defined $file_name) {
5563 $title .= " :: $file_name";
5564 $feed_type = 'history';
5565 }
5566 } elsif (defined $file_name) {
5567 $title .= " - $file_name";
5568 $feed_type = 'history';
5569 }
5570 $title .= " $feed_type";
5571 my $descr = git_get_project_description($project);
5572 if (defined $descr) {
5573 $descr = esc_html($descr);
5574 } else {
5575 $descr = "$project " .
5576 ($format eq 'rss' ? 'RSS' : 'Atom') .
5577 " feed";
5578 }
5579 my $owner = git_get_project_owner($project);
5580 $owner = esc_html($owner);
5581
5582 #header
5583 my $alt_url;
5584 if (defined $file_name) {
5585 $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
5586 } elsif (defined $hash) {
5587 $alt_url = href(-full=>1, action=>"log", hash=>$hash);
5588 } else {
5589 $alt_url = href(-full=>1, action=>"summary");
5590 }
5591 print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
5592 if ($format eq 'rss') {
5593 print <<XML;
Jakub Narebski59b9f612006-08-22 23:42:53 +02005594<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
5595<channel>
Jakub Narebski59b9f612006-08-22 23:42:53 +02005596XML
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005597 print "<title>$title</title>\n" .
5598 "<link>$alt_url</link>\n" .
5599 "<description>$descr</description>\n" .
5600 "<language>en</language>\n";
5601 } elsif ($format eq 'atom') {
5602 print <<XML;
5603<feed xmlns="http://www.w3.org/2005/Atom">
5604XML
5605 print "<title>$title</title>\n" .
5606 "<subtitle>$descr</subtitle>\n" .
5607 '<link rel="alternate" type="text/html" href="' .
5608 $alt_url . '" />' . "\n" .
5609 '<link rel="self" type="' . $content_type . '" href="' .
5610 $cgi->self_url() . '" />' . "\n" .
5611 "<id>" . href(-full=>1) . "</id>\n" .
5612 # use project owner for feed author
5613 "<author><name>$owner</name></author>\n";
5614 if (defined $favicon) {
5615 print "<icon>" . esc_url($favicon) . "</icon>\n";
5616 }
5617 if (defined $logo_url) {
5618 # not twice as wide as tall: 72 x 27 pixels
Jakub Narebskie1147262006-12-04 14:09:43 +01005619 print "<logo>" . esc_url($logo) . "</logo>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005620 }
5621 if (! %latest_date) {
5622 # dummy date to keep the feed valid until commits trickle in:
5623 print "<updated>1970-01-01T00:00:00Z</updated>\n";
5624 } else {
5625 print "<updated>$latest_date{'iso-8601'}</updated>\n";
5626 }
5627 }
Jakub Narebski717b8312006-07-31 21:22:15 +02005628
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005629 # contents
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00005630 for (my $i = 0; $i <= $#commitlist; $i++) {
5631 my %co = %{$commitlist[$i]};
5632 my $commit = $co{'id'};
Jakub Narebski717b8312006-07-31 21:22:15 +02005633 # we read 150, we always show 30 and the ones more recent than 48 hours
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01005634 if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02005635 last;
5636 }
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01005637 my %cd = parse_date($co{'author_epoch'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005638
5639 # get list of changed files
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00005640 open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskic906b182007-05-19 02:47:51 +02005641 $co{'parent'} || "--root",
5642 $co{'id'}, "--", (defined $file_name ? $file_name : ())
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02005643 or next;
Jakub Narebski717b8312006-07-31 21:22:15 +02005644 my @difftree = map { chomp; $_ } <$fd>;
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02005645 close $fd
5646 or next;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005647
5648 # print element (entry, item)
Florian La Rochee62a6412008-02-03 12:38:46 +01005649 my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005650 if ($format eq 'rss') {
5651 print "<item>\n" .
5652 "<title>" . esc_html($co{'title'}) . "</title>\n" .
5653 "<author>" . esc_html($co{'author'}) . "</author>\n" .
5654 "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
5655 "<guid isPermaLink=\"true\">$co_url</guid>\n" .
5656 "<link>$co_url</link>\n" .
5657 "<description>" . esc_html($co{'title'}) . "</description>\n" .
5658 "<content:encoded>" .
5659 "<![CDATA[\n";
5660 } elsif ($format eq 'atom') {
5661 print "<entry>\n" .
5662 "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
5663 "<updated>$cd{'iso-8601'}</updated>\n" .
Jakub Narebskiab23c192006-11-25 15:54:33 +01005664 "<author>\n" .
5665 " <name>" . esc_html($co{'author_name'}) . "</name>\n";
5666 if ($co{'author_email'}) {
5667 print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
5668 }
5669 print "</author>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005670 # use committer for contributor
Jakub Narebskiab23c192006-11-25 15:54:33 +01005671 "<contributor>\n" .
5672 " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
5673 if ($co{'committer_email'}) {
5674 print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
5675 }
5676 print "</contributor>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005677 "<published>$cd{'iso-8601'}</published>\n" .
5678 "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
5679 "<id>$co_url</id>\n" .
5680 "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
5681 "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
5682 }
Jakub Narebski717b8312006-07-31 21:22:15 +02005683 my $comment = $co{'comment'};
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005684 print "<pre>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02005685 foreach my $line (@$comment) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005686 $line = esc_html($line);
5687 print "$line\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02005688 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005689 print "</pre><ul>\n";
5690 foreach my $difftree_line (@difftree) {
5691 my %difftree = parse_difftree_raw_line($difftree_line);
5692 next if !$difftree{'from_id'};
5693
5694 my $file = $difftree{'file'} || $difftree{'to_file'};
5695
5696 print "<li>" .
5697 "[" .
5698 $cgi->a({-href => href(-full=>1, action=>"blobdiff",
5699 hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
5700 hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
5701 file_name=>$file, file_parent=>$difftree{'from_file'}),
5702 -title => "diff"}, 'D');
5703 if ($have_blame) {
5704 print $cgi->a({-href => href(-full=>1, action=>"blame",
5705 file_name=>$file, hash_base=>$commit),
5706 -title => "blame"}, 'B');
Jakub Narebski717b8312006-07-31 21:22:15 +02005707 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005708 # if this is not a feed of a file history
5709 if (!defined $file_name || $file_name ne $file) {
5710 print $cgi->a({-href => href(-full=>1, action=>"history",
5711 file_name=>$file, hash=>$commit),
5712 -title => "history"}, 'H');
5713 }
5714 $file = esc_path($file);
5715 print "] ".
5716 "$file</li>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02005717 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005718 if ($format eq 'rss') {
5719 print "</ul>]]>\n" .
5720 "</content:encoded>\n" .
5721 "</item>\n";
5722 } elsif ($format eq 'atom') {
5723 print "</ul>\n</div>\n" .
5724 "</content>\n" .
5725 "</entry>\n";
5726 }
Jakub Narebski717b8312006-07-31 21:22:15 +02005727 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005728
5729 # end of feed
5730 if ($format eq 'rss') {
5731 print "</channel>\n</rss>\n";
5732 } elsif ($format eq 'atom') {
5733 print "</feed>\n";
5734 }
5735}
5736
5737sub git_rss {
5738 git_feed('rss');
5739}
5740
5741sub git_atom {
5742 git_feed('atom');
Jakub Narebski717b8312006-07-31 21:22:15 +02005743}
5744
5745sub git_opml {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005746 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02005747
5748 print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
Jakub Narebski59b9f612006-08-22 23:42:53 +02005749 print <<XML;
5750<?xml version="1.0" encoding="utf-8"?>
5751<opml version="1.0">
5752<head>
Petr Baudis8be28902006-10-24 05:18:39 +02005753 <title>$site_name OPML Export</title>
Jakub Narebski59b9f612006-08-22 23:42:53 +02005754</head>
5755<body>
5756<outline text="git RSS feeds">
5757XML
Jakub Narebski717b8312006-07-31 21:22:15 +02005758
5759 foreach my $pr (@list) {
5760 my %proj = %$pr;
Jakub Narebski847e01f2006-08-14 02:05:47 +02005761 my $head = git_get_head_hash($proj{'path'});
Jakub Narebski717b8312006-07-31 21:22:15 +02005762 if (!defined $head) {
5763 next;
5764 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005765 $git_dir = "$projectroot/$proj{'path'}";
Jakub Narebski847e01f2006-08-14 02:05:47 +02005766 my %co = parse_commit($head);
Jakub Narebski717b8312006-07-31 21:22:15 +02005767 if (!%co) {
5768 next;
5769 }
5770
5771 my $path = esc_html(chop_str($proj{'path'}, 25, 5));
5772 my $rss = "$my_url?p=$proj{'path'};a=rss";
5773 my $html = "$my_url?p=$proj{'path'};a=summary";
5774 print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
5775 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02005776 print <<XML;
5777</outline>
5778</body>
5779</opml>
5780XML
Jakub Narebski717b8312006-07-31 21:22:15 +02005781}