blob: 5351da2c116e2bb37b7e1f6983c374f6f344ed95 [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++";
Gerrit Pape17a8b252008-03-26 18:11:19 +0000372if (-e $GITWEB_CONFIG) {
373 do $GITWEB_CONFIG;
374} else {
375 our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
376 do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM;
377}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400378
379# version of the core git binary
Jakub Narebski66115d32008-06-14 20:37:59 +0200380our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
Jeff Kingc8d138a2006-08-02 15:23:34 -0400381
382$projects_list ||= $projectroot;
Jeff Kingc8d138a2006-08-02 15:23:34 -0400383
Jakub Narebski154b4d72006-08-05 12:55:20 +0200384# ======================================================================
Kay Sievers09bd7892005-08-07 20:21:23 +0200385# input validation and dispatch
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200386our $action = $cgi->param('a');
Kay Sievers09bd7892005-08-07 20:21:23 +0200387if (defined $action) {
Kay Sieversc91da262005-09-03 14:50:33 +0200388 if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200389 die_error(400, "Invalid action parameter");
Kay Sieversb87d78d2005-08-07 20:21:04 +0200390 }
Kay Sieversb87d78d2005-08-07 20:21:04 +0200391}
392
Jakub Narebski24d06932006-09-26 01:57:02 +0200393# parameters which are pathnames
Martin Waitzdd702352006-09-16 23:08:32 +0200394our $project = $cgi->param('p');
Martin Waitz13d02162006-08-17 00:28:40 +0200395if (defined $project) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200396 if (!validate_pathname($project) ||
Matthias Lederhofer7939fe42006-09-17 00:30:27 +0200397 !(-d "$projectroot/$project") ||
Junio C Hamano2172ce42006-10-03 02:30:47 -0700398 !check_head_link("$projectroot/$project") ||
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200399 ($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
400 ($strict_export && !project_in_list($project))) {
Matthias Lederhofer7939fe42006-09-17 00:30:27 +0200401 undef $project;
Lea Wiemann074afaa2008-06-19 22:03:21 +0200402 die_error(404, "No such project");
Kay Sievers9cd3d982005-08-07 20:17:42 +0200403 }
Kay Sievers2ad93312005-08-07 20:14:48 +0200404}
Kay Sievers6191f8e2005-08-07 20:19:56 +0200405
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200406our $file_name = $cgi->param('f');
Jakub Narebski24d06932006-09-26 01:57:02 +0200407if (defined $file_name) {
408 if (!validate_pathname($file_name)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200409 die_error(400, "Invalid file parameter");
Jakub Narebski24d06932006-09-26 01:57:02 +0200410 }
411}
Martin Waitz5c95fab2006-08-17 00:28:38 +0200412
Jakub Narebski24d06932006-09-26 01:57:02 +0200413our $file_parent = $cgi->param('fp');
414if (defined $file_parent) {
415 if (!validate_pathname($file_parent)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200416 die_error(400, "Invalid file parent parameter");
Jakub Narebski24d06932006-09-26 01:57:02 +0200417 }
418}
419
420# parameters which are refnames
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200421our $hash = $cgi->param('h');
Kay Sievers4fac5292005-08-07 20:27:38 +0200422if (defined $hash) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200423 if (!validate_refname($hash)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200424 die_error(400, "Invalid hash parameter");
Kay Sievers4fac5292005-08-07 20:27:38 +0200425 }
Kay Sieversa59d4af2005-08-07 20:15:44 +0200426}
Kay Sievers6191f8e2005-08-07 20:19:56 +0200427
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200428our $hash_parent = $cgi->param('hp');
Kay Sieversc91da262005-09-03 14:50:33 +0200429if (defined $hash_parent) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200430 if (!validate_refname($hash_parent)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200431 die_error(400, "Invalid hash parent parameter");
Kay Sieversc91da262005-09-03 14:50:33 +0200432 }
Kay Sievers09bd7892005-08-07 20:21:23 +0200433}
434
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200435our $hash_base = $cgi->param('hb');
Kay Sieversc91da262005-09-03 14:50:33 +0200436if (defined $hash_base) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200437 if (!validate_refname($hash_base)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200438 die_error(400, "Invalid hash base parameter");
Kay Sieversc91da262005-09-03 14:50:33 +0200439 }
Kay Sieversa59d4af2005-08-07 20:15:44 +0200440}
Kay Sievers6191f8e2005-08-07 20:19:56 +0200441
Miklos Vajna868bc062007-07-12 20:39:27 +0200442my %allowed_options = (
443 "--no-merges" => [ qw(rss atom log shortlog history) ],
444);
445
446our @extra_options = $cgi->param('opt');
447if (defined @extra_options) {
Jakub Narebski12075102007-07-28 16:27:32 +0200448 foreach my $opt (@extra_options) {
449 if (not exists $allowed_options{$opt}) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200450 die_error(400, "Invalid option parameter");
Miklos Vajna868bc062007-07-12 20:39:27 +0200451 }
Jakub Narebski12075102007-07-28 16:27:32 +0200452 if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200453 die_error(400, "Invalid option parameter for this action");
Miklos Vajna868bc062007-07-12 20:39:27 +0200454 }
455 }
456}
457
Jakub Narebski420e92f2006-08-24 23:53:54 +0200458our $hash_parent_base = $cgi->param('hpb');
459if (defined $hash_parent_base) {
Jakub Narebski24d06932006-09-26 01:57:02 +0200460 if (!validate_refname($hash_parent_base)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200461 die_error(400, "Invalid hash parent base parameter");
Jakub Narebski420e92f2006-08-24 23:53:54 +0200462 }
463}
464
Jakub Narebski24d06932006-09-26 01:57:02 +0200465# other parameters
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200466our $page = $cgi->param('pg');
Kay Sieversea4a6df2005-08-07 20:26:49 +0200467if (defined $page) {
Matthias Lederhoferac8e3f22006-09-17 13:52:45 +0200468 if ($page =~ m/[^0-9]/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200469 die_error(400, "Invalid page parameter");
Kay Sieversb87d78d2005-08-07 20:21:04 +0200470 }
Kay Sieversa59d4af2005-08-07 20:15:44 +0200471}
Kay Sievers823d5dc2005-08-07 19:57:58 +0200472
Petr Baudise7738552007-05-17 04:31:12 +0200473our $searchtype = $cgi->param('st');
474if (defined $searchtype) {
475 if ($searchtype =~ m/[^a-z]/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200476 die_error(400, "Invalid searchtype parameter");
Petr Baudise7738552007-05-17 04:31:12 +0200477 }
478}
479
Petr Baudis0e559912008-02-26 13:22:08 +0100480our $search_use_regexp = $cgi->param('sr');
481
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200482our $searchtext = $cgi->param('s');
Jakub Narebski7e431ef2007-05-16 01:56:10 +0200483our $search_regexp;
Kay Sievers19806692005-08-07 20:26:27 +0200484if (defined $searchtext) {
Robert Fitzsimons9d032c72006-12-23 03:35:15 +0000485 if (length($searchtext) < 2) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200486 die_error(403, "At least two characters are required for search parameter");
Robert Fitzsimons9d032c72006-12-23 03:35:15 +0000487 }
Petr Baudis0e559912008-02-26 13:22:08 +0100488 $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
Kay Sievers19806692005-08-07 20:26:27 +0200489}
490
Martin Waitzdd702352006-09-16 23:08:32 +0200491# now read PATH_INFO and use it as alternative to parameters
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200492sub evaluate_path_info {
493 return if defined $project;
494 my $path_info = $ENV{"PATH_INFO"};
495 return if !$path_info;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200496 $path_info =~ s,^/+,,;
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200497 return if !$path_info;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200498 # find which part of PATH_INFO is project
Martin Waitzdd702352006-09-16 23:08:32 +0200499 $project = $path_info;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200500 $project =~ s,/+$,,;
Junio C Hamano2172ce42006-10-03 02:30:47 -0700501 while ($project && !check_head_link("$projectroot/$project")) {
Martin Waitzdd702352006-09-16 23:08:32 +0200502 $project =~ s,/*[^/]*$,,;
503 }
Jakub Narebskicd90e752006-09-20 00:49:51 +0200504 # validate project
Jakub Narebski24d06932006-09-26 01:57:02 +0200505 $project = validate_pathname($project);
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200506 if (!$project ||
507 ($export_ok && !-e "$projectroot/$project/$export_ok") ||
508 ($strict_export && !project_in_list($project))) {
509 undef $project;
510 return;
Martin Waitzdd702352006-09-16 23:08:32 +0200511 }
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200512 # do not change any parameters if an action is given using the query string
513 return if $action;
martin f. krafftbbd4c302008-04-20 23:03:56 +0200514 $path_info =~ s,^\Q$project\E/*,,;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200515 my ($refname, $pathname) = split(/:/, $path_info, 2);
516 if (defined $pathname) {
517 # we got "project.git/branch:filename" or "project.git/branch:dir/"
518 # we could use git_get_type(branch:pathname), but it needs $git_dir
519 $pathname =~ s,^/+,,;
520 if (!$pathname || substr($pathname, -1) eq "/") {
521 $action ||= "tree";
Martin Waitz053d62b2006-09-21 09:48:21 +0200522 $pathname =~ s,/$,,;
Jakub Narebskicd90e752006-09-20 00:49:51 +0200523 } else {
524 $action ||= "blob_plain";
525 }
Jakub Narebski24d06932006-09-26 01:57:02 +0200526 $hash_base ||= validate_refname($refname);
527 $file_name ||= validate_pathname($pathname);
Jakub Narebskicd90e752006-09-20 00:49:51 +0200528 } elsif (defined $refname) {
Martin Waitzdd702352006-09-16 23:08:32 +0200529 # we got "project.git/branch"
530 $action ||= "shortlog";
Jakub Narebski24d06932006-09-26 01:57:02 +0200531 $hash ||= validate_refname($refname);
Martin Waitzdd702352006-09-16 23:08:32 +0200532 }
533}
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200534evaluate_path_info();
Martin Waitzdd702352006-09-16 23:08:32 +0200535
Matthias Lederhofer645927c2006-09-17 15:29:48 +0200536# path to the current git repository
537our $git_dir;
538$git_dir = "$projectroot/$project" if $project;
Martin Waitzdd702352006-09-16 23:08:32 +0200539
Jakub Narebski717b8312006-07-31 21:22:15 +0200540# dispatch
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200541my %actions = (
542 "blame" => \&git_blame2,
543 "blobdiff" => \&git_blobdiff,
544 "blobdiff_plain" => \&git_blobdiff_plain,
545 "blob" => \&git_blob,
546 "blob_plain" => \&git_blob_plain,
547 "commitdiff" => \&git_commitdiff,
548 "commitdiff_plain" => \&git_commitdiff_plain,
549 "commit" => \&git_commit,
Petr Baudise30496d2006-10-24 05:33:17 +0200550 "forks" => \&git_forks,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200551 "heads" => \&git_heads,
552 "history" => \&git_history,
553 "log" => \&git_log,
554 "rss" => \&git_rss,
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +0100555 "atom" => \&git_atom,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200556 "search" => \&git_search,
Petr Baudis88ad7292006-10-24 05:15:46 +0200557 "search_help" => \&git_search_help,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200558 "shortlog" => \&git_shortlog,
559 "summary" => \&git_summary,
560 "tag" => \&git_tag,
561 "tags" => \&git_tags,
562 "tree" => \&git_tree,
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +0530563 "snapshot" => \&git_snapshot,
Jakub Narebskica946012006-12-10 13:25:47 +0100564 "object" => \&git_object,
Jakub Narebski77a153f2006-08-22 16:59:20 +0200565 # those below don't need $project
566 "opml" => \&git_opml,
567 "project_list" => \&git_project_list,
Jakub Narebskifc2b2be2006-09-15 04:56:03 +0200568 "project_index" => \&git_project_index,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200569);
570
Gerrit Pape7f9778b2007-05-10 07:32:07 +0000571if (!defined $action) {
572 if (defined $hash) {
573 $action = git_get_type($hash);
574 } elsif (defined $hash_base && defined $file_name) {
575 $action = git_get_type("$hash_base:$file_name");
576 } elsif (defined $project) {
577 $action = 'summary';
578 } else {
579 $action = 'project_list';
580 }
Jakub Narebski77a153f2006-08-22 16:59:20 +0200581}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200582if (!defined($actions{$action})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200583 die_error(400, "Unknown action");
Kay Sievers09bd7892005-08-07 20:21:23 +0200584}
Jakub Narebskid04d3d42006-09-19 21:53:22 +0200585if ($action !~ m/^(opml|project_list|project_index)$/ &&
586 !$project) {
Lea Wiemann074afaa2008-06-19 22:03:21 +0200587 die_error(400, "Project needed");
Jakub Narebskid04d3d42006-09-19 21:53:22 +0200588}
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200589$actions{$action}->();
590exit;
Kay Sievers09bd7892005-08-07 20:21:23 +0200591
Jakub Narebski717b8312006-07-31 21:22:15 +0200592## ======================================================================
Martin Waitz06a9d862006-08-16 00:23:50 +0200593## action links
594
Jakub Narebski35621982008-04-20 22:09:48 +0200595sub href (%) {
Jakub Narebski498fe002006-08-22 19:05:25 +0200596 my %params = @_;
Jakub Narebskibd5d1e42006-11-19 15:05:21 +0100597 # default is to use -absolute url() i.e. $my_uri
598 my $href = $params{-full} ? $my_url : $my_uri;
Jakub Narebski498fe002006-08-22 19:05:25 +0200599
Petr Baudis34c06112006-10-11 22:31:15 +0200600 # XXX: Warning: If you touch this, check the search form for updating,
601 # too.
Jakub Narebski498fe002006-08-22 19:05:25 +0200602
603 my @mapping = (
Martin Waitz06a9d862006-08-16 00:23:50 +0200604 project => "p",
Jakub Narebski420e92f2006-08-24 23:53:54 +0200605 action => "a",
Martin Waitz06a9d862006-08-16 00:23:50 +0200606 file_name => "f",
Martin Waitz5c95fab2006-08-17 00:28:38 +0200607 file_parent => "fp",
Martin Waitz06a9d862006-08-16 00:23:50 +0200608 hash => "h",
609 hash_parent => "hp",
610 hash_base => "hb",
Jakub Narebski420e92f2006-08-24 23:53:54 +0200611 hash_parent_base => "hpb",
Martin Waitz06a9d862006-08-16 00:23:50 +0200612 page => "pg",
Jakub Narebskia1565c42006-09-15 19:30:34 +0200613 order => "o",
Martin Waitz06a9d862006-08-16 00:23:50 +0200614 searchtext => "s",
Petr Baudis88ad7292006-10-24 05:15:46 +0200615 searchtype => "st",
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200616 snapshot_format => "sf",
Jakub Narebski12075102007-07-28 16:27:32 +0200617 extra_options => "opt",
Petr Baudis0e559912008-02-26 13:22:08 +0100618 search_use_regexp => "sr",
Martin Waitz06a9d862006-08-16 00:23:50 +0200619 );
Jakub Narebski498fe002006-08-22 19:05:25 +0200620 my %mapping = @mapping;
Martin Waitz06a9d862006-08-16 00:23:50 +0200621
Jakub Narebskiafa9b622008-02-14 09:22:30 +0100622 $params{'project'} = $project unless exists $params{'project'};
623
Jakub Narebski1cad2832007-11-01 13:06:27 +0100624 if ($params{-replay}) {
625 while (my ($name, $symbol) = each %mapping) {
626 if (!exists $params{$name}) {
627 # to allow for multivalued params we use arrayref form
628 $params{$name} = [ $cgi->param($symbol) ];
629 }
630 }
631 }
632
Martin Waitz9e756902006-10-01 23:57:48 +0200633 my ($use_pathinfo) = gitweb_check_feature('pathinfo');
634 if ($use_pathinfo) {
635 # use PATH_INFO for project name
martin f. krafft85d17a12008-04-20 23:23:38 +0200636 $href .= "/".esc_url($params{'project'}) if defined $params{'project'};
Martin Waitz9e756902006-10-01 23:57:48 +0200637 delete $params{'project'};
638
639 # Summary just uses the project path URL
640 if (defined $params{'action'} && $params{'action'} eq 'summary') {
641 delete $params{'action'};
642 }
643 }
644
645 # now encode the parameters explicitly
Jakub Narebski498fe002006-08-22 19:05:25 +0200646 my @result = ();
647 for (my $i = 0; $i < @mapping; $i += 2) {
648 my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]);
649 if (defined $params{$name}) {
Jakub Narebskif22cca42007-07-29 01:04:09 +0200650 if (ref($params{$name}) eq "ARRAY") {
651 foreach my $par (@{$params{$name}}) {
652 push @result, $symbol . "=" . esc_param($par);
653 }
654 } else {
655 push @result, $symbol . "=" . esc_param($params{$name});
656 }
Jakub Narebski498fe002006-08-22 19:05:25 +0200657 }
658 }
Martin Waitz9e756902006-10-01 23:57:48 +0200659 $href .= "?" . join(';', @result) if scalar @result;
660
661 return $href;
Martin Waitz06a9d862006-08-16 00:23:50 +0200662}
663
664
665## ======================================================================
Jakub Narebski717b8312006-07-31 21:22:15 +0200666## validation, quoting/unquoting and escaping
667
Jakub Narebski24d06932006-09-26 01:57:02 +0200668sub validate_pathname {
669 my $input = shift || return undef;
Jakub Narebski717b8312006-07-31 21:22:15 +0200670
Jakub Narebski24d06932006-09-26 01:57:02 +0200671 # no '.' or '..' as elements of path, i.e. no '.' nor '..'
672 # at the beginning, at the end, and between slashes.
673 # also this catches doubled slashes
674 if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
675 return undef;
676 }
677 # no null characters
678 if ($input =~ m!\0!) {
679 return undef;
680 }
681 return $input;
682}
683
684sub validate_refname {
685 my $input = shift || return undef;
686
687 # textual hashes are O.K.
Jakub Narebski717b8312006-07-31 21:22:15 +0200688 if ($input =~ m/^[0-9a-fA-F]{40}$/) {
689 return $input;
690 }
Jakub Narebski24d06932006-09-26 01:57:02 +0200691 # it must be correct pathname
692 $input = validate_pathname($input)
693 or return undef;
694 # restrictions on ref name according to git-check-ref-format
695 if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +0200696 return undef;
697 }
698 return $input;
699}
700
Martin Koegler00f429a2007-06-03 17:42:44 +0200701# decode sequences of octets in utf8 into Perl's internal form,
702# which is utf-8 with utf8 flag set if needed. gitweb writes out
703# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
704sub to_utf8 {
705 my $str = shift;
İsmail Dönmeze5d3de52007-12-04 10:55:41 +0200706 if (utf8::valid($str)) {
707 utf8::decode($str);
708 return $str;
Martin Koegler00f429a2007-06-03 17:42:44 +0200709 } else {
710 return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
711 }
712}
713
Kay Sievers232ff552005-11-24 16:56:55 +0100714# quote unsafe chars, but keep the slash, even when it's not
715# correct, but quoted slashes look too horrible in bookmarks
716sub esc_param {
Kay Sievers353347b2005-11-14 05:47:18 +0100717 my $str = shift;
Petr Baudisa2f3db22006-09-24 00:18:41 +0200718 $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg;
Kay Sievers18216712005-11-14 06:10:07 +0100719 $str =~ s/\+/%2B/g;
Kay Sieversa9e60b72005-11-14 15:15:12 +0100720 $str =~ s/ /\+/g;
Kay Sievers353347b2005-11-14 05:47:18 +0100721 return $str;
722}
723
Jakub Narebskif93bff82006-09-26 01:58:41 +0200724# quote unsafe chars in whole URL, so some charactrs cannot be quoted
725sub esc_url {
726 my $str = shift;
727 $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
728 $str =~ s/\+/%2B/g;
729 $str =~ s/ /\+/g;
730 return $str;
731}
732
Kay Sievers232ff552005-11-24 16:56:55 +0100733# replace invalid utf8 character with SUBSTITUTION sequence
Jakub Narebski6255ef02006-11-01 14:33:21 +0100734sub esc_html ($;%) {
Kay Sievers40c13812005-11-19 17:41:29 +0100735 my $str = shift;
Jakub Narebski6255ef02006-11-01 14:33:21 +0100736 my %opts = @_;
737
Martin Koegler00f429a2007-06-03 17:42:44 +0200738 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +0800739 $str = $cgi->escapeHTML($str);
Jakub Narebski6255ef02006-11-01 14:33:21 +0100740 if ($opts{'-nbsp'}) {
741 $str =~ s/ /&nbsp;/g;
742 }
Junio C Hamano25ffbb22006-11-08 15:11:10 -0800743 $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
Kay Sievers40c13812005-11-19 17:41:29 +0100744 return $str;
745}
746
Jakub Narebski391862e2006-11-25 09:43:59 +0100747# quote control characters and escape filename to HTML
748sub esc_path {
749 my $str = shift;
750 my %opts = @_;
751
Martin Koegler00f429a2007-06-03 17:42:44 +0200752 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +0800753 $str = $cgi->escapeHTML($str);
Jakub Narebski391862e2006-11-25 09:43:59 +0100754 if ($opts{'-nbsp'}) {
755 $str =~ s/ /&nbsp;/g;
756 }
757 $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
758 return $str;
759}
760
761# Make control characters "printable", using character escape codes (CEC)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100762sub quot_cec {
763 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +0100764 my %opts = @_;
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100765 my %es = ( # character escape codes, aka escape sequences
Jakub Narebskic84c4832008-02-17 18:48:13 +0100766 "\t" => '\t', # tab (HT)
767 "\n" => '\n', # line feed (LF)
768 "\r" => '\r', # carrige return (CR)
769 "\f" => '\f', # form feed (FF)
770 "\b" => '\b', # backspace (BS)
771 "\a" => '\a', # alarm (bell) (BEL)
772 "\e" => '\e', # escape (ESC)
773 "\013" => '\v', # vertical tab (VT)
774 "\000" => '\0', # nul character (NUL)
775 );
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100776 my $chr = ( (exists $es{$cntrl})
777 ? $es{$cntrl}
778 : sprintf('\%03o', ord($cntrl)) );
Jakub Narebskic84c4832008-02-17 18:48:13 +0100779 if ($opts{-nohtml}) {
780 return $chr;
781 } else {
782 return "<span class=\"cntrl\">$chr</span>";
783 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100784}
785
Jakub Narebski391862e2006-11-25 09:43:59 +0100786# Alternatively use unicode control pictures codepoints,
787# Unicode "printable representation" (PR)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100788sub quot_upr {
789 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +0100790 my %opts = @_;
791
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100792 my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
Jakub Narebskic84c4832008-02-17 18:48:13 +0100793 if ($opts{-nohtml}) {
794 return $chr;
795 } else {
796 return "<span class=\"cntrl\">$chr</span>";
797 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +0100798}
799
Kay Sievers232ff552005-11-24 16:56:55 +0100800# git may return quoted and escaped filenames
801sub unquote {
802 my $str = shift;
Jakub Narebski403d0902006-11-08 11:48:56 +0100803
804 sub unq {
805 my $seq = shift;
806 my %es = ( # character escape codes, aka escape sequences
807 't' => "\t", # tab (HT, TAB)
808 'n' => "\n", # newline (NL)
809 'r' => "\r", # return (CR)
810 'f' => "\f", # form feed (FF)
811 'b' => "\b", # backspace (BS)
812 'a' => "\a", # alarm (bell) (BEL)
813 'e' => "\e", # escape (ESC)
814 'v' => "\013", # vertical tab (VT)
815 );
816
817 if ($seq =~ m/^[0-7]{1,3}$/) {
818 # octal char sequence
819 return chr(oct($seq));
820 } elsif (exists $es{$seq}) {
821 # C escape sequence, aka character escape code
Jakub Narebskic84c4832008-02-17 18:48:13 +0100822 return $es{$seq};
Jakub Narebski403d0902006-11-08 11:48:56 +0100823 }
824 # quoted ordinary character
825 return $seq;
826 }
827
Kay Sievers232ff552005-11-24 16:56:55 +0100828 if ($str =~ m/^"(.*)"$/) {
Jakub Narebski403d0902006-11-08 11:48:56 +0100829 # needs unquoting
Kay Sievers232ff552005-11-24 16:56:55 +0100830 $str = $1;
Jakub Narebski403d0902006-11-08 11:48:56 +0100831 $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
Kay Sievers232ff552005-11-24 16:56:55 +0100832 }
833 return $str;
834}
835
Jakub Narebskif16db172006-08-06 02:08:31 +0200836# escape tabs (convert tabs to spaces)
837sub untabify {
838 my $line = shift;
839
840 while ((my $pos = index($line, "\t")) != -1) {
841 if (my $count = (8 - ($pos % 8))) {
842 my $spaces = ' ' x $count;
843 $line =~ s/\t/$spaces/;
844 }
845 }
846
847 return $line;
848}
849
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200850sub project_in_list {
851 my $project = shift;
852 my @list = git_get_projects_list();
853 return @list && scalar(grep { $_->{'path'} eq $project } @list);
854}
855
Jakub Narebski717b8312006-07-31 21:22:15 +0200856## ----------------------------------------------------------------------
857## HTML aware string manipulation
858
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100859# Try to chop given string on a word boundary between position
860# $len and $len+$add_len. If there is no word boundary there,
861# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
862# (marking chopped part) would be longer than given string.
Jakub Narebski717b8312006-07-31 21:22:15 +0200863sub chop_str {
864 my $str = shift;
865 my $len = shift;
866 my $add_len = shift || 10;
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100867 my $where = shift || 'right'; # 'left' | 'center' | 'right'
Jakub Narebski717b8312006-07-31 21:22:15 +0200868
Anders Waldenborgdee27752008-05-21 13:44:43 +0200869 # Make sure perl knows it is utf8 encoded so we don't
870 # cut in the middle of a utf8 multibyte char.
871 $str = to_utf8($str);
872
Jakub Narebski717b8312006-07-31 21:22:15 +0200873 # allow only $len chars, but don't cut a word if it would fit in $add_len
874 # 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 +0100875 # remove chopped character entities entirely
876
877 # when chopping in the middle, distribute $len into left and right part
878 # return early if chopping wouldn't make string shorter
879 if ($where eq 'center') {
880 return $str if ($len + 5 >= length($str)); # filler is length 5
881 $len = int($len/2);
882 } else {
883 return $str if ($len + 4 >= length($str)); # filler is length 4
Jakub Narebski717b8312006-07-31 21:22:15 +0200884 }
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100885
886 # regexps: ending and beginning with word part up to $add_len
887 my $endre = qr/.{$len}\w{0,$add_len}/;
888 my $begre = qr/\w{0,$add_len}.{$len}/;
889
890 if ($where eq 'left') {
891 $str =~ m/^(.*?)($begre)$/;
892 my ($lead, $body) = ($1, $2);
893 if (length($lead) > 4) {
894 $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
895 $lead = " ...";
896 }
897 return "$lead$body";
898
899 } elsif ($where eq 'center') {
900 $str =~ m/^($endre)(.*)$/;
901 my ($left, $str) = ($1, $2);
902 $str =~ m/^(.*?)($begre)$/;
903 my ($mid, $right) = ($1, $2);
904 if (length($mid) > 5) {
905 $left =~ s/&[^;]*$//;
906 $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
907 $mid = " ... ";
908 }
909 return "$left$mid$right";
910
911 } else {
912 $str =~ m/^($endre)(.*)$/;
913 my $body = $1;
914 my $tail = $2;
915 if (length($tail) > 4) {
916 $body =~ s/&[^;]*$//;
917 $tail = "... ";
918 }
919 return "$body$tail";
920 }
Jakub Narebski717b8312006-07-31 21:22:15 +0200921}
922
David Symondsce58ec92007-10-23 11:31:22 +1000923# takes the same arguments as chop_str, but also wraps a <span> around the
924# result with a title attribute if it does get chopped. Additionally, the
925# string is HTML-escaped.
926sub chop_and_escape_str {
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100927 my ($str) = @_;
David Symondsce58ec92007-10-23 11:31:22 +1000928
Jakub Narebskib8d97d02008-02-25 21:07:57 +0100929 my $chopped = chop_str(@_);
David Symondsce58ec92007-10-23 11:31:22 +1000930 if ($chopped eq $str) {
931 return esc_html($chopped);
932 } else {
Jakub Narebski850b90a2008-02-16 23:07:46 +0100933 $str =~ s/([[:cntrl:]])/?/g;
934 return $cgi->span({-title=>$str}, esc_html($chopped));
David Symondsce58ec92007-10-23 11:31:22 +1000935 }
936}
937
Jakub Narebski717b8312006-07-31 21:22:15 +0200938## ----------------------------------------------------------------------
939## functions returning short strings
940
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +0000941# CSS class for given age value (in seconds)
942sub age_class {
943 my $age = shift;
944
Jakub Narebski785cdea2007-05-13 12:39:22 +0200945 if (!defined $age) {
946 return "noage";
947 } elsif ($age < 60*60*2) {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +0000948 return "age0";
949 } elsif ($age < 60*60*24*2) {
950 return "age1";
951 } else {
952 return "age2";
953 }
954}
955
Jakub Narebski717b8312006-07-31 21:22:15 +0200956# convert age in seconds to "nn units ago" string
957sub age_string {
958 my $age = shift;
959 my $age_str;
960
961 if ($age > 60*60*24*365*2) {
962 $age_str = (int $age/60/60/24/365);
963 $age_str .= " years ago";
964 } elsif ($age > 60*60*24*(365/12)*2) {
965 $age_str = int $age/60/60/24/(365/12);
966 $age_str .= " months ago";
967 } elsif ($age > 60*60*24*7*2) {
968 $age_str = int $age/60/60/24/7;
969 $age_str .= " weeks ago";
970 } elsif ($age > 60*60*24*2) {
971 $age_str = int $age/60/60/24;
972 $age_str .= " days ago";
973 } elsif ($age > 60*60*2) {
974 $age_str = int $age/60/60;
975 $age_str .= " hours ago";
976 } elsif ($age > 60*2) {
977 $age_str = int $age/60;
978 $age_str .= " min ago";
979 } elsif ($age > 2) {
980 $age_str = int $age;
981 $age_str .= " sec ago";
982 } else {
983 $age_str .= " right now";
984 }
985 return $age_str;
986}
987
Jakub Narebski01ac1e32007-07-28 16:27:31 +0200988use constant {
989 S_IFINVALID => 0030000,
990 S_IFGITLINK => 0160000,
991};
992
993# submodule/subproject, a commit object reference
994sub S_ISGITLINK($) {
995 my $mode = shift;
996
997 return (($mode & S_IFMT) == S_IFGITLINK)
998}
999
Jakub Narebski717b8312006-07-31 21:22:15 +02001000# convert file mode in octal to symbolic file mode string
1001sub mode_str {
1002 my $mode = oct shift;
1003
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001004 if (S_ISGITLINK($mode)) {
1005 return 'm---------';
1006 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001007 return 'drwxr-xr-x';
1008 } elsif (S_ISLNK($mode)) {
1009 return 'lrwxrwxrwx';
1010 } elsif (S_ISREG($mode)) {
1011 # git cares only about the executable bit
1012 if ($mode & S_IXUSR) {
1013 return '-rwxr-xr-x';
1014 } else {
1015 return '-rw-r--r--';
1016 };
1017 } else {
1018 return '----------';
1019 }
1020}
1021
1022# convert file mode in octal to file type string
1023sub file_type {
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02001024 my $mode = shift;
1025
1026 if ($mode !~ m/^[0-7]+$/) {
1027 return $mode;
1028 } else {
1029 $mode = oct $mode;
1030 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001031
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001032 if (S_ISGITLINK($mode)) {
1033 return "submodule";
1034 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001035 return "directory";
1036 } elsif (S_ISLNK($mode)) {
1037 return "symlink";
1038 } elsif (S_ISREG($mode)) {
1039 return "file";
1040 } else {
1041 return "unknown";
1042 }
1043}
1044
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001045# convert file mode in octal to file type description string
1046sub file_type_long {
1047 my $mode = shift;
1048
1049 if ($mode !~ m/^[0-7]+$/) {
1050 return $mode;
1051 } else {
1052 $mode = oct $mode;
1053 }
1054
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001055 if (S_ISGITLINK($mode)) {
1056 return "submodule";
1057 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001058 return "directory";
1059 } elsif (S_ISLNK($mode)) {
1060 return "symlink";
1061 } elsif (S_ISREG($mode)) {
1062 if ($mode & S_IXUSR) {
1063 return "executable";
1064 } else {
1065 return "file";
1066 };
1067 } else {
1068 return "unknown";
1069 }
1070}
1071
1072
Jakub Narebski717b8312006-07-31 21:22:15 +02001073## ----------------------------------------------------------------------
1074## functions returning short HTML fragments, or transforming HTML fragments
Pavel Roskin3dff5372007-02-03 23:49:16 -05001075## which don't belong to other sections
Jakub Narebski717b8312006-07-31 21:22:15 +02001076
Junio C Hamano225932e2006-11-09 00:57:13 -08001077# format line of commit message.
Jakub Narebski717b8312006-07-31 21:22:15 +02001078sub format_log_line_html {
1079 my $line = shift;
1080
Junio C Hamano225932e2006-11-09 00:57:13 -08001081 $line = esc_html($line, -nbsp=>1);
Jakub Narebskibfe21912006-12-10 13:25:49 +01001082 if ($line =~ m/([0-9a-fA-F]{8,40})/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001083 my $hash_text = $1;
Jakub Narebskibfe21912006-12-10 13:25:49 +01001084 my $link =
1085 $cgi->a({-href => href(action=>"object", hash=>$hash_text),
1086 -class => "text"}, $hash_text);
1087 $line =~ s/$hash_text/$link/;
Jakub Narebski717b8312006-07-31 21:22:15 +02001088 }
1089 return $line;
1090}
1091
1092# format marker of refs pointing to given object
Jakub Narebski847e01f2006-08-14 02:05:47 +02001093sub format_ref_marker {
Jakub Narebski717b8312006-07-31 21:22:15 +02001094 my ($refs, $id) = @_;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001095 my $markers = '';
Jakub Narebski717b8312006-07-31 21:22:15 +02001096
1097 if (defined $refs->{$id}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001098 foreach my $ref (@{$refs->{$id}}) {
1099 my ($type, $name) = qw();
1100 # e.g. tags/v2.6.11 or heads/next
1101 if ($ref =~ m!^(.*?)s?/(.*)$!) {
1102 $type = $1;
1103 $name = $2;
1104 } else {
1105 $type = "ref";
1106 $name = $ref;
1107 }
1108
Jakub Narebski5fce2782006-12-15 23:49:12 +01001109 $markers .= " <span class=\"$type\" title=\"$ref\">" .
1110 esc_html($name) . "</span>";
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001111 }
1112 }
1113
1114 if ($markers) {
1115 return ' <span class="refs">'. $markers . '</span>';
Jakub Narebski717b8312006-07-31 21:22:15 +02001116 } else {
1117 return "";
1118 }
1119}
1120
Jakub Narebski17d07442006-08-14 02:08:27 +02001121# format, perhaps shortened and with markers, title line
1122sub format_subject_html {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02001123 my ($long, $short, $href, $extra) = @_;
Jakub Narebski17d07442006-08-14 02:08:27 +02001124 $extra = '' unless defined($extra);
1125
1126 if (length($short) < length($long)) {
Jakub Narebski7c278012006-08-22 12:02:48 +02001127 return $cgi->a({-href => $href, -class => "list subject",
Martin Koegler00f429a2007-06-03 17:42:44 +02001128 -title => to_utf8($long)},
Jakub Narebski17d07442006-08-14 02:08:27 +02001129 esc_html($short) . $extra);
1130 } else {
Jakub Narebski7c278012006-08-22 12:02:48 +02001131 return $cgi->a({-href => $href, -class => "list subject"},
Jakub Narebski17d07442006-08-14 02:08:27 +02001132 esc_html($long) . $extra);
1133 }
1134}
1135
Jakub Narebski90921742007-06-08 13:27:42 +02001136# format git diff header line, i.e. "diff --(git|combined|cc) ..."
1137sub format_git_diff_header_line {
1138 my $line = shift;
1139 my $diffinfo = shift;
1140 my ($from, $to) = @_;
1141
1142 if ($diffinfo->{'nparents'}) {
1143 # combined diff
1144 $line =~ s!^(diff (.*?) )"?.*$!$1!;
1145 if ($to->{'href'}) {
1146 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1147 esc_path($to->{'file'}));
1148 } else { # file was deleted (no href)
1149 $line .= esc_path($to->{'file'});
1150 }
1151 } else {
1152 # "ordinary" diff
1153 $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
1154 if ($from->{'href'}) {
1155 $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
1156 'a/' . esc_path($from->{'file'}));
1157 } else { # file was added (no href)
1158 $line .= 'a/' . esc_path($from->{'file'});
1159 }
1160 $line .= ' ';
1161 if ($to->{'href'}) {
1162 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1163 'b/' . esc_path($to->{'file'}));
1164 } else { # file was deleted
1165 $line .= 'b/' . esc_path($to->{'file'});
1166 }
1167 }
1168
1169 return "<div class=\"diff header\">$line</div>\n";
1170}
1171
1172# format extended diff header line, before patch itself
1173sub format_extended_diff_header_line {
1174 my $line = shift;
1175 my $diffinfo = shift;
1176 my ($from, $to) = @_;
1177
1178 # match <path>
1179 if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
1180 $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1181 esc_path($from->{'file'}));
1182 }
1183 if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
1184 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1185 esc_path($to->{'file'}));
1186 }
1187 # match single <mode>
1188 if ($line =~ m/\s(\d{6})$/) {
1189 $line .= '<span class="info"> (' .
1190 file_type_long($1) .
1191 ')</span>';
1192 }
1193 # match <hash>
1194 if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
1195 # can match only for combined diff
1196 $line = 'index ';
1197 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1198 if ($from->{'href'}[$i]) {
1199 $line .= $cgi->a({-href=>$from->{'href'}[$i],
1200 -class=>"hash"},
1201 substr($diffinfo->{'from_id'}[$i],0,7));
1202 } else {
1203 $line .= '0' x 7;
1204 }
1205 # separator
1206 $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
1207 }
1208 $line .= '..';
1209 if ($to->{'href'}) {
1210 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1211 substr($diffinfo->{'to_id'},0,7));
1212 } else {
1213 $line .= '0' x 7;
1214 }
1215
1216 } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
1217 # can match only for ordinary diff
1218 my ($from_link, $to_link);
1219 if ($from->{'href'}) {
1220 $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
1221 substr($diffinfo->{'from_id'},0,7));
1222 } else {
1223 $from_link = '0' x 7;
1224 }
1225 if ($to->{'href'}) {
1226 $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1227 substr($diffinfo->{'to_id'},0,7));
1228 } else {
1229 $to_link = '0' x 7;
1230 }
1231 my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
1232 $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
1233 }
1234
1235 return $line . "<br/>\n";
1236}
1237
1238# format from-file/to-file diff header
1239sub format_diff_from_to_header {
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001240 my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
Jakub Narebski90921742007-06-08 13:27:42 +02001241 my $line;
1242 my $result = '';
1243
1244 $line = $from_line;
1245 #assert($line =~ m/^---/) if DEBUG;
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001246 # no extra formatting for "^--- /dev/null"
1247 if (! $diffinfo->{'nparents'}) {
1248 # ordinary (single parent) diff
1249 if ($line =~ m!^--- "?a/!) {
1250 if ($from->{'href'}) {
1251 $line = '--- a/' .
1252 $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1253 esc_path($from->{'file'}));
1254 } else {
1255 $line = '--- a/' .
1256 esc_path($from->{'file'});
1257 }
1258 }
1259 $result .= qq!<div class="diff from_file">$line</div>\n!;
1260
1261 } else {
1262 # combined diff (merge commit)
1263 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1264 if ($from->{'href'}[$i]) {
1265 $line = '--- ' .
Jakub Narebski91af4ce2007-06-08 13:32:44 +02001266 $cgi->a({-href=>href(action=>"blobdiff",
1267 hash_parent=>$diffinfo->{'from_id'}[$i],
1268 hash_parent_base=>$parents[$i],
1269 file_parent=>$from->{'file'}[$i],
1270 hash=>$diffinfo->{'to_id'},
1271 hash_base=>$hash,
1272 file_name=>$to->{'file'}),
1273 -class=>"path",
1274 -title=>"diff" . ($i+1)},
1275 $i+1) .
1276 '/' .
Jakub Narebskideaa01a2007-06-08 13:29:49 +02001277 $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
1278 esc_path($from->{'file'}[$i]));
1279 } else {
1280 $line = '--- /dev/null';
1281 }
1282 $result .= qq!<div class="diff from_file">$line</div>\n!;
Jakub Narebski90921742007-06-08 13:27:42 +02001283 }
1284 }
Jakub Narebski90921742007-06-08 13:27:42 +02001285
1286 $line = $to_line;
1287 #assert($line =~ m/^\+\+\+/) if DEBUG;
1288 # no extra formatting for "^+++ /dev/null"
1289 if ($line =~ m!^\+\+\+ "?b/!) {
1290 if ($to->{'href'}) {
1291 $line = '+++ b/' .
1292 $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1293 esc_path($to->{'file'}));
1294 } else {
1295 $line = '+++ b/' .
1296 esc_path($to->{'file'});
1297 }
1298 }
1299 $result .= qq!<div class="diff to_file">$line</div>\n!;
1300
1301 return $result;
1302}
1303
Jakub Narebskicd030c32007-06-08 13:33:28 +02001304# create note for patch simplified by combined diff
1305sub format_diff_cc_simplified {
1306 my ($diffinfo, @parents) = @_;
1307 my $result = '';
1308
1309 $result .= "<div class=\"diff header\">" .
1310 "diff --cc ";
1311 if (!is_deleted($diffinfo)) {
1312 $result .= $cgi->a({-href => href(action=>"blob",
1313 hash_base=>$hash,
1314 hash=>$diffinfo->{'to_id'},
1315 file_name=>$diffinfo->{'to_file'}),
1316 -class => "path"},
1317 esc_path($diffinfo->{'to_file'}));
1318 } else {
1319 $result .= esc_path($diffinfo->{'to_file'});
1320 }
1321 $result .= "</div>\n" . # class="diff header"
1322 "<div class=\"diff nodifferences\">" .
1323 "Simple merge" .
1324 "</div>\n"; # class="diff nodifferences"
1325
1326 return $result;
1327}
1328
Jakub Narebski90921742007-06-08 13:27:42 +02001329# format patch (diff) line (not to be used for diff headers)
Jakub Narebskieee08902006-08-24 00:15:14 +02001330sub format_diff_line {
1331 my $line = shift;
Jakub Narebski59e3b142006-11-18 23:35:40 +01001332 my ($from, $to) = @_;
Jakub Narebskieee08902006-08-24 00:15:14 +02001333 my $diff_class = "";
1334
1335 chomp $line;
1336
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001337 if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
1338 # combined diff
1339 my $prefix = substr($line, 0, scalar @{$from->{'href'}});
1340 if ($line =~ m/^\@{3}/) {
1341 $diff_class = " chunk_header";
1342 } elsif ($line =~ m/^\\/) {
1343 $diff_class = " incomplete";
1344 } elsif ($prefix =~ tr/+/+/) {
1345 $diff_class = " add";
1346 } elsif ($prefix =~ tr/-/-/) {
1347 $diff_class = " rem";
1348 }
1349 } else {
1350 # assume ordinary diff
1351 my $char = substr($line, 0, 1);
1352 if ($char eq '+') {
1353 $diff_class = " add";
1354 } elsif ($char eq '-') {
1355 $diff_class = " rem";
1356 } elsif ($char eq '@') {
1357 $diff_class = " chunk_header";
1358 } elsif ($char eq "\\") {
1359 $diff_class = " incomplete";
1360 }
Jakub Narebskieee08902006-08-24 00:15:14 +02001361 }
1362 $line = untabify($line);
Jakub Narebski59e3b142006-11-18 23:35:40 +01001363 if ($from && $to && $line =~ m/^\@{2} /) {
1364 my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
1365 $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
1366
1367 $from_lines = 0 unless defined $from_lines;
1368 $to_lines = 0 unless defined $to_lines;
1369
1370 if ($from->{'href'}) {
1371 $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
1372 -class=>"list"}, $from_text);
1373 }
1374 if ($to->{'href'}) {
1375 $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
1376 -class=>"list"}, $to_text);
1377 }
1378 $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
1379 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1380 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02001381 } elsif ($from && $to && $line =~ m/^\@{3}/) {
1382 my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
1383 my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
1384
1385 @from_text = split(' ', $ranges);
1386 for (my $i = 0; $i < @from_text; ++$i) {
1387 ($from_start[$i], $from_nlines[$i]) =
1388 (split(',', substr($from_text[$i], 1)), 0);
1389 }
1390
1391 $to_text = pop @from_text;
1392 $to_start = pop @from_start;
1393 $to_nlines = pop @from_nlines;
1394
1395 $line = "<span class=\"chunk_info\">$prefix ";
1396 for (my $i = 0; $i < @from_text; ++$i) {
1397 if ($from->{'href'}[$i]) {
1398 $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
1399 -class=>"list"}, $from_text[$i]);
1400 } else {
1401 $line .= $from_text[$i];
1402 }
1403 $line .= " ";
1404 }
1405 if ($to->{'href'}) {
1406 $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
1407 -class=>"list"}, $to_text);
1408 } else {
1409 $line .= $to_text;
1410 }
1411 $line .= " $prefix</span>" .
1412 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
1413 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebski59e3b142006-11-18 23:35:40 +01001414 }
Jakub Narebski6255ef02006-11-01 14:33:21 +01001415 return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02001416}
1417
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001418# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
1419# linked. Pass the hash of the tree/commit to snapshot.
1420sub format_snapshot_links {
1421 my ($hash) = @_;
1422 my @snapshot_fmts = gitweb_check_feature('snapshot');
Jakub Narebskia7817852007-07-22 23:41:20 +02001423 @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001424 my $num_fmts = @snapshot_fmts;
1425 if ($num_fmts > 1) {
1426 # A parenthesized list of links bearing format names.
Jakub Narebskia7817852007-07-22 23:41:20 +02001427 # e.g. "snapshot (_tar.gz_ _zip_)"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001428 return "snapshot (" . join(' ', map
1429 $cgi->a({
1430 -href => href(
1431 action=>"snapshot",
1432 hash=>$hash,
1433 snapshot_format=>$_
1434 )
1435 }, $known_snapshot_formats{$_}{'display'})
1436 , @snapshot_fmts) . ")";
1437 } elsif ($num_fmts == 1) {
1438 # A single "snapshot" link whose tooltip bears the format name.
Jakub Narebskia7817852007-07-22 23:41:20 +02001439 # i.e. "_snapshot_"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001440 my ($fmt) = @snapshot_fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +02001441 return
1442 $cgi->a({
Matt McCutchena3c8ab32007-07-22 01:30:27 +02001443 -href => href(
1444 action=>"snapshot",
1445 hash=>$hash,
1446 snapshot_format=>$fmt
1447 ),
1448 -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
1449 }, "snapshot");
1450 } else { # $num_fmts == 0
1451 return undef;
1452 }
1453}
1454
Jakub Narebski35621982008-04-20 22:09:48 +02001455## ......................................................................
1456## functions returning values to be passed, perhaps after some
1457## transformation, to other functions; e.g. returning arguments to href()
1458
1459# returns hash to be passed to href to generate gitweb URL
1460# in -title key it returns description of link
1461sub get_feed_info {
1462 my $format = shift || 'Atom';
1463 my %res = (action => lc($format));
1464
1465 # feed links are possible only for project views
1466 return unless (defined $project);
1467 # some views should link to OPML, or to generic project feed,
1468 # or don't have specific feed yet (so they should use generic)
1469 return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
1470
1471 my $branch;
1472 # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
1473 # from tag links; this also makes possible to detect branch links
1474 if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
1475 (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) {
1476 $branch = $1;
1477 }
1478 # find log type for feed description (title)
1479 my $type = 'log';
1480 if (defined $file_name) {
1481 $type = "history of $file_name";
1482 $type .= "/" if ($action eq 'tree');
1483 $type .= " on '$branch'" if (defined $branch);
1484 } else {
1485 $type = "log of $branch" if (defined $branch);
1486 }
1487
1488 $res{-title} = $type;
1489 $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
1490 $res{'file_name'} = $file_name;
1491
1492 return %res;
1493}
1494
Jakub Narebski717b8312006-07-31 21:22:15 +02001495## ----------------------------------------------------------------------
1496## git utility subroutines, invoking git commands
1497
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001498# returns path to the core git executable and the --git-dir parameter as list
1499sub git_cmd {
1500 return $GIT, '--git-dir='.$git_dir;
1501}
1502
Lea Wiemann516381d2008-06-17 23:46:35 +02001503# quote the given arguments for passing them to the shell
1504# quote_command("command", "arg 1", "arg with ' and ! characters")
1505# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
1506# Try to avoid using this function wherever possible.
1507sub quote_command {
1508 return join(' ',
1509 map( { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ ));
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001510}
1511
Jakub Narebski717b8312006-07-31 21:22:15 +02001512# get HEAD ref of given project as hash
Jakub Narebski847e01f2006-08-14 02:05:47 +02001513sub git_get_head_hash {
Jakub Narebski717b8312006-07-31 21:22:15 +02001514 my $project = shift;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001515 my $o_git_dir = $git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02001516 my $retval = undef;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001517 $git_dir = "$projectroot/$project";
1518 if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
Jakub Narebski717b8312006-07-31 21:22:15 +02001519 my $head = <$fd>;
1520 close $fd;
1521 if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
1522 $retval = $1;
1523 }
1524 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001525 if (defined $o_git_dir) {
1526 $git_dir = $o_git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02001527 }
1528 return $retval;
1529}
1530
1531# get type of given object
1532sub git_get_type {
1533 my $hash = shift;
1534
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001535 open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02001536 my $type = <$fd>;
1537 close $fd or return;
1538 chomp $type;
1539 return $type;
1540}
1541
Jakub Narebskib2019272007-11-03 00:41:19 +01001542# repository configuration
1543our $config_file = '';
1544our %config;
1545
1546# store multiple values for single key as anonymous array reference
1547# single values stored directly in the hash, not as [ <value> ]
1548sub hash_set_multi {
1549 my ($hash, $key, $value) = @_;
1550
1551 if (!exists $hash->{$key}) {
1552 $hash->{$key} = $value;
1553 } elsif (!ref $hash->{$key}) {
1554 $hash->{$key} = [ $hash->{$key}, $value ];
1555 } else {
1556 push @{$hash->{$key}}, $value;
1557 }
1558}
1559
1560# return hash of git project configuration
1561# optionally limited to some section, e.g. 'gitweb'
1562sub git_parse_project_config {
1563 my $section_regexp = shift;
1564 my %config;
1565
1566 local $/ = "\0";
1567
1568 open my $fh, "-|", git_cmd(), "config", '-z', '-l',
1569 or return;
1570
1571 while (my $keyval = <$fh>) {
1572 chomp $keyval;
1573 my ($key, $value) = split(/\n/, $keyval, 2);
1574
1575 hash_set_multi(\%config, $key, $value)
1576 if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
1577 }
1578 close $fh;
1579
1580 return %config;
1581}
1582
1583# convert config value to boolean, 'true' or 'false'
1584# no value, number > 0, 'true' and 'yes' values are true
1585# rest of values are treated as false (never as error)
1586sub config_to_bool {
1587 my $val = shift;
1588
1589 # strip leading and trailing whitespace
1590 $val =~ s/^\s+//;
1591 $val =~ s/\s+$//;
1592
1593 return (!defined $val || # section.key
1594 ($val =~ /^\d+$/ && $val) || # section.key = 1
1595 ($val =~ /^(?:true|yes)$/i)); # section.key = true
1596}
1597
1598# convert config value to simple decimal number
1599# an optional value suffix of 'k', 'm', or 'g' will cause the value
1600# to be multiplied by 1024, 1048576, or 1073741824
1601sub config_to_int {
1602 my $val = shift;
1603
1604 # strip leading and trailing whitespace
1605 $val =~ s/^\s+//;
1606 $val =~ s/\s+$//;
1607
1608 if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
1609 $unit = lc($unit);
1610 # unknown unit is treated as 1
1611 return $num * ($unit eq 'g' ? 1073741824 :
1612 $unit eq 'm' ? 1048576 :
1613 $unit eq 'k' ? 1024 : 1);
1614 }
1615 return $val;
1616}
1617
1618# convert config value to array reference, if needed
1619sub config_to_multi {
1620 my $val = shift;
1621
Jakub Narebskid76a5852007-12-20 10:48:09 +01001622 return ref($val) ? $val : (defined($val) ? [ $val ] : []);
Jakub Narebskib2019272007-11-03 00:41:19 +01001623}
1624
Jakub Narebski717b8312006-07-31 21:22:15 +02001625sub git_get_project_config {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05301626 my ($key, $type) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02001627
Jakub Narebskib2019272007-11-03 00:41:19 +01001628 # key sanity check
Jakub Narebski717b8312006-07-31 21:22:15 +02001629 return unless ($key);
1630 $key =~ s/^gitweb\.//;
1631 return if ($key =~ m/\W/);
1632
Jakub Narebskib2019272007-11-03 00:41:19 +01001633 # type sanity check
1634 if (defined $type) {
1635 $type =~ s/^--//;
1636 $type = undef
1637 unless ($type eq 'bool' || $type eq 'int');
1638 }
1639
1640 # get config
1641 if (!defined $config_file ||
1642 $config_file ne "$git_dir/config") {
1643 %config = git_parse_project_config('gitweb');
1644 $config_file = "$git_dir/config";
1645 }
1646
1647 # ensure given type
1648 if (!defined $type) {
1649 return $config{"gitweb.$key"};
1650 } elsif ($type eq 'bool') {
1651 # backward compatibility: 'git config --bool' returns true/false
1652 return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
1653 } elsif ($type eq 'int') {
1654 return config_to_int($config{"gitweb.$key"});
1655 }
1656 return $config{"gitweb.$key"};
Jakub Narebski717b8312006-07-31 21:22:15 +02001657}
1658
Jakub Narebski717b8312006-07-31 21:22:15 +02001659# get hash of given path at given ref
1660sub git_get_hash_by_path {
1661 my $base = shift;
1662 my $path = shift || return undef;
Jakub Narebski1d782b02006-09-21 18:09:12 +02001663 my $type = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02001664
Jakub Narebski4b02f482006-09-26 01:54:24 +02001665 $path =~ s,/+$,,;
Jakub Narebski717b8312006-07-31 21:22:15 +02001666
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001667 open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
Lea Wiemann074afaa2008-06-19 22:03:21 +02001668 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski717b8312006-07-31 21:22:15 +02001669 my $line = <$fd>;
1670 close $fd or return undef;
1671
Jakub Narebski198a2a82007-05-12 21:16:34 +02001672 if (!defined $line) {
1673 # there is no tree or hash given by $path at $base
1674 return undef;
1675 }
1676
Jakub Narebski717b8312006-07-31 21:22:15 +02001677 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01001678 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
Jakub Narebski1d782b02006-09-21 18:09:12 +02001679 if (defined $type && $type ne $2) {
1680 # type doesn't match
1681 return undef;
1682 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001683 return $3;
1684}
1685
Jakub Narebskied224de2007-05-07 01:10:04 +02001686# get path of entry with given hash at given tree-ish (ref)
1687# used to get 'from' filename for combined diff (merge commit) for renames
1688sub git_get_path_by_hash {
1689 my $base = shift || return;
1690 my $hash = shift || return;
1691
1692 local $/ = "\0";
1693
1694 open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
1695 or return undef;
1696 while (my $line = <$fd>) {
1697 chomp $line;
1698
1699 #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
1700 #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
1701 if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
1702 close $fd;
1703 return $1;
1704 }
1705 }
1706 close $fd;
1707 return undef;
1708}
1709
Jakub Narebski717b8312006-07-31 21:22:15 +02001710## ......................................................................
1711## git utility functions, directly accessing git repository
1712
Jakub Narebski847e01f2006-08-14 02:05:47 +02001713sub git_get_project_description {
Jakub Narebski717b8312006-07-31 21:22:15 +02001714 my $path = shift;
1715
Jakub Narebski0e121a22007-11-03 00:41:20 +01001716 $git_dir = "$projectroot/$path";
Bruno Ribasc1dcf7e2008-01-30 03:37:56 -02001717 open my $fd, "$git_dir/description"
Jakub Narebski0e121a22007-11-03 00:41:20 +01001718 or return git_get_project_config('description');
Jakub Narebski717b8312006-07-31 21:22:15 +02001719 my $descr = <$fd>;
1720 close $fd;
Junio C Hamano2eb54ef2007-05-16 21:04:16 -07001721 if (defined $descr) {
1722 chomp $descr;
1723 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001724 return $descr;
1725}
1726
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02001727sub git_get_project_url_list {
1728 my $path = shift;
1729
Jakub Narebski0e121a22007-11-03 00:41:20 +01001730 $git_dir = "$projectroot/$path";
Bruno Ribas201945e2008-02-06 15:15:12 -02001731 open my $fd, "$git_dir/cloneurl"
Jakub Narebski0e121a22007-11-03 00:41:20 +01001732 or return wantarray ?
1733 @{ config_to_multi(git_get_project_config('url')) } :
1734 config_to_multi(git_get_project_config('url'));
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02001735 my @git_project_url_list = map { chomp; $_ } <$fd>;
1736 close $fd;
1737
1738 return wantarray ? @git_project_url_list : \@git_project_url_list;
1739}
1740
Jakub Narebski847e01f2006-08-14 02:05:47 +02001741sub git_get_projects_list {
Petr Baudise30496d2006-10-24 05:33:17 +02001742 my ($filter) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02001743 my @list;
1744
Petr Baudise30496d2006-10-24 05:33:17 +02001745 $filter ||= '';
1746 $filter =~ s/\.git$//;
1747
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001748 my ($check_forks) = gitweb_check_feature('forks');
1749
Jakub Narebski717b8312006-07-31 21:22:15 +02001750 if (-d $projects_list) {
1751 # search in directory
Petr Baudise30496d2006-10-24 05:33:17 +02001752 my $dir = $projects_list . ($filter ? "/$filter" : '');
Aneesh Kumar K.V6768d6b2006-11-03 10:41:45 +05301753 # remove the trailing "/"
1754 $dir =~ s!/+$!!;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001755 my $pfxlen = length("$dir");
Luke Luca5e9492007-10-16 20:45:25 -07001756 my $pfxdepth = ($dir =~ tr!/!!);
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001757
1758 File::Find::find({
1759 follow_fast => 1, # follow symbolic links
Junio C Hamanod20602e2007-07-27 01:23:03 -07001760 follow_skip => 2, # ignore duplicates
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001761 dangling_symlinks => 0, # ignore dangling symlinks, silently
1762 wanted => sub {
1763 # skip project-list toplevel, if we get it.
1764 return if (m!^[/.]$!);
1765 # only directories can be git repositories
1766 return unless (-d $_);
Luke Luca5e9492007-10-16 20:45:25 -07001767 # don't traverse too deep (Find is super slow on os x)
1768 if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
1769 $File::Find::prune = 1;
1770 return;
1771 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001772
1773 my $subdir = substr($File::Find::name, $pfxlen + 1);
1774 # we check related file in $projectroot
Petr Baudise30496d2006-10-24 05:33:17 +02001775 if ($check_forks and $subdir =~ m#/.#) {
1776 $File::Find::prune = 1;
1777 } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
1778 push @list, { path => ($filter ? "$filter/" : '') . $subdir };
Jakub Narebskic0011ff2006-09-14 22:18:59 +02001779 $File::Find::prune = 1;
1780 }
1781 },
1782 }, "$dir");
1783
Jakub Narebski717b8312006-07-31 21:22:15 +02001784 } elsif (-f $projects_list) {
1785 # read from file(url-encoded):
1786 # 'git%2Fgit.git Linus+Torvalds'
1787 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
1788 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001789 my %paths;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +02001790 open my ($fd), $projects_list or return;
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001791 PROJECT:
Jakub Narebski717b8312006-07-31 21:22:15 +02001792 while (my $line = <$fd>) {
1793 chomp $line;
1794 my ($path, $owner) = split ' ', $line;
1795 $path = unescape($path);
1796 $owner = unescape($owner);
1797 if (!defined $path) {
1798 next;
1799 }
Junio C Hamano83ee94c2006-11-07 22:37:17 -08001800 if ($filter ne '') {
1801 # looking for forks;
1802 my $pfx = substr($path, 0, length($filter));
1803 if ($pfx ne $filter) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001804 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08001805 }
1806 my $sfx = substr($path, length($filter));
1807 if ($sfx !~ /^\/.*\.git$/) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001808 next PROJECT;
1809 }
1810 } elsif ($check_forks) {
1811 PATH:
1812 foreach my $filter (keys %paths) {
1813 # looking for forks;
1814 my $pfx = substr($path, 0, length($filter));
1815 if ($pfx ne $filter) {
1816 next PATH;
1817 }
1818 my $sfx = substr($path, length($filter));
1819 if ($sfx !~ /^\/.*\.git$/) {
1820 next PATH;
1821 }
1822 # is a fork, don't include it in
1823 # the list
1824 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08001825 }
1826 }
Junio C Hamano2172ce42006-10-03 02:30:47 -07001827 if (check_export_ok("$projectroot/$path")) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001828 my $pr = {
1829 path => $path,
Martin Koegler00f429a2007-06-03 17:42:44 +02001830 owner => to_utf8($owner),
Jakub Narebski717b8312006-07-31 21:22:15 +02001831 };
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02001832 push @list, $pr;
1833 (my $forks_path = $path) =~ s/\.git$//;
1834 $paths{$forks_path}++;
Jakub Narebski717b8312006-07-31 21:22:15 +02001835 }
1836 }
1837 close $fd;
1838 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001839 return @list;
1840}
1841
Junio C Hamano47852452007-07-03 22:10:42 -07001842our $gitweb_project_owner = undef;
1843sub git_get_project_list_from_file {
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001844
Junio C Hamano47852452007-07-03 22:10:42 -07001845 return if (defined $gitweb_project_owner);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001846
Junio C Hamano47852452007-07-03 22:10:42 -07001847 $gitweb_project_owner = {};
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001848 # read from file (url-encoded):
1849 # 'git%2Fgit.git Linus+Torvalds'
1850 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
1851 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
1852 if (-f $projects_list) {
1853 open (my $fd , $projects_list);
1854 while (my $line = <$fd>) {
1855 chomp $line;
1856 my ($pr, $ow) = split ' ', $line;
1857 $pr = unescape($pr);
1858 $ow = unescape($ow);
Junio C Hamano47852452007-07-03 22:10:42 -07001859 $gitweb_project_owner->{$pr} = to_utf8($ow);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001860 }
1861 close $fd;
1862 }
Junio C Hamano47852452007-07-03 22:10:42 -07001863}
1864
1865sub git_get_project_owner {
1866 my $project = shift;
1867 my $owner;
1868
1869 return undef unless $project;
Bruno Ribasb59012e2008-02-08 14:38:04 -02001870 $git_dir = "$projectroot/$project";
Junio C Hamano47852452007-07-03 22:10:42 -07001871
1872 if (!defined $gitweb_project_owner) {
1873 git_get_project_list_from_file();
1874 }
1875
1876 if (exists $gitweb_project_owner->{$project}) {
1877 $owner = $gitweb_project_owner->{$project};
1878 }
Bruno Ribasb59012e2008-02-08 14:38:04 -02001879 if (!defined $owner){
1880 $owner = git_get_project_config('owner');
1881 }
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001882 if (!defined $owner) {
Bruno Ribasb59012e2008-02-08 14:38:04 -02001883 $owner = get_file_owner("$git_dir");
Jakub Narebski1e0cf032006-08-14 02:10:06 +02001884 }
1885
1886 return $owner;
1887}
1888
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001889sub git_get_last_activity {
1890 my ($path) = @_;
1891 my $fd;
1892
1893 $git_dir = "$projectroot/$path";
1894 open($fd, "-|", git_cmd(), 'for-each-ref',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00001895 '--format=%(committer)',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001896 '--sort=-committerdate',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00001897 '--count=1',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001898 'refs/heads') or return;
1899 my $most_recent = <$fd>;
1900 close $fd or return;
Jakub Narebski785cdea2007-05-13 12:39:22 +02001901 if (defined $most_recent &&
1902 $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001903 my $timestamp = $1;
1904 my $age = time - $timestamp;
1905 return ($age, age_string($age));
1906 }
Matt McCutchenc9563952007-06-28 18:15:22 -04001907 return (undef, undef);
Jakub Narebskic60c56c2006-10-28 19:43:40 +02001908}
1909
Jakub Narebski847e01f2006-08-14 02:05:47 +02001910sub git_get_references {
Jakub Narebski717b8312006-07-31 21:22:15 +02001911 my $type = shift || "";
1912 my %refs;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01001913 # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
1914 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
1915 open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
1916 ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
Jakub Narebski9704d752006-09-19 14:31:49 +02001917 or return;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001918
Jakub Narebski717b8312006-07-31 21:22:15 +02001919 while (my $line = <$fd>) {
1920 chomp $line;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01001921 if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001922 if (defined $refs{$1}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001923 push @{$refs{$1}}, $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02001924 } else {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001925 $refs{$1} = [ $2 ];
Jakub Narebski717b8312006-07-31 21:22:15 +02001926 }
1927 }
1928 }
1929 close $fd or return;
1930 return \%refs;
1931}
1932
Jakub Narebski56a322f2006-08-24 19:41:23 +02001933sub git_get_rev_name_tags {
1934 my $hash = shift || return undef;
1935
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001936 open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
Jakub Narebski56a322f2006-08-24 19:41:23 +02001937 or return;
1938 my $name_rev = <$fd>;
1939 close $fd;
1940
1941 if ($name_rev =~ m|^$hash tags/(.*)$|) {
1942 return $1;
1943 } else {
1944 # catches also '$hash undefined' output
1945 return undef;
1946 }
1947}
1948
Jakub Narebski717b8312006-07-31 21:22:15 +02001949## ----------------------------------------------------------------------
1950## parse to hash functions
1951
Jakub Narebski847e01f2006-08-14 02:05:47 +02001952sub parse_date {
Jakub Narebski717b8312006-07-31 21:22:15 +02001953 my $epoch = shift;
1954 my $tz = shift || "-0000";
1955
1956 my %date;
1957 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
1958 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
1959 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
1960 $date{'hour'} = $hour;
1961 $date{'minute'} = $min;
1962 $date{'mday'} = $mday;
1963 $date{'day'} = $days[$wday];
1964 $date{'month'} = $months[$mon];
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01001965 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
1966 $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
Jakub Narebski952c65f2006-08-22 16:52:50 +02001967 $date{'mday-time'} = sprintf "%d %s %02d:%02d",
1968 $mday, $months[$mon], $hour ,$min;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01001969 $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
Vincent Zanottia62d6d82007-11-10 19:55:27 +01001970 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
Jakub Narebski717b8312006-07-31 21:22:15 +02001971
1972 $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
1973 my $local = $epoch + ((int $1 + ($2/60)) * 3600);
1974 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
1975 $date{'hour_local'} = $hour;
1976 $date{'minute_local'} = $min;
1977 $date{'tz_local'} = $tz;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01001978 $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
1979 1900+$year, $mon+1, $mday,
1980 $hour, $min, $sec, $tz);
Jakub Narebski717b8312006-07-31 21:22:15 +02001981 return %date;
1982}
1983
Jakub Narebski847e01f2006-08-14 02:05:47 +02001984sub parse_tag {
Jakub Narebski717b8312006-07-31 21:22:15 +02001985 my $tag_id = shift;
1986 my %tag;
1987 my @comment;
1988
Dennis Stosberg25691fb2006-08-28 17:49:58 +02001989 open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02001990 $tag{'id'} = $tag_id;
1991 while (my $line = <$fd>) {
1992 chomp $line;
1993 if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
1994 $tag{'object'} = $1;
1995 } elsif ($line =~ m/^type (.+)$/) {
1996 $tag{'type'} = $1;
1997 } elsif ($line =~ m/^tag (.+)$/) {
1998 $tag{'name'} = $1;
1999 } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
2000 $tag{'author'} = $1;
2001 $tag{'epoch'} = $2;
2002 $tag{'tz'} = $3;
2003 } elsif ($line =~ m/--BEGIN/) {
2004 push @comment, $line;
2005 last;
2006 } elsif ($line eq "") {
2007 last;
2008 }
2009 }
2010 push @comment, <$fd>;
2011 $tag{'comment'} = \@comment;
2012 close $fd or return;
2013 if (!defined $tag{'name'}) {
2014 return
2015 };
2016 return %tag
2017}
2018
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002019sub parse_commit_text {
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002020 my ($commit_text, $withparents) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002021 my @commit_lines = split '\n', $commit_text;
Jakub Narebski717b8312006-07-31 21:22:15 +02002022 my %co;
2023
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002024 pop @commit_lines; # Remove '\0'
2025
Jakub Narebski198a2a82007-05-12 21:16:34 +02002026 if (! @commit_lines) {
2027 return;
2028 }
2029
Jakub Narebski717b8312006-07-31 21:22:15 +02002030 my $header = shift @commit_lines;
Jakub Narebski198a2a82007-05-12 21:16:34 +02002031 if ($header !~ m/^[0-9a-fA-F]{40}/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002032 return;
2033 }
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002034 ($co{'id'}, my @parents) = split ' ', $header;
Jakub Narebski717b8312006-07-31 21:22:15 +02002035 while (my $line = shift @commit_lines) {
2036 last if $line eq "\n";
2037 if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
2038 $co{'tree'} = $1;
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002039 } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002040 push @parents, $1;
Jakub Narebski717b8312006-07-31 21:22:15 +02002041 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
2042 $co{'author'} = $1;
2043 $co{'author_epoch'} = $2;
2044 $co{'author_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002045 if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
2046 $co{'author_name'} = $1;
2047 $co{'author_email'} = $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02002048 } else {
2049 $co{'author_name'} = $co{'author'};
2050 }
2051 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
2052 $co{'committer'} = $1;
2053 $co{'committer_epoch'} = $2;
2054 $co{'committer_tz'} = $3;
2055 $co{'committer_name'} = $co{'committer'};
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002056 if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
2057 $co{'committer_name'} = $1;
2058 $co{'committer_email'} = $2;
2059 } else {
2060 $co{'committer_name'} = $co{'committer'};
2061 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002062 }
2063 }
2064 if (!defined $co{'tree'}) {
2065 return;
2066 };
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002067 $co{'parents'} = \@parents;
2068 $co{'parent'} = $parents[0];
Jakub Narebski717b8312006-07-31 21:22:15 +02002069
2070 foreach my $title (@commit_lines) {
2071 $title =~ s/^ //;
2072 if ($title ne "") {
2073 $co{'title'} = chop_str($title, 80, 5);
2074 # remove leading stuff of merges to make the interesting part visible
2075 if (length($title) > 50) {
2076 $title =~ s/^Automatic //;
2077 $title =~ s/^merge (of|with) /Merge ... /i;
2078 if (length($title) > 50) {
2079 $title =~ s/(http|rsync):\/\///;
2080 }
2081 if (length($title) > 50) {
2082 $title =~ s/(master|www|rsync)\.//;
2083 }
2084 if (length($title) > 50) {
2085 $title =~ s/kernel.org:?//;
2086 }
2087 if (length($title) > 50) {
2088 $title =~ s/\/pub\/scm//;
2089 }
2090 }
2091 $co{'title_short'} = chop_str($title, 50, 5);
2092 last;
2093 }
2094 }
Petr Baudis7e0fe5c2006-10-06 18:55:04 +02002095 if ($co{'title'} eq "") {
2096 $co{'title'} = $co{'title_short'} = '(no commit message)';
2097 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002098 # remove added spaces
2099 foreach my $line (@commit_lines) {
2100 $line =~ s/^ //;
2101 }
2102 $co{'comment'} = \@commit_lines;
2103
2104 my $age = time - $co{'committer_epoch'};
2105 $co{'age'} = $age;
2106 $co{'age_string'} = age_string($age);
2107 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
2108 if ($age > 60*60*24*7*2) {
2109 $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2110 $co{'age_string_age'} = $co{'age_string'};
2111 } else {
2112 $co{'age_string_date'} = $co{'age_string'};
2113 $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2114 }
2115 return %co;
2116}
2117
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002118sub parse_commit {
2119 my ($commit_id) = @_;
2120 my %co;
2121
2122 local $/ = "\0";
2123
2124 open my $fd, "-|", git_cmd(), "rev-list",
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002125 "--parents",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002126 "--header",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002127 "--max-count=1",
2128 $commit_id,
2129 "--",
Lea Wiemann074afaa2008-06-19 22:03:21 +02002130 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002131 %co = parse_commit_text(<$fd>, 1);
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002132 close $fd;
2133
2134 return %co;
2135}
2136
2137sub parse_commits {
Jakub Narebski311e5522008-02-26 13:22:06 +01002138 my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002139 my @cos;
2140
2141 $maxcount ||= 1;
2142 $skip ||= 0;
2143
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002144 local $/ = "\0";
2145
2146 open my $fd, "-|", git_cmd(), "rev-list",
2147 "--header",
Jakub Narebski311e5522008-02-26 13:22:06 +01002148 @args,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002149 ("--max-count=" . $maxcount),
Robert Fitzsimonsf47efbb2006-12-24 14:31:49 +00002150 ("--skip=" . $skip),
Miklos Vajna868bc062007-07-12 20:39:27 +02002151 @extra_options,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002152 $commit_id,
2153 "--",
2154 ($filename ? ($filename) : ())
Lea Wiemann074afaa2008-06-19 22:03:21 +02002155 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002156 while (my $line = <$fd>) {
2157 my %co = parse_commit_text($line);
2158 push @cos, \%co;
2159 }
2160 close $fd;
2161
2162 return wantarray ? @cos : \@cos;
2163}
2164
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002165# parse line of git-diff-tree "raw" output
Jakub Narebski740e67f2006-08-21 23:07:00 +02002166sub parse_difftree_raw_line {
2167 my $line = shift;
2168 my %res;
2169
2170 # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
2171 # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
2172 if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
2173 $res{'from_mode'} = $1;
2174 $res{'to_mode'} = $2;
2175 $res{'from_id'} = $3;
2176 $res{'to_id'} = $4;
Jakub Narebski4ed4a342008-04-05 21:13:24 +01002177 $res{'status'} = $5;
Jakub Narebski740e67f2006-08-21 23:07:00 +02002178 $res{'similarity'} = $6;
2179 if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02002180 ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002181 } else {
Jakub Narebski9d301452007-11-01 12:38:08 +01002182 $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02002183 }
2184 }
Jakub Narebski78bc4032007-05-07 01:10:03 +02002185 # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
2186 # combined diff (for merge commit)
2187 elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
2188 $res{'nparents'} = length($1);
2189 $res{'from_mode'} = [ split(' ', $2) ];
2190 $res{'to_mode'} = pop @{$res{'from_mode'}};
2191 $res{'from_id'} = [ split(' ', $3) ];
2192 $res{'to_id'} = pop @{$res{'from_id'}};
2193 $res{'status'} = [ split('', $4) ];
2194 $res{'to_file'} = unquote($5);
2195 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002196 # 'c512b523472485aef4fff9e57b229d9d243c967f'
Jakub Narebski0edcb372006-08-31 00:36:04 +02002197 elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
2198 $res{'commit'} = $1;
2199 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02002200
2201 return wantarray ? %res : \%res;
2202}
2203
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002204# wrapper: return parsed line of git-diff-tree "raw" output
2205# (the argument might be raw line, or parsed info)
2206sub parsed_difftree_line {
2207 my $line_or_ref = shift;
2208
2209 if (ref($line_or_ref) eq "HASH") {
2210 # pre-parsed (or generated by hand)
2211 return $line_or_ref;
2212 } else {
2213 return parse_difftree_raw_line($line_or_ref);
2214 }
2215}
2216
Jakub Narebskicb849b42006-08-31 00:32:15 +02002217# parse line of git-ls-tree output
2218sub parse_ls_tree_line ($;%) {
2219 my $line = shift;
2220 my %opts = @_;
2221 my %res;
2222
2223 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002224 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
Jakub Narebskicb849b42006-08-31 00:32:15 +02002225
2226 $res{'mode'} = $1;
2227 $res{'type'} = $2;
2228 $res{'hash'} = $3;
2229 if ($opts{'-z'}) {
2230 $res{'name'} = $4;
2231 } else {
2232 $res{'name'} = unquote($4);
2233 }
2234
2235 return wantarray ? %res : \%res;
2236}
2237
Jakub Narebski90921742007-06-08 13:27:42 +02002238# generates _two_ hashes, references to which are passed as 2 and 3 argument
2239sub parse_from_to_diffinfo {
2240 my ($diffinfo, $from, $to, @parents) = @_;
2241
2242 if ($diffinfo->{'nparents'}) {
2243 # combined diff
2244 $from->{'file'} = [];
2245 $from->{'href'} = [];
2246 fill_from_file_info($diffinfo, @parents)
2247 unless exists $diffinfo->{'from_file'};
2248 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
Jakub Narebski9d301452007-11-01 12:38:08 +01002249 $from->{'file'}[$i] =
2250 defined $diffinfo->{'from_file'}[$i] ?
2251 $diffinfo->{'from_file'}[$i] :
2252 $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002253 if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
2254 $from->{'href'}[$i] = href(action=>"blob",
2255 hash_base=>$parents[$i],
2256 hash=>$diffinfo->{'from_id'}[$i],
2257 file_name=>$from->{'file'}[$i]);
2258 } else {
2259 $from->{'href'}[$i] = undef;
2260 }
2261 }
2262 } else {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01002263 # ordinary (not combined) diff
Jakub Narebski9d301452007-11-01 12:38:08 +01002264 $from->{'file'} = $diffinfo->{'from_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002265 if ($diffinfo->{'status'} ne "A") { # not new (added) file
2266 $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
2267 hash=>$diffinfo->{'from_id'},
2268 file_name=>$from->{'file'});
2269 } else {
2270 delete $from->{'href'};
2271 }
2272 }
2273
Jakub Narebski9d301452007-11-01 12:38:08 +01002274 $to->{'file'} = $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02002275 if (!is_deleted($diffinfo)) { # file exists in result
2276 $to->{'href'} = href(action=>"blob", hash_base=>$hash,
2277 hash=>$diffinfo->{'to_id'},
2278 file_name=>$to->{'file'});
2279 } else {
2280 delete $to->{'href'};
2281 }
2282}
2283
Jakub Narebski717b8312006-07-31 21:22:15 +02002284## ......................................................................
2285## parse to array of hashes functions
2286
Jakub Narebskicd146402006-11-02 20:23:11 +01002287sub git_get_heads_list {
2288 my $limit = shift;
2289 my @headslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002290
Jakub Narebskicd146402006-11-02 20:23:11 +01002291 open my $fd, '-|', git_cmd(), 'for-each-ref',
2292 ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
2293 '--format=%(objectname) %(refname) %(subject)%00%(committer)',
2294 'refs/heads'
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002295 or return;
2296 while (my $line = <$fd>) {
Jakub Narebskicd146402006-11-02 20:23:11 +01002297 my %ref_item;
Jakub Narebski120ddde2006-09-19 14:33:22 +02002298
Jakub Narebskicd146402006-11-02 20:23:11 +01002299 chomp $line;
2300 my ($refinfo, $committerinfo) = split(/\0/, $line);
2301 my ($hash, $name, $title) = split(' ', $refinfo, 3);
2302 my ($committer, $epoch, $tz) =
2303 ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01002304 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01002305 $name =~ s!^refs/heads/!!;
2306
2307 $ref_item{'name'} = $name;
2308 $ref_item{'id'} = $hash;
2309 $ref_item{'title'} = $title || '(no commit message)';
2310 $ref_item{'epoch'} = $epoch;
2311 if ($epoch) {
2312 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
2313 } else {
2314 $ref_item{'age'} = "unknown";
Jakub Narebski717b8312006-07-31 21:22:15 +02002315 }
Jakub Narebskicd146402006-11-02 20:23:11 +01002316
2317 push @headslist, \%ref_item;
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002318 }
2319 close $fd;
Junio C Hamano7a13b992006-07-31 19:18:34 -07002320
Jakub Narebskicd146402006-11-02 20:23:11 +01002321 return wantarray ? @headslist : \@headslist;
2322}
Jakub Narebskic83a77e2006-09-15 03:43:28 +02002323
Jakub Narebskicd146402006-11-02 20:23:11 +01002324sub git_get_tags_list {
2325 my $limit = shift;
2326 my @tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002327
Jakub Narebskicd146402006-11-02 20:23:11 +01002328 open my $fd, '-|', git_cmd(), 'for-each-ref',
2329 ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
2330 '--format=%(objectname) %(objecttype) %(refname) '.
2331 '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
2332 'refs/tags'
2333 or return;
2334 while (my $line = <$fd>) {
2335 my %ref_item;
2336
2337 chomp $line;
2338 my ($refinfo, $creatorinfo) = split(/\0/, $line);
2339 my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
2340 my ($creator, $epoch, $tz) =
2341 ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01002342 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01002343 $name =~ s!^refs/tags/!!;
2344
2345 $ref_item{'type'} = $type;
2346 $ref_item{'id'} = $id;
2347 $ref_item{'name'} = $name;
2348 if ($type eq "tag") {
2349 $ref_item{'subject'} = $title;
2350 $ref_item{'reftype'} = $reftype;
2351 $ref_item{'refid'} = $refid;
2352 } else {
2353 $ref_item{'reftype'} = $type;
2354 $ref_item{'refid'} = $id;
2355 }
2356
2357 if ($type eq "tag" || $type eq "commit") {
2358 $ref_item{'epoch'} = $epoch;
2359 if ($epoch) {
2360 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
2361 } else {
2362 $ref_item{'age'} = "unknown";
2363 }
2364 }
2365
2366 push @tagslist, \%ref_item;
Jakub Narebski717b8312006-07-31 21:22:15 +02002367 }
Jakub Narebskicd146402006-11-02 20:23:11 +01002368 close $fd;
2369
2370 return wantarray ? @tagslist : \@tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02002371}
2372
2373## ----------------------------------------------------------------------
2374## filesystem-related functions
2375
2376sub get_file_owner {
2377 my $path = shift;
2378
2379 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
2380 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
2381 if (!defined $gcos) {
2382 return undef;
2383 }
2384 my $owner = $gcos;
2385 $owner =~ s/[,;].*$//;
Martin Koegler00f429a2007-06-03 17:42:44 +02002386 return to_utf8($owner);
Jakub Narebski717b8312006-07-31 21:22:15 +02002387}
2388
2389## ......................................................................
2390## mimetype related functions
2391
2392sub mimetype_guess_file {
2393 my $filename = shift;
2394 my $mimemap = shift;
2395 -r $mimemap or return undef;
2396
2397 my %mimemap;
2398 open(MIME, $mimemap) or return undef;
2399 while (<MIME>) {
Jakub Narebski618918e2006-08-14 02:15:22 +02002400 next if m/^#/; # skip comments
Jakub Narebski717b8312006-07-31 21:22:15 +02002401 my ($mime, $exts) = split(/\t+/);
Junio C Hamano46b059d2006-07-31 19:24:37 -07002402 if (defined $exts) {
2403 my @exts = split(/\s+/, $exts);
2404 foreach my $ext (@exts) {
2405 $mimemap{$ext} = $mime;
2406 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002407 }
2408 }
2409 close(MIME);
2410
Jakub Narebski80593192006-09-19 13:57:03 +02002411 $filename =~ /\.([^.]*)$/;
Jakub Narebski717b8312006-07-31 21:22:15 +02002412 return $mimemap{$1};
2413}
2414
2415sub mimetype_guess {
2416 my $filename = shift;
2417 my $mime;
2418 $filename =~ /\./ or return undef;
2419
2420 if ($mimetypes_file) {
2421 my $file = $mimetypes_file;
Jakub Narebskid5aa50d2006-08-14 02:16:33 +02002422 if ($file !~ m!^/!) { # if it is relative path
2423 # it is relative to project
2424 $file = "$projectroot/$project/$file";
2425 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002426 $mime = mimetype_guess_file($filename, $file);
2427 }
2428 $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
2429 return $mime;
2430}
2431
Jakub Narebski847e01f2006-08-14 02:05:47 +02002432sub blob_mimetype {
Jakub Narebski717b8312006-07-31 21:22:15 +02002433 my $fd = shift;
2434 my $filename = shift;
2435
2436 if ($filename) {
2437 my $mime = mimetype_guess($filename);
2438 $mime and return $mime;
2439 }
2440
2441 # just in case
2442 return $default_blob_plain_mimetype unless $fd;
2443
2444 if (-T $fd) {
Jakub Narebski7f718e82008-06-03 16:47:10 +02002445 return 'text/plain';
Jakub Narebski717b8312006-07-31 21:22:15 +02002446 } elsif (! $filename) {
2447 return 'application/octet-stream';
2448 } elsif ($filename =~ m/\.png$/i) {
2449 return 'image/png';
2450 } elsif ($filename =~ m/\.gif$/i) {
2451 return 'image/gif';
2452 } elsif ($filename =~ m/\.jpe?g$/i) {
2453 return 'image/jpeg';
2454 } else {
2455 return 'application/octet-stream';
2456 }
2457}
2458
Jakub Narebski7f718e82008-06-03 16:47:10 +02002459sub blob_contenttype {
2460 my ($fd, $file_name, $type) = @_;
2461
2462 $type ||= blob_mimetype($fd, $file_name);
2463 if ($type eq 'text/plain' && defined $default_text_plain_charset) {
2464 $type .= "; charset=$default_text_plain_charset";
2465 }
2466
2467 return $type;
2468}
2469
Jakub Narebski717b8312006-07-31 21:22:15 +02002470## ======================================================================
2471## functions printing HTML: header, footer, error page
2472
Kay Sievers12a88f22005-08-07 20:02:47 +02002473sub git_header_html {
Kay Sieversa59d4af2005-08-07 20:15:44 +02002474 my $status = shift || "200 OK";
Kay Sievers11044292005-10-19 03:18:45 +02002475 my $expires = shift;
Kay Sieversa59d4af2005-08-07 20:15:44 +02002476
Petr Baudis8be28902006-10-24 05:18:39 +02002477 my $title = "$site_name";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002478 if (defined $project) {
Martin Koegler00f429a2007-06-03 17:42:44 +02002479 $title .= " - " . to_utf8($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02002480 if (defined $action) {
2481 $title .= "/$action";
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00002482 if (defined $file_name) {
Jakub Narebski403d0902006-11-08 11:48:56 +01002483 $title .= " - " . esc_path($file_name);
Jakub Narebski7bedd9f2006-06-20 06:17:03 +00002484 if ($action eq "tree" && $file_name !~ m|/$|) {
2485 $title .= "/";
2486 }
2487 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02002488 }
2489 }
Alp Tokerf6801d62006-07-11 11:19:34 +01002490 my $content_type;
2491 # require explicit support from the UA if we are to send the page as
2492 # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
2493 # we have to do this because MSIE sometimes globs '*/*', pretending to
2494 # support xhtml+xml but choking when it gets what it asked for.
Jakub Narebski952c65f2006-08-22 16:52:50 +02002495 if (defined $cgi->http('HTTP_ACCEPT') &&
2496 $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
2497 $cgi->Accept('application/xhtml+xml') != 0) {
Alp Tokerf6801d62006-07-11 11:19:34 +01002498 $content_type = 'application/xhtml+xml';
2499 } else {
2500 $content_type = 'text/html';
2501 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02002502 print $cgi->header(-type=>$content_type, -charset => 'utf-8',
2503 -status=> $status, -expires => $expires);
Jakub Narebski45c9a752006-12-27 23:59:51 +01002504 my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
Kay Sieversa59d4af2005-08-07 20:15:44 +02002505 print <<EOF;
Kay Sievers6191f8e2005-08-07 20:19:56 +02002506<?xml version="1.0" encoding="utf-8"?>
Kay Sievers161332a2005-08-07 19:49:46 +02002507<!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 +02002508<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
Jakub Narebskid4baf9e2006-08-17 11:21:28 +02002509<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
Jakub Narebskiae20de52006-06-21 09:48:03 +02002510<!-- git core binaries version $git_version -->
Kay Sievers161332a2005-08-07 19:49:46 +02002511<head>
Alp Tokerf6801d62006-07-11 11:19:34 +01002512<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
Jakub Narebski45c9a752006-12-27 23:59:51 +01002513<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
Kay Sieversc994d622005-08-07 20:27:18 +02002514<meta name="robots" content="index, nofollow"/>
Kay Sieversb87d78d2005-08-07 20:21:04 +02002515<title>$title</title>
Kay Sievers161332a2005-08-07 19:49:46 +02002516EOF
Alan Chandlerb2d34762006-10-03 13:49:03 +01002517# print out each stylesheet that exist
2518 if (defined $stylesheet) {
2519#provides backwards capability for those people who define style sheet in a config file
2520 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
2521 } else {
2522 foreach my $stylesheet (@stylesheets) {
2523 next unless $stylesheet;
2524 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
2525 }
2526 }
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002527 if (defined $project) {
Jakub Narebski35621982008-04-20 22:09:48 +02002528 my %href_params = get_feed_info();
2529 if (!exists $href_params{'-title'}) {
2530 $href_params{'-title'} = 'log';
2531 }
2532
2533 foreach my $format qw(RSS Atom) {
2534 my $type = lc($format);
2535 my %link_attr = (
2536 '-rel' => 'alternate',
2537 '-title' => "$project - $href_params{'-title'} - $format feed",
2538 '-type' => "application/$type+xml"
2539 );
2540
2541 $href_params{'action'} = $type;
2542 $link_attr{'-href'} = href(%href_params);
2543 print "<link ".
2544 "rel=\"$link_attr{'-rel'}\" ".
2545 "title=\"$link_attr{'-title'}\" ".
2546 "href=\"$link_attr{'-href'}\" ".
2547 "type=\"$link_attr{'-type'}\" ".
2548 "/>\n";
2549
2550 $href_params{'extra_options'} = '--no-merges';
2551 $link_attr{'-href'} = href(%href_params);
2552 $link_attr{'-title'} .= ' (no merges)';
2553 print "<link ".
2554 "rel=\"$link_attr{'-rel'}\" ".
2555 "title=\"$link_attr{'-title'}\" ".
2556 "href=\"$link_attr{'-href'}\" ".
2557 "type=\"$link_attr{'-type'}\" ".
2558 "/>\n";
2559 }
2560
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002561 } else {
2562 printf('<link rel="alternate" title="%s projects list" '.
Jakub Narebski35621982008-04-20 22:09:48 +02002563 'href="%s" type="text/plain; charset=utf-8" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002564 $site_name, href(project=>undef, action=>"project_index"));
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002565 printf('<link rel="alternate" title="%s projects feeds" '.
Jakub Narebski35621982008-04-20 22:09:48 +02002566 'href="%s" type="text/x-opml" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002567 $site_name, href(project=>undef, action=>"opml"));
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002568 }
Jakub Narebski0b5deba2006-09-04 20:32:13 +02002569 if (defined $favicon) {
Jakub Narebski35621982008-04-20 22:09:48 +02002570 print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
Jakub Narebski0b5deba2006-09-04 20:32:13 +02002571 }
Jakub Narebski10161352006-08-05 13:18:58 +02002572
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02002573 print "</head>\n" .
Alan Chandlerb2d34762006-10-03 13:49:03 +01002574 "<body>\n";
2575
2576 if (-f $site_header) {
2577 open (my $fd, $site_header);
2578 print <$fd>;
2579 close $fd;
2580 }
2581
2582 print "<div class=\"page_header\">\n" .
Jakub Narebski9a7a62f2006-10-06 12:31:05 +02002583 $cgi->a({-href => esc_url($logo_url),
2584 -title => $logo_label},
2585 qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
Jakub Narebskif93bff82006-09-26 01:58:41 +02002586 print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002587 if (defined $project) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002588 print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
Kay Sieversb87d78d2005-08-07 20:21:04 +02002589 if (defined $action) {
2590 print " / $action";
2591 }
Kay Sievers19806692005-08-07 20:26:27 +02002592 print "\n";
Robert Fitzsimons6be93512006-12-23 03:35:16 +00002593 }
Petr Baudisd77b5672007-05-17 04:24:19 +02002594 print "</div>\n";
2595
Robert Fitzsimons6be93512006-12-23 03:35:16 +00002596 my ($have_search) = gitweb_check_feature('search');
Jakub Narebskif70dda22008-06-02 11:54:41 +02002597 if (defined $project && $have_search) {
Kay Sievers19806692005-08-07 20:26:27 +02002598 if (!defined $searchtext) {
2599 $searchtext = "";
2600 }
Kay Sieversc39e47d2005-09-17 03:00:21 +02002601 my $search_hash;
Timo Hirvonen4c5c2022006-06-20 16:41:05 +03002602 if (defined $hash_base) {
2603 $search_hash = $hash_base;
2604 } elsif (defined $hash) {
Kay Sieversc39e47d2005-09-17 03:00:21 +02002605 $search_hash = $hash;
2606 } else {
Jakub Narebski8adc4bd2006-06-22 08:52:57 +02002607 $search_hash = "HEAD";
Kay Sieversc39e47d2005-09-17 03:00:21 +02002608 }
Matt McCutchen40375a82007-06-28 14:57:07 -04002609 my $action = $my_uri;
2610 my ($use_pathinfo) = gitweb_check_feature('pathinfo');
2611 if ($use_pathinfo) {
martin f. krafft85d17a12008-04-20 23:23:38 +02002612 $action .= "/".esc_url($project);
Matt McCutchen40375a82007-06-28 14:57:07 -04002613 }
Matt McCutchen40375a82007-06-28 14:57:07 -04002614 print $cgi->startform(-method => "get", -action => $action) .
Kay Sieversc994d622005-08-07 20:27:18 +02002615 "<div class=\"search\">\n" .
Jakub Narebskif70dda22008-06-02 11:54:41 +02002616 (!$use_pathinfo &&
2617 $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
2618 $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
2619 $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
Petr Baudis88ad7292006-10-24 05:15:46 +02002620 $cgi->popup_menu(-name => 'st', -default => 'commit',
Petr Baudise7738552007-05-17 04:31:12 +02002621 -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
Petr Baudis88ad7292006-10-24 05:15:46 +02002622 $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
2623 " search:\n",
Kay Sieversc994d622005-08-07 20:27:18 +02002624 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
Petr Baudis0e559912008-02-26 13:22:08 +01002625 "<span title=\"Extended regular expression\">" .
2626 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
2627 -checked => $search_use_regexp) .
2628 "</span>" .
Kay Sieversc994d622005-08-07 20:27:18 +02002629 "</div>" .
2630 $cgi->end_form() . "\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02002631 }
Kay Sievers161332a2005-08-07 19:49:46 +02002632}
2633
Kay Sievers12a88f22005-08-07 20:02:47 +02002634sub git_footer_html {
Jakub Narebski35621982008-04-20 22:09:48 +02002635 my $feed_class = 'rss_logo';
2636
Kay Sievers6191f8e2005-08-07 20:19:56 +02002637 print "<div class=\"page_footer\">\n";
Kay Sieversb87d78d2005-08-07 20:21:04 +02002638 if (defined $project) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02002639 my $descr = git_get_project_description($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02002640 if (defined $descr) {
Kay Sievers40c13812005-11-19 17:41:29 +01002641 print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
Kay Sievers6191f8e2005-08-07 20:19:56 +02002642 }
Jakub Narebski35621982008-04-20 22:09:48 +02002643
2644 my %href_params = get_feed_info();
2645 if (!%href_params) {
2646 $feed_class .= ' generic';
2647 }
2648 $href_params{'-title'} ||= 'log';
2649
2650 foreach my $format qw(RSS Atom) {
2651 $href_params{'action'} = lc($format);
2652 print $cgi->a({-href => href(%href_params),
2653 -title => "$href_params{'-title'} $format feed",
2654 -class => $feed_class}, $format)."\n";
2655 }
2656
Kay Sieversc994d622005-08-07 20:27:18 +02002657 } else {
Jakub Narebskia1565c42006-09-15 19:30:34 +02002658 print $cgi->a({-href => href(project=>undef, action=>"opml"),
Jakub Narebski35621982008-04-20 22:09:48 +02002659 -class => $feed_class}, "OPML") . " ";
Jakub Narebski9d0734a2006-09-15 11:11:33 +02002660 print $cgi->a({-href => href(project=>undef, action=>"project_index"),
Jakub Narebski35621982008-04-20 22:09:48 +02002661 -class => $feed_class}, "TXT") . "\n";
Kay Sieversff7669a2005-08-07 20:13:02 +02002662 }
Jakub Narebski35621982008-04-20 22:09:48 +02002663 print "</div>\n"; # class="page_footer"
Alan Chandlerb2d34762006-10-03 13:49:03 +01002664
2665 if (-f $site_footer) {
2666 open (my $fd, $site_footer);
2667 print <$fd>;
2668 close $fd;
2669 }
2670
2671 print "</body>\n" .
Kay Sievers9cd3d982005-08-07 20:17:42 +02002672 "</html>";
Kay Sievers161332a2005-08-07 19:49:46 +02002673}
2674
Lea Wiemann074afaa2008-06-19 22:03:21 +02002675# die_error(<http_status_code>, <error_message>)
2676# Example: die_error(404, 'Hash not found')
2677# By convention, use the following status codes (as defined in RFC 2616):
2678# 400: Invalid or missing CGI parameters, or
2679# requested object exists but has wrong type.
2680# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
2681# this server or project.
2682# 404: Requested object/revision/project doesn't exist.
2683# 500: The server isn't configured properly, or
2684# an internal error occurred (e.g. failed assertions caused by bugs), or
2685# an unknown error occurred (e.g. the git binary died unexpectedly).
Kay Sievers061cc7c2005-08-07 20:15:57 +02002686sub die_error {
Lea Wiemann074afaa2008-06-19 22:03:21 +02002687 my $status = shift || 500;
2688 my $error = shift || "Internal server error";
Kay Sievers664f4cc2005-08-07 20:17:19 +02002689
Lea Wiemann074afaa2008-06-19 22:03:21 +02002690 my %http_responses = (400 => '400 Bad Request',
2691 403 => '403 Forbidden',
2692 404 => '404 Not Found',
2693 500 => '500 Internal Server Error');
2694 git_header_html($http_responses{$status});
Jakub Narebski59b9f612006-08-22 23:42:53 +02002695 print <<EOF;
2696<div class="page_body">
2697<br /><br />
2698$status - $error
2699<br />
2700</div>
2701EOF
Kay Sieversa59d4af2005-08-07 20:15:44 +02002702 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02002703 exit;
Kay Sieversa59d4af2005-08-07 20:15:44 +02002704}
2705
Jakub Narebski717b8312006-07-31 21:22:15 +02002706## ----------------------------------------------------------------------
2707## functions printing or outputting HTML: navigation
2708
Jakub Narebski847e01f2006-08-14 02:05:47 +02002709sub git_print_page_nav {
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002710 my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
2711 $extra = '' if !defined $extra; # pager or formats
2712
2713 my @navs = qw(summary shortlog log commit commitdiff tree);
2714 if ($suppress) {
2715 @navs = grep { $_ ne $suppress } @navs;
2716 }
2717
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002718 my %arg = map { $_ => {action=>$_} } @navs;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002719 if (defined $head) {
2720 for (qw(commit commitdiff)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02002721 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002722 }
2723 if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
2724 for (qw(shortlog log)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02002725 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002726 }
2727 }
2728 }
Jakub Narebski3be8e722007-04-01 22:22:21 +02002729 $arg{'tree'}{'hash'} = $treehead if defined $treehead;
2730 $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002731
2732 print "<div class=\"page_nav\">\n" .
2733 (join " | ",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002734 map { $_ eq $current ?
2735 $_ : $cgi->a({-href => href(%{$arg{$_}})}, "$_")
2736 } @navs);
Jakub Narebski898a8932006-07-30 16:14:43 +02002737 print "<br/>\n$extra<br/>\n" .
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02002738 "</div>\n";
2739}
2740
Jakub Narebski847e01f2006-08-14 02:05:47 +02002741sub format_paging_nav {
Lea Wiemann1f684dc2008-05-28 01:25:42 +02002742 my ($action, $hash, $head, $page, $has_next_link) = @_;
Jakub Narebski43ffc062006-07-30 17:49:00 +02002743 my $paging_nav;
2744
2745
2746 if ($hash ne $head || $page) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002747 $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
Jakub Narebski43ffc062006-07-30 17:49:00 +02002748 } else {
2749 $paging_nav .= "HEAD";
2750 }
2751
2752 if ($page > 0) {
2753 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01002754 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski26298b52006-08-10 12:38:47 +02002755 -accesskey => "p", -title => "Alt-p"}, "prev");
Jakub Narebski43ffc062006-07-30 17:49:00 +02002756 } else {
2757 $paging_nav .= " &sdot; prev";
2758 }
2759
Lea Wiemann1f684dc2008-05-28 01:25:42 +02002760 if ($has_next_link) {
Jakub Narebski43ffc062006-07-30 17:49:00 +02002761 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01002762 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebski26298b52006-08-10 12:38:47 +02002763 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski43ffc062006-07-30 17:49:00 +02002764 } else {
2765 $paging_nav .= " &sdot; next";
2766 }
2767
2768 return $paging_nav;
2769}
2770
Jakub Narebski717b8312006-07-31 21:22:15 +02002771## ......................................................................
2772## functions printing or outputting HTML: div
Kay Sievers42f7eb92005-08-07 20:21:46 +02002773
Jakub Narebski847e01f2006-08-14 02:05:47 +02002774sub git_print_header_div {
Jakub Narebski717b8312006-07-31 21:22:15 +02002775 my ($action, $title, $hash, $hash_base) = @_;
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002776 my %args = ();
Jakub Narebski717b8312006-07-31 21:22:15 +02002777
Jakub Narebski3be8e722007-04-01 22:22:21 +02002778 $args{'action'} = $action;
2779 $args{'hash'} = $hash if $hash;
2780 $args{'hash_base'} = $hash_base if $hash_base;
Jakub Narebski717b8312006-07-31 21:22:15 +02002781
2782 print "<div class=\"header\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02002783 $cgi->a({-href => href(%args), -class => "title"},
2784 $title ? $title : $action) .
2785 "\n</div>\n";
Kay Sievers42f7eb92005-08-07 20:21:46 +02002786}
2787
Jakub Narebski6fd92a22006-08-28 14:48:12 +02002788#sub git_print_authorship (\%) {
2789sub git_print_authorship {
2790 my $co = shift;
2791
Jakub Narebskia44465c2006-08-28 23:17:31 +02002792 my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
Jakub Narebski6fd92a22006-08-28 14:48:12 +02002793 print "<div class=\"author_date\">" .
2794 esc_html($co->{'author_name'}) .
Jakub Narebskia44465c2006-08-28 23:17:31 +02002795 " [$ad{'rfc2822'}";
2796 if ($ad{'hour_local'} < 6) {
2797 printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
2798 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
2799 } else {
2800 printf(" (%02d:%02d %s)",
2801 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
2802 }
2803 print "]</div>\n";
Jakub Narebski6fd92a22006-08-28 14:48:12 +02002804}
2805
Jakub Narebski717b8312006-07-31 21:22:15 +02002806sub git_print_page_path {
2807 my $name = shift;
2808 my $type = shift;
Luben Tuikov59fb1c92006-08-17 10:39:29 -07002809 my $hb = shift;
Junio C Hamanodf2c37a2006-01-09 13:13:39 +01002810
Jakub Narebski4df118e2006-10-21 17:53:55 +02002811
2812 print "<div class=\"page_path\">";
2813 print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
Martin Koegler00f429a2007-06-03 17:42:44 +02002814 -title => 'tree root'}, to_utf8("[$project]"));
Jakub Narebski4df118e2006-10-21 17:53:55 +02002815 print " / ";
2816 if (defined $name) {
Jakub Narebski762c7202006-09-04 18:17:58 +02002817 my @dirname = split '/', $name;
2818 my $basename = pop @dirname;
2819 my $fullname = '';
2820
Jakub Narebski762c7202006-09-04 18:17:58 +02002821 foreach my $dir (@dirname) {
Petr Baudis16fdb482006-09-21 02:05:50 +02002822 $fullname .= ($fullname ? '/' : '') . $dir;
Jakub Narebski762c7202006-09-04 18:17:58 +02002823 print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
2824 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01002825 -title => $fullname}, esc_path($dir));
Petr Baudis26d0a972006-09-23 01:00:12 +02002826 print " / ";
Jakub Narebski762c7202006-09-04 18:17:58 +02002827 }
2828 if (defined $type && $type eq 'blob') {
Jakub Narebski952c65f2006-08-22 16:52:50 +02002829 print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
Jakub Narebski762c7202006-09-04 18:17:58 +02002830 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01002831 -title => $name}, esc_path($basename));
Jakub Narebski762c7202006-09-04 18:17:58 +02002832 } elsif (defined $type && $type eq 'tree') {
2833 print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
2834 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01002835 -title => $name}, esc_path($basename));
Jakub Narebski4df118e2006-10-21 17:53:55 +02002836 print " / ";
Luben Tuikov59fb1c92006-08-17 10:39:29 -07002837 } else {
Jakub Narebski403d0902006-11-08 11:48:56 +01002838 print esc_path($basename);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07002839 }
Kay Sievers8ed23e12005-08-07 20:05:44 +02002840 }
Jakub Narebski4df118e2006-10-21 17:53:55 +02002841 print "<br/></div>\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02002842}
2843
Jakub Narebskib7f92532006-08-28 14:48:10 +02002844# sub git_print_log (\@;%) {
2845sub git_print_log ($;%) {
Jakub Narebskid16d0932006-08-17 11:21:23 +02002846 my $log = shift;
Jakub Narebskib7f92532006-08-28 14:48:10 +02002847 my %opts = @_;
Jakub Narebskid16d0932006-08-17 11:21:23 +02002848
Jakub Narebskib7f92532006-08-28 14:48:10 +02002849 if ($opts{'-remove_title'}) {
2850 # remove title, i.e. first line of log
2851 shift @$log;
2852 }
Jakub Narebskid16d0932006-08-17 11:21:23 +02002853 # remove leading empty lines
2854 while (defined $log->[0] && $log->[0] eq "") {
2855 shift @$log;
2856 }
2857
2858 # print log
2859 my $signoff = 0;
2860 my $empty = 0;
2861 foreach my $line (@$log) {
Jakub Narebskib7f92532006-08-28 14:48:10 +02002862 if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
2863 $signoff = 1;
Jakub Narebskifba20b42006-08-28 14:48:13 +02002864 $empty = 0;
Jakub Narebskib7f92532006-08-28 14:48:10 +02002865 if (! $opts{'-remove_signoff'}) {
2866 print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
2867 next;
2868 } else {
2869 # remove signoff lines
2870 next;
2871 }
2872 } else {
2873 $signoff = 0;
2874 }
2875
Jakub Narebskid16d0932006-08-17 11:21:23 +02002876 # print only one empty line
2877 # do not print empty line after signoff
2878 if ($line eq "") {
2879 next if ($empty || $signoff);
2880 $empty = 1;
2881 } else {
2882 $empty = 0;
2883 }
Jakub Narebskib7f92532006-08-28 14:48:10 +02002884
2885 print format_log_line_html($line) . "<br/>\n";
2886 }
2887
2888 if ($opts{'-final_empty_line'}) {
2889 # end with single empty line
2890 print "<br/>\n" unless $empty;
Jakub Narebskid16d0932006-08-17 11:21:23 +02002891 }
2892}
2893
Jakub Narebskie33fba42006-12-10 13:25:46 +01002894# return link target (what link points to)
2895sub git_get_link_target {
2896 my $hash = shift;
2897 my $link_target;
2898
2899 # read link
2900 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
2901 or return;
2902 {
2903 local $/;
2904 $link_target = <$fd>;
2905 }
2906 close $fd
2907 or return;
2908
2909 return $link_target;
2910}
2911
Jakub Narebski3bf9d572006-12-10 13:25:48 +01002912# given link target, and the directory (basedir) the link is in,
2913# return target of link relative to top directory (top tree);
2914# return undef if it is not possible (including absolute links).
2915sub normalize_link_target {
2916 my ($link_target, $basedir, $hash_base) = @_;
2917
2918 # we can normalize symlink target only if $hash_base is provided
2919 return unless $hash_base;
2920
2921 # absolute symlinks (beginning with '/') cannot be normalized
2922 return if (substr($link_target, 0, 1) eq '/');
2923
2924 # normalize link target to path from top (root) tree (dir)
2925 my $path;
2926 if ($basedir) {
2927 $path = $basedir . '/' . $link_target;
2928 } else {
2929 # we are in top (root) tree (dir)
2930 $path = $link_target;
2931 }
2932
2933 # remove //, /./, and /../
2934 my @path_parts;
2935 foreach my $part (split('/', $path)) {
2936 # discard '.' and ''
2937 next if (!$part || $part eq '.');
2938 # handle '..'
2939 if ($part eq '..') {
2940 if (@path_parts) {
2941 pop @path_parts;
2942 } else {
2943 # link leads outside repository (outside top dir)
2944 return;
2945 }
2946 } else {
2947 push @path_parts, $part;
2948 }
2949 }
2950 $path = join('/', @path_parts);
2951
2952 return $path;
2953}
Jakub Narebskie33fba42006-12-10 13:25:46 +01002954
Jakub Narebskifa702002006-08-31 00:35:07 +02002955# print tree entry (row of git_tree), but without encompassing <tr> element
2956sub git_print_tree_entry {
2957 my ($t, $basedir, $hash_base, $have_blame) = @_;
2958
2959 my %base_key = ();
Jakub Narebskie33fba42006-12-10 13:25:46 +01002960 $base_key{'hash_base'} = $hash_base if defined $hash_base;
Jakub Narebskifa702002006-08-31 00:35:07 +02002961
Luben Tuikov4de741b2006-09-25 22:38:16 -07002962 # The format of a table row is: mode list link. Where mode is
2963 # the mode of the entry, list is the name of the entry, an href,
2964 # and link is the action links of the entry.
2965
Jakub Narebskifa702002006-08-31 00:35:07 +02002966 print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
2967 if ($t->{'type'} eq "blob") {
2968 print "<td class=\"list\">" .
Luben Tuikov4de741b2006-09-25 22:38:16 -07002969 $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02002970 file_name=>"$basedir$t->{'name'}", %base_key),
Jakub Narebskie33fba42006-12-10 13:25:46 +01002971 -class => "list"}, esc_path($t->{'name'}));
2972 if (S_ISLNK(oct $t->{'mode'})) {
2973 my $link_target = git_get_link_target($t->{'hash'});
2974 if ($link_target) {
Jakub Narebski3bf9d572006-12-10 13:25:48 +01002975 my $norm_target = normalize_link_target($link_target, $basedir, $hash_base);
2976 if (defined $norm_target) {
2977 print " -> " .
2978 $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
2979 file_name=>$norm_target),
2980 -title => $norm_target}, esc_path($link_target));
2981 } else {
2982 print " -> " . esc_path($link_target);
2983 }
Jakub Narebskie33fba42006-12-10 13:25:46 +01002984 }
2985 }
2986 print "</td>\n";
Luben Tuikov4de741b2006-09-25 22:38:16 -07002987 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02002988 print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01002989 file_name=>"$basedir$t->{'name'}", %base_key)},
2990 "blob");
Jakub Narebskifa702002006-08-31 00:35:07 +02002991 if ($have_blame) {
Petr Baudis4777b012006-10-24 05:36:10 +02002992 print " | " .
2993 $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01002994 file_name=>"$basedir$t->{'name'}", %base_key)},
2995 "blame");
Jakub Narebskifa702002006-08-31 00:35:07 +02002996 }
2997 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02002998 print " | " .
2999 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02003000 hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
3001 "history");
3002 }
3003 print " | " .
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07003004 $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003005 file_name=>"$basedir$t->{'name'}")},
3006 "raw");
Luben Tuikov4de741b2006-09-25 22:38:16 -07003007 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02003008
3009 } elsif ($t->{'type'} eq "tree") {
Luben Tuikov0fa105e2006-09-26 12:45:37 -07003010 print "<td class=\"list\">";
3011 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskifa702002006-08-31 00:35:07 +02003012 file_name=>"$basedir$t->{'name'}", %base_key)},
Jakub Narebski403d0902006-11-08 11:48:56 +01003013 esc_path($t->{'name'}));
Luben Tuikov0fa105e2006-09-26 12:45:37 -07003014 print "</td>\n";
3015 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02003016 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01003017 file_name=>"$basedir$t->{'name'}", %base_key)},
3018 "tree");
Jakub Narebskifa702002006-08-31 00:35:07 +02003019 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02003020 print " | " .
3021 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02003022 file_name=>"$basedir$t->{'name'}")},
3023 "history");
3024 }
3025 print "</td>\n";
Jakub Narebski01ac1e32007-07-28 16:27:31 +02003026 } else {
3027 # unknown object: we can only present history for it
3028 # (this includes 'commit' object, i.e. submodule support)
3029 print "<td class=\"list\">" .
3030 esc_path($t->{'name'}) .
3031 "</td>\n";
3032 print "<td class=\"link\">";
3033 if (defined $hash_base) {
3034 print $cgi->a({-href => href(action=>"history",
3035 hash_base=>$hash_base,
3036 file_name=>"$basedir$t->{'name'}")},
3037 "history");
3038 }
3039 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02003040 }
3041}
3042
Jakub Narebski717b8312006-07-31 21:22:15 +02003043## ......................................................................
3044## functions printing large fragments of HTML
Kay Sieversede5e102005-08-07 20:23:12 +02003045
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003046# get pre-image filenames for merge (combined) diff
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003047sub fill_from_file_info {
3048 my ($diff, @parents) = @_;
3049
3050 $diff->{'from_file'} = [ ];
3051 $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
3052 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
3053 if ($diff->{'status'}[$i] eq 'R' ||
3054 $diff->{'status'}[$i] eq 'C') {
3055 $diff->{'from_file'}[$i] =
3056 git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
3057 }
3058 }
3059
3060 return $diff;
3061}
3062
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003063# is current raw difftree line of file deletion
Jakub Narebski90921742007-06-08 13:27:42 +02003064sub is_deleted {
3065 my $diffinfo = shift;
3066
Jakub Narebski4ed4a342008-04-05 21:13:24 +01003067 return $diffinfo->{'to_id'} eq ('0' x 40);
Jakub Narebski90921742007-06-08 13:27:42 +02003068}
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003069
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003070# does patch correspond to [previous] difftree raw line
3071# $diffinfo - hashref of parsed raw diff format
3072# $patchinfo - hashref of parsed patch diff format
3073# (the same keys as in $diffinfo)
3074sub is_patch_split {
3075 my ($diffinfo, $patchinfo) = @_;
3076
3077 return defined $diffinfo && defined $patchinfo
Jakub Narebski9d301452007-11-01 12:38:08 +01003078 && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003079}
3080
3081
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003082sub git_difftree_body {
Jakub Narebskied224de2007-05-07 01:10:04 +02003083 my ($difftree, $hash, @parents) = @_;
3084 my ($parent) = $parents[0];
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003085 my ($have_blame) = gitweb_check_feature('blame');
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003086 print "<div class=\"list_head\">\n";
3087 if ($#{$difftree} > 10) {
3088 print(($#{$difftree} + 1) . " files changed:\n");
3089 }
3090 print "</div>\n";
3091
Jakub Narebskied224de2007-05-07 01:10:04 +02003092 print "<table class=\"" .
3093 (@parents > 1 ? "combined " : "") .
3094 "diff_tree\">\n";
Jakub Narebski47598d72007-06-08 13:24:56 +02003095
3096 # header only for combined diff in 'commitdiff' view
Jakub Narebski3ef408a2007-09-08 21:54:28 +02003097 my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
Jakub Narebski47598d72007-06-08 13:24:56 +02003098 if ($has_header) {
3099 # table header
3100 print "<thead><tr>\n" .
3101 "<th></th><th></th>\n"; # filename, patchN link
3102 for (my $i = 0; $i < @parents; $i++) {
3103 my $par = $parents[$i];
3104 print "<th>" .
3105 $cgi->a({-href => href(action=>"commitdiff",
3106 hash=>$hash, hash_parent=>$par),
3107 -title => 'commitdiff to parent number ' .
3108 ($i+1) . ': ' . substr($par,0,7)},
3109 $i+1) .
3110 "&nbsp;</th>\n";
3111 }
3112 print "</tr></thead>\n<tbody>\n";
3113 }
3114
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003115 my $alternate = 1;
Jakub Narebskib4657e72006-08-28 14:48:14 +02003116 my $patchno = 0;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003117 foreach my $line (@{$difftree}) {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003118 my $diff = parsed_difftree_line($line);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003119
3120 if ($alternate) {
3121 print "<tr class=\"dark\">\n";
3122 } else {
3123 print "<tr class=\"light\">\n";
3124 }
3125 $alternate ^= 1;
3126
Jakub Narebski493e01d2007-05-07 01:10:06 +02003127 if (exists $diff->{'nparents'}) { # combined diff
Jakub Narebskied224de2007-05-07 01:10:04 +02003128
Jakub Narebski493e01d2007-05-07 01:10:06 +02003129 fill_from_file_info($diff, @parents)
3130 unless exists $diff->{'from_file'};
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003131
Jakub Narebski90921742007-06-08 13:27:42 +02003132 if (!is_deleted($diff)) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003133 # file exists in the result (child) commit
3134 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003135 $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3136 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003137 hash_base=>$hash),
Jakub Narebski493e01d2007-05-07 01:10:06 +02003138 -class => "list"}, esc_path($diff->{'to_file'})) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003139 "</td>\n";
3140 } else {
3141 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02003142 esc_path($diff->{'to_file'}) .
Jakub Narebskied224de2007-05-07 01:10:04 +02003143 "</td>\n";
3144 }
3145
3146 if ($action eq 'commitdiff') {
3147 # link to patch
3148 $patchno++;
3149 print "<td class=\"link\">" .
3150 $cgi->a({-href => "#patch$patchno"}, "patch") .
3151 " | " .
3152 "</td>\n";
3153 }
3154
3155 my $has_history = 0;
3156 my $not_deleted = 0;
Jakub Narebski493e01d2007-05-07 01:10:06 +02003157 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003158 my $hash_parent = $parents[$i];
Jakub Narebski493e01d2007-05-07 01:10:06 +02003159 my $from_hash = $diff->{'from_id'}[$i];
3160 my $from_path = $diff->{'from_file'}[$i];
3161 my $status = $diff->{'status'}[$i];
Jakub Narebskied224de2007-05-07 01:10:04 +02003162
3163 $has_history ||= ($status ne 'A');
3164 $not_deleted ||= ($status ne 'D');
3165
Jakub Narebskied224de2007-05-07 01:10:04 +02003166 if ($status eq 'A') {
3167 print "<td class=\"link\" align=\"right\"> | </td>\n";
3168 } elsif ($status eq 'D') {
3169 print "<td class=\"link\">" .
3170 $cgi->a({-href => href(action=>"blob",
3171 hash_base=>$hash,
3172 hash=>$from_hash,
3173 file_name=>$from_path)},
3174 "blob" . ($i+1)) .
3175 " | </td>\n";
3176 } else {
Jakub Narebski493e01d2007-05-07 01:10:06 +02003177 if ($diff->{'to_id'} eq $from_hash) {
Jakub Narebskied224de2007-05-07 01:10:04 +02003178 print "<td class=\"link nochange\">";
3179 } else {
3180 print "<td class=\"link\">";
3181 }
3182 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003183 hash=>$diff->{'to_id'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003184 hash_parent=>$from_hash,
3185 hash_base=>$hash,
3186 hash_parent_base=>$hash_parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003187 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003188 file_parent=>$from_path)},
3189 "diff" . ($i+1)) .
3190 " | </td>\n";
3191 }
3192 }
3193
3194 print "<td class=\"link\">";
3195 if ($not_deleted) {
3196 print $cgi->a({-href => href(action=>"blob",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003197 hash=>$diff->{'to_id'},
3198 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003199 hash_base=>$hash)},
3200 "blob");
3201 print " | " if ($has_history);
3202 }
3203 if ($has_history) {
3204 print $cgi->a({-href => href(action=>"history",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003205 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02003206 hash_base=>$hash)},
3207 "history");
3208 }
3209 print "</td>\n";
3210
3211 print "</tr>\n";
3212 next; # instead of 'else' clause, to avoid extra indent
3213 }
3214 # else ordinary diff
3215
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003216 my ($to_mode_oct, $to_mode_str, $to_file_type);
3217 my ($from_mode_oct, $from_mode_str, $from_file_type);
Jakub Narebski493e01d2007-05-07 01:10:06 +02003218 if ($diff->{'to_mode'} ne ('0' x 6)) {
3219 $to_mode_oct = oct $diff->{'to_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003220 if (S_ISREG($to_mode_oct)) { # only for regular file
3221 $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003222 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003223 $to_file_type = file_type($diff->{'to_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003224 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003225 if ($diff->{'from_mode'} ne ('0' x 6)) {
3226 $from_mode_oct = oct $diff->{'from_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003227 if (S_ISREG($to_mode_oct)) { # only for regular file
3228 $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
3229 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003230 $from_file_type = file_type($diff->{'from_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003231 }
3232
Jakub Narebski493e01d2007-05-07 01:10:06 +02003233 if ($diff->{'status'} eq "A") { # created
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003234 my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
3235 $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
3236 $mode_chng .= "]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07003237 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_chng</td>\n";
3243 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02003244 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02003245 # link to patch
3246 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07003247 print $cgi->a({-href => "#patch$patchno"}, "patch");
Jakub Narebski897d1d22006-11-19 22:51:39 +01003248 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02003249 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003250 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3251 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski3faa5412007-01-08 02:10:42 +01003252 "blob");
Jakub Narebskib4657e72006-08-28 14:48:14 +02003253 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003254
Jakub Narebski493e01d2007-05-07 01:10:06 +02003255 } elsif ($diff->{'status'} eq "D") { # deleted
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003256 my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07003257 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003258 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
3259 hash_base=>$parent, file_name=>$diff->{'file'}),
3260 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003261 print "</td>\n";
3262 print "<td>$mode_chng</td>\n";
3263 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02003264 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02003265 # link to patch
3266 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07003267 print $cgi->a({-href => "#patch$patchno"}, "patch");
3268 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02003269 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003270 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
3271 hash_base=>$parent, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003272 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003273 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003274 print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003275 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003276 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003277 }
Jakub Narebskib4657e72006-08-28 14:48:14 +02003278 print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003279 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003280 "history");
Luben Tuikov499faed2006-09-27 17:23:25 -07003281 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003282
Jakub Narebski493e01d2007-05-07 01:10:06 +02003283 } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003284 my $mode_chnge = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003285 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003286 $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
Jakub Narebski6e72cf42007-01-03 20:47:24 +01003287 if ($from_file_type ne $to_file_type) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003288 $mode_chnge .= " from $from_file_type to $to_file_type";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003289 }
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003290 if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
3291 if ($from_mode_str && $to_mode_str) {
3292 $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
3293 } elsif ($to_mode_str) {
3294 $mode_chnge .= " mode: $to_mode_str";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003295 }
3296 }
3297 $mode_chnge .= "]</span>\n";
3298 }
3299 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003300 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3301 hash_base=>$hash, file_name=>$diff->{'file'}),
3302 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07003303 print "</td>\n";
3304 print "<td>$mode_chnge</td>\n";
3305 print "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01003306 if ($action eq 'commitdiff') {
3307 # link to patch
3308 $patchno++;
3309 print $cgi->a({-href => "#patch$patchno"}, "patch") .
3310 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003311 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01003312 # "commit" view and modified file (not onlu mode changed)
3313 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003314 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01003315 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003316 file_name=>$diff->{'file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01003317 "diff") .
3318 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003319 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003320 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3321 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003322 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003323 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003324 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003325 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003326 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003327 }
Luben Tuikoveb51ec92006-09-27 17:24:49 -07003328 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003329 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003330 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003331 print "</td>\n";
3332
Jakub Narebski493e01d2007-05-07 01:10:06 +02003333 } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003334 my %status_name = ('R' => 'moved', 'C' => 'copied');
Jakub Narebski493e01d2007-05-07 01:10:06 +02003335 my $nstatus = $status_name{$diff->{'status'}};
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003336 my $mode_chng = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003337 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003338 # mode also for directories, so we cannot use $to_mode_str
3339 $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003340 }
3341 print "<td>" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003342 $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003343 hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
3344 -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003345 "<td><span class=\"file_status $nstatus\">[$nstatus from " .
3346 $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003347 hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
3348 -class => "list"}, esc_path($diff->{'from_file'})) .
3349 " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
Luben Tuikov499faed2006-09-27 17:23:25 -07003350 "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01003351 if ($action eq 'commitdiff') {
3352 # link to patch
3353 $patchno++;
3354 print $cgi->a({-href => "#patch$patchno"}, "patch") .
3355 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02003356 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01003357 # "commit" view and modified file (not only pure rename or copy)
3358 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02003359 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01003360 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003361 file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01003362 "diff") .
3363 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003364 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02003365 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
3366 hash_base=>$parent, file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003367 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003368 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01003369 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003370 file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01003371 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08003372 }
Jakub Narebski897d1d22006-11-19 22:51:39 +01003373 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02003374 file_name=>$diff->{'to_file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02003375 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003376 print "</td>\n";
3377
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003378 } # we should not encounter Unmerged (U) or Unknown (X) status
3379 print "</tr>\n";
3380 }
Jakub Narebski47598d72007-06-08 13:24:56 +02003381 print "</tbody>" if $has_header;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02003382 print "</table>\n";
3383}
3384
Jakub Narebskieee08902006-08-24 00:15:14 +02003385sub git_patchset_body {
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02003386 my ($fd, $difftree, $hash, @hash_parents) = @_;
3387 my ($hash_parent) = $hash_parents[0];
Jakub Narebskieee08902006-08-24 00:15:14 +02003388
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003389 my $is_combined = (@hash_parents > 1);
Jakub Narebskieee08902006-08-24 00:15:14 +02003390 my $patch_idx = 0;
Martin Koegler4280cde2007-04-22 22:49:25 -07003391 my $patch_number = 0;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003392 my $patch_line;
Jakub Narebskife875852006-08-25 20:59:39 +02003393 my $diffinfo;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003394 my $to_name;
Jakub Narebski744d0ac2006-11-08 17:59:41 +01003395 my (%from, %to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003396
3397 print "<div class=\"patchset\">\n";
3398
Jakub Narebski6d55f052006-11-18 23:35:39 +01003399 # skip to first patch
3400 while ($patch_line = <$fd>) {
Jakub Narebski157e43b2006-08-24 19:34:36 +02003401 chomp $patch_line;
Jakub Narebskieee08902006-08-24 00:15:14 +02003402
Jakub Narebski6d55f052006-11-18 23:35:39 +01003403 last if ($patch_line =~ m/^diff /);
3404 }
3405
3406 PATCH:
3407 while ($patch_line) {
Jakub Narebski6d55f052006-11-18 23:35:39 +01003408
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003409 # parse "git diff" header line
3410 if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
3411 # $1 is from_name, which we do not use
3412 $to_name = unquote($2);
3413 $to_name =~ s!^b/!!;
3414 } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
3415 # $1 is 'cc' or 'combined', which we do not use
3416 $to_name = unquote($2);
3417 } else {
3418 $to_name = undef;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003419 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01003420
3421 # check if current patch belong to current raw line
3422 # and parse raw git-diff line if needed
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003423 if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
Jakub Narebski22065372007-05-12 12:42:32 +02003424 # this is continuation of a split patch
Jakub Narebski6d55f052006-11-18 23:35:39 +01003425 print "<div class=\"patch cont\">\n";
3426 } else {
3427 # advance raw git-diff output if needed
3428 $patch_idx++ if defined $diffinfo;
Jakub Narebskieee08902006-08-24 00:15:14 +02003429
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003430 # read and prepare patch information
3431 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
3432
Jakub Narebskicd030c32007-06-08 13:33:28 +02003433 # compact combined diff output can have some patches skipped
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003434 # find which patch (using pathname of result) we are at now;
3435 if ($is_combined) {
3436 while ($to_name ne $diffinfo->{'to_file'}) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02003437 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
3438 format_diff_cc_simplified($diffinfo, @hash_parents) .
3439 "</div>\n"; # class="patch"
3440
3441 $patch_idx++;
3442 $patch_number++;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003443
3444 last if $patch_idx > $#$difftree;
3445 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02003446 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003447 }
Jakub Narebski711fa742007-09-08 21:49:11 +02003448
Jakub Narebski90921742007-06-08 13:27:42 +02003449 # modifies %from, %to hashes
3450 parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
Jakub Narebski5f855052007-05-17 22:54:28 +02003451
Jakub Narebski6d55f052006-11-18 23:35:39 +01003452 # this is first patch for raw difftree line with $patch_idx index
3453 # we index @$difftree array from 0, but number patches from 1
3454 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
Jakub Narebski744d0ac2006-11-08 17:59:41 +01003455 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003456
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003457 # git diff header
3458 #assert($patch_line =~ m/^diff /) if DEBUG;
3459 #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
3460 $patch_number++;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003461 # print "git diff" header
Jakub Narebski90921742007-06-08 13:27:42 +02003462 print format_git_diff_header_line($patch_line, $diffinfo,
3463 \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003464
Jakub Narebski6d55f052006-11-18 23:35:39 +01003465 # print extended diff header
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003466 print "<div class=\"diff extended_header\">\n";
Jakub Narebski6d55f052006-11-18 23:35:39 +01003467 EXTENDED_HEADER:
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003468 while ($patch_line = <$fd>) {
3469 chomp $patch_line;
3470
3471 last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
3472
Jakub Narebski90921742007-06-08 13:27:42 +02003473 print format_extended_diff_header_line($patch_line, $diffinfo,
3474 \%from, \%to);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003475 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003476 print "</div>\n"; # class="diff extended_header"
Jakub Narebskie4e4f822006-08-25 21:04:13 +02003477
Jakub Narebski6d55f052006-11-18 23:35:39 +01003478 # from-file/to-file diff header
Jakub Narebski0bdb28c2007-01-10 00:07:43 +01003479 if (! $patch_line) {
3480 print "</div>\n"; # class="patch"
3481 last PATCH;
3482 }
Jakub Narebski66399ef2007-01-07 02:52:25 +01003483 next PATCH if ($patch_line =~ m/^diff /);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003484 #assert($patch_line =~ m/^---/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003485
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003486 my $last_patch_line = $patch_line;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003487 $patch_line = <$fd>;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003488 chomp $patch_line;
Jakub Narebski90921742007-06-08 13:27:42 +02003489 #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01003490
Jakub Narebski90921742007-06-08 13:27:42 +02003491 print format_diff_from_to_header($last_patch_line, $patch_line,
Jakub Narebski91af4ce2007-06-08 13:32:44 +02003492 $diffinfo, \%from, \%to,
3493 @hash_parents);
Jakub Narebski6d55f052006-11-18 23:35:39 +01003494
3495 # the patch itself
3496 LINE:
3497 while ($patch_line = <$fd>) {
3498 chomp $patch_line;
3499
3500 next PATCH if ($patch_line =~ m/^diff /);
3501
Jakub Narebski59e3b142006-11-18 23:35:40 +01003502 print format_diff_line($patch_line, \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02003503 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003504
Jakub Narebski6d55f052006-11-18 23:35:39 +01003505 } continue {
3506 print "</div>\n"; # class="patch"
Jakub Narebskieee08902006-08-24 00:15:14 +02003507 }
Jakub Narebskid26c4262007-05-17 00:05:55 +02003508
Jakub Narebskicd030c32007-06-08 13:33:28 +02003509 # for compact combined (--cc) format, with chunk and patch simpliciaction
3510 # patchset might be empty, but there might be unprocessed raw lines
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003511 for (++$patch_idx if $patch_number > 0;
Jakub Narebskicd030c32007-06-08 13:33:28 +02003512 $patch_idx < @$difftree;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003513 ++$patch_idx) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02003514 # read and prepare patch information
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003515 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02003516
3517 # generate anchor for "patch" links in difftree / whatchanged part
3518 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
3519 format_diff_cc_simplified($diffinfo, @hash_parents) .
3520 "</div>\n"; # class="patch"
3521
3522 $patch_number++;
3523 }
3524
Jakub Narebskid26c4262007-05-17 00:05:55 +02003525 if ($patch_number == 0) {
3526 if (@hash_parents > 1) {
3527 print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
3528 } else {
3529 print "<div class=\"diff nodifferences\">No differences found</div>\n";
3530 }
3531 }
Jakub Narebskieee08902006-08-24 00:15:14 +02003532
3533 print "</div>\n"; # class="patchset"
3534}
3535
3536# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3537
Petr Baudise30496d2006-10-24 05:33:17 +02003538sub git_project_list_body {
3539 my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
3540
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08003541 my ($check_forks) = gitweb_check_feature('forks');
Petr Baudise30496d2006-10-24 05:33:17 +02003542
3543 my @projects;
3544 foreach my $pr (@$projlist) {
3545 my (@aa) = git_get_last_activity($pr->{'path'});
3546 unless (@aa) {
3547 next;
3548 }
3549 ($pr->{'age'}, $pr->{'age_string'}) = @aa;
3550 if (!defined $pr->{'descr'}) {
3551 my $descr = git_get_project_description($pr->{'path'}) || "";
Martin Koegler00f429a2007-06-03 17:42:44 +02003552 $pr->{'descr_long'} = to_utf8($descr);
Michael Hendricks55feb122007-07-04 18:36:48 -06003553 $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
Petr Baudise30496d2006-10-24 05:33:17 +02003554 }
3555 if (!defined $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02003556 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
Petr Baudise30496d2006-10-24 05:33:17 +02003557 }
3558 if ($check_forks) {
3559 my $pname = $pr->{'path'};
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003560 if (($pname =~ s/\.git$//) &&
3561 ($pname !~ /\/$/) &&
3562 (-d "$projectroot/$pname")) {
3563 $pr->{'forks'} = "-d $projectroot/$pname";
3564 }
3565 else {
3566 $pr->{'forks'} = 0;
3567 }
Petr Baudise30496d2006-10-24 05:33:17 +02003568 }
3569 push @projects, $pr;
3570 }
3571
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02003572 $order ||= $default_projects_order;
Petr Baudise30496d2006-10-24 05:33:17 +02003573 $from = 0 unless defined $from;
3574 $to = $#projects if (!defined $to || $#projects < $to);
3575
3576 print "<table class=\"project_list\">\n";
3577 unless ($no_header) {
3578 print "<tr>\n";
3579 if ($check_forks) {
3580 print "<th></th>\n";
3581 }
3582 if ($order eq "project") {
3583 @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
3584 print "<th>Project</th>\n";
3585 } else {
3586 print "<th>" .
3587 $cgi->a({-href => href(project=>undef, order=>'project'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003588 -class => "header"}, "Project") .
Petr Baudise30496d2006-10-24 05:33:17 +02003589 "</th>\n";
3590 }
3591 if ($order eq "descr") {
3592 @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
3593 print "<th>Description</th>\n";
3594 } else {
3595 print "<th>" .
3596 $cgi->a({-href => href(project=>undef, order=>'descr'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003597 -class => "header"}, "Description") .
Petr Baudise30496d2006-10-24 05:33:17 +02003598 "</th>\n";
3599 }
3600 if ($order eq "owner") {
3601 @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
3602 print "<th>Owner</th>\n";
3603 } else {
3604 print "<th>" .
3605 $cgi->a({-href => href(project=>undef, order=>'owner'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003606 -class => "header"}, "Owner") .
Petr Baudise30496d2006-10-24 05:33:17 +02003607 "</th>\n";
3608 }
3609 if ($order eq "age") {
3610 @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
3611 print "<th>Last Change</th>\n";
3612 } else {
3613 print "<th>" .
3614 $cgi->a({-href => href(project=>undef, order=>'age'),
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003615 -class => "header"}, "Last Change") .
Petr Baudise30496d2006-10-24 05:33:17 +02003616 "</th>\n";
3617 }
3618 print "<th></th>\n" .
3619 "</tr>\n";
3620 }
3621 my $alternate = 1;
3622 for (my $i = $from; $i <= $to; $i++) {
3623 my $pr = $projects[$i];
3624 if ($alternate) {
3625 print "<tr class=\"dark\">\n";
3626 } else {
3627 print "<tr class=\"light\">\n";
3628 }
3629 $alternate ^= 1;
3630 if ($check_forks) {
3631 print "<td>";
3632 if ($pr->{'forks'}) {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08003633 print "<!-- $pr->{'forks'} -->\n";
Petr Baudise30496d2006-10-24 05:33:17 +02003634 print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
3635 }
3636 print "</td>\n";
3637 }
3638 print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
3639 -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01003640 "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
3641 -class => "list", -title => $pr->{'descr_long'}},
3642 esc_html($pr->{'descr'})) . "</td>\n" .
David Symondsd3cd2492007-10-23 11:31:23 +10003643 "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
Petr Baudise30496d2006-10-24 05:33:17 +02003644 print "<td class=\"". age_class($pr->{'age'}) . "\">" .
Jakub Narebski785cdea2007-05-13 12:39:22 +02003645 (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
Petr Baudise30496d2006-10-24 05:33:17 +02003646 "<td class=\"link\">" .
3647 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
Alexandre Julliardfaa1bbf2006-11-15 21:37:50 +01003648 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
Petr Baudise30496d2006-10-24 05:33:17 +02003649 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
3650 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
3651 ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
3652 "</td>\n" .
3653 "</tr>\n";
3654 }
3655 if (defined $extra) {
3656 print "<tr>\n";
3657 if ($check_forks) {
3658 print "<td></td>\n";
3659 }
3660 print "<td colspan=\"5\">$extra</td>\n" .
3661 "</tr>\n";
3662 }
3663 print "</table>\n";
3664}
3665
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003666sub git_shortlog_body {
3667 # uses global variable $project
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00003668 my ($commitlist, $from, $to, $refs, $extra) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05303669
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003670 $from = 0 unless defined $from;
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00003671 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003672
Jakub Narebski591ebf62007-11-19 14:16:11 +01003673 print "<table class=\"shortlog\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003674 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003675 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00003676 my %co = %{$commitlist->[$i]};
3677 my $commit = $co{'id'};
Jakub Narebski847e01f2006-08-14 02:05:47 +02003678 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003679 if ($alternate) {
3680 print "<tr class=\"dark\">\n";
3681 } else {
3682 print "<tr class=\"light\">\n";
3683 }
3684 $alternate ^= 1;
David Symondsce58ec92007-10-23 11:31:22 +10003685 my $author = chop_and_escape_str($co{'author_name'}, 10);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003686 # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
3687 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10003688 "<td><i>" . $author . "</i></td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003689 "<td>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02003690 print format_subject_html($co{'title'}, $co{'title_short'},
3691 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003692 print "</td>\n" .
3693 "<td class=\"link\">" .
Petr Baudis4777b012006-10-24 05:36:10 +02003694 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
Petr Baudis35749ae2006-09-22 03:19:44 +02003695 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
Petr Baudis55ff35c2006-10-06 15:57:52 +02003696 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02003697 my $snapshot_links = format_snapshot_links($commit);
3698 if (defined $snapshot_links) {
3699 print " | " . $snapshot_links;
Petr Baudis55ff35c2006-10-06 15:57:52 +02003700 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05303701 print "</td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003702 "</tr>\n";
3703 }
3704 if (defined $extra) {
3705 print "<tr>\n" .
3706 "<td colspan=\"4\">$extra</td>\n" .
3707 "</tr>\n";
3708 }
3709 print "</table>\n";
3710}
3711
Jakub Narebski581860e2006-08-14 02:09:08 +02003712sub git_history_body {
3713 # Warning: assumes constant type (blob or tree) during history
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003714 my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
Jakub Narebski8be68352006-09-11 00:36:04 +02003715
3716 $from = 0 unless defined $from;
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003717 $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
Jakub Narebski581860e2006-08-14 02:09:08 +02003718
Jakub Narebski591ebf62007-11-19 14:16:11 +01003719 print "<table class=\"history\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003720 my $alternate = 1;
Jakub Narebski8be68352006-09-11 00:36:04 +02003721 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003722 my %co = %{$commitlist->[$i]};
Jakub Narebski581860e2006-08-14 02:09:08 +02003723 if (!%co) {
3724 next;
3725 }
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00003726 my $commit = $co{'id'};
Jakub Narebski581860e2006-08-14 02:09:08 +02003727
3728 my $ref = format_ref_marker($refs, $commit);
3729
3730 if ($alternate) {
3731 print "<tr class=\"dark\">\n";
3732 } else {
3733 print "<tr class=\"light\">\n";
3734 }
3735 $alternate ^= 1;
David Symondse076a0e2007-10-22 10:28:03 +10003736 # shortlog uses chop_str($co{'author_name'}, 10)
David Symondsce58ec92007-10-23 11:31:22 +10003737 my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
Jakub Narebski581860e2006-08-14 02:09:08 +02003738 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10003739 "<td><i>" . $author . "</i></td>\n" .
Jakub Narebski581860e2006-08-14 02:09:08 +02003740 "<td>";
3741 # originally git_history used chop_str($co{'title'}, 50)
Jakub Narebski952c65f2006-08-22 16:52:50 +02003742 print format_subject_html($co{'title'}, $co{'title_short'},
3743 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski581860e2006-08-14 02:09:08 +02003744 print "</td>\n" .
3745 "<td class=\"link\">" .
Luben Tuikov6d81c5a2006-09-28 17:21:07 -07003746 $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
3747 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
Jakub Narebski581860e2006-08-14 02:09:08 +02003748
3749 if ($ftype eq 'blob') {
3750 my $blob_current = git_get_hash_by_path($hash_base, $file_name);
3751 my $blob_parent = git_get_hash_by_path($commit, $file_name);
3752 if (defined $blob_current && defined $blob_parent &&
3753 $blob_current ne $blob_parent) {
3754 print " | " .
Jakub Narebski420e92f2006-08-24 23:53:54 +02003755 $cgi->a({-href => href(action=>"blobdiff",
3756 hash=>$blob_current, hash_parent=>$blob_parent,
3757 hash_base=>$hash_base, hash_parent_base=>$commit,
3758 file_name=>$file_name)},
Jakub Narebski581860e2006-08-14 02:09:08 +02003759 "diff to current");
3760 }
3761 }
3762 print "</td>\n" .
3763 "</tr>\n";
3764 }
3765 if (defined $extra) {
3766 print "<tr>\n" .
3767 "<td colspan=\"4\">$extra</td>\n" .
3768 "</tr>\n";
3769 }
3770 print "</table>\n";
3771}
3772
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003773sub git_tags_body {
3774 # uses global variable $project
3775 my ($taglist, $from, $to, $extra) = @_;
3776 $from = 0 unless defined $from;
3777 $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
3778
Jakub Narebski591ebf62007-11-19 14:16:11 +01003779 print "<table class=\"tags\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003780 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003781 for (my $i = $from; $i <= $to; $i++) {
3782 my $entry = $taglist->[$i];
3783 my %tag = %$entry;
Jakub Narebskicd146402006-11-02 20:23:11 +01003784 my $comment = $tag{'subject'};
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003785 my $comment_short;
3786 if (defined $comment) {
3787 $comment_short = chop_str($comment, 30, 5);
3788 }
3789 if ($alternate) {
3790 print "<tr class=\"dark\">\n";
3791 } else {
3792 print "<tr class=\"light\">\n";
3793 }
3794 $alternate ^= 1;
Jakub Narebski27dd1a82007-01-03 22:54:29 +01003795 if (defined $tag{'age'}) {
3796 print "<td><i>$tag{'age'}</i></td>\n";
3797 } else {
3798 print "<td></td>\n";
3799 }
3800 print "<td>" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003801 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
Jakub Narebski63e42202006-08-22 12:38:59 +02003802 -class => "list name"}, esc_html($tag{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003803 "</td>\n" .
3804 "<td>";
3805 if (defined $comment) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02003806 print format_subject_html($comment, $comment_short,
3807 href(action=>"tag", hash=>$tag{'id'}));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003808 }
3809 print "</td>\n" .
3810 "<td class=\"selflink\">";
3811 if ($tag{'type'} eq "tag") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003812 print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003813 } else {
3814 print "&nbsp;";
3815 }
3816 print "</td>\n" .
3817 "<td class=\"link\">" . " | " .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003818 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003819 if ($tag{'reftype'} eq "commit") {
Jakub Narebskibf901f82007-12-15 15:40:28 +01003820 print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
3821 " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003822 } elsif ($tag{'reftype'} eq "blob") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003823 print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003824 }
3825 print "</td>\n" .
3826 "</tr>";
3827 }
3828 if (defined $extra) {
3829 print "<tr>\n" .
3830 "<td colspan=\"5\">$extra</td>\n" .
3831 "</tr>\n";
3832 }
3833 print "</table>\n";
3834}
3835
3836sub git_heads_body {
3837 # uses global variable $project
Jakub Narebski120ddde2006-09-19 14:33:22 +02003838 my ($headlist, $head, $from, $to, $extra) = @_;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003839 $from = 0 unless defined $from;
Jakub Narebski120ddde2006-09-19 14:33:22 +02003840 $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003841
Jakub Narebski591ebf62007-11-19 14:16:11 +01003842 print "<table class=\"heads\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07003843 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003844 for (my $i = $from; $i <= $to; $i++) {
Jakub Narebski120ddde2006-09-19 14:33:22 +02003845 my $entry = $headlist->[$i];
Jakub Narebskicd146402006-11-02 20:23:11 +01003846 my %ref = %$entry;
3847 my $curr = $ref{'id'} eq $head;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003848 if ($alternate) {
3849 print "<tr class=\"dark\">\n";
3850 } else {
3851 print "<tr class=\"light\">\n";
3852 }
3853 $alternate ^= 1;
Jakub Narebskicd146402006-11-02 20:23:11 +01003854 print "<td><i>$ref{'age'}</i></td>\n" .
3855 ($curr ? "<td class=\"current_head\">" : "<td>") .
Jakub Narebskibf901f82007-12-15 15:40:28 +01003856 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
Jakub Narebskicd146402006-11-02 20:23:11 +01003857 -class => "list name"},esc_html($ref{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003858 "</td>\n" .
3859 "<td class=\"link\">" .
Jakub Narebskibf901f82007-12-15 15:40:28 +01003860 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
3861 $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
3862 $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'name'})}, "tree") .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02003863 "</td>\n" .
3864 "</tr>";
3865 }
3866 if (defined $extra) {
3867 print "<tr>\n" .
3868 "<td colspan=\"3\">$extra</td>\n" .
3869 "</tr>\n";
3870 }
3871 print "</table>\n";
3872}
3873
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003874sub git_search_grep_body {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003875 my ($commitlist, $from, $to, $extra) = @_;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003876 $from = 0 unless defined $from;
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003877 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003878
Jakub Narebski591ebf62007-11-19 14:16:11 +01003879 print "<table class=\"commit_search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003880 my $alternate = 1;
3881 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003882 my %co = %{$commitlist->[$i]};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003883 if (!%co) {
3884 next;
3885 }
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00003886 my $commit = $co{'id'};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003887 if ($alternate) {
3888 print "<tr class=\"dark\">\n";
3889 } else {
3890 print "<tr class=\"light\">\n";
3891 }
3892 $alternate ^= 1;
David Symondsce58ec92007-10-23 11:31:22 +10003893 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003894 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
David Symondse076a0e2007-10-22 10:28:03 +10003895 "<td><i>" . $author . "</i></td>\n" .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003896 "<td>" .
Junio C Hamanobe8b9062008-02-22 17:33:47 +01003897 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
3898 -class => "list subject"},
3899 chop_and_escape_str($co{'title'}, 50) . "<br/>");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003900 my $comment = $co{'comment'};
3901 foreach my $line (@$comment) {
Jakub Narebski6dfbb302008-03-02 16:57:14 +01003902 if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
Junio C Hamanobe8b9062008-02-22 17:33:47 +01003903 my ($lead, $match, $trail) = ($1, $2, $3);
Jakub Narebskib8d97d02008-02-25 21:07:57 +01003904 $match = chop_str($match, 70, 5, 'center');
3905 my $contextlen = int((80 - length($match))/2);
3906 $contextlen = 30 if ($contextlen > 30);
3907 $lead = chop_str($lead, $contextlen, 10, 'left');
3908 $trail = chop_str($trail, $contextlen, 10, 'right');
Junio C Hamanobe8b9062008-02-22 17:33:47 +01003909
3910 $lead = esc_html($lead);
3911 $match = esc_html($match);
3912 $trail = esc_html($trail);
3913
3914 print "$lead<span class=\"match\">$match</span>$trail<br />";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003915 }
3916 }
3917 print "</td>\n" .
3918 "<td class=\"link\">" .
3919 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
3920 " | " .
Denis Chengf1fe8f52007-11-26 20:42:06 +08003921 $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
3922 " | " .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00003923 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
3924 print "</td>\n" .
3925 "</tr>\n";
3926 }
3927 if (defined $extra) {
3928 print "<tr>\n" .
3929 "<td colspan=\"3\">$extra</td>\n" .
3930 "</tr>\n";
3931 }
3932 print "</table>\n";
3933}
3934
Jakub Narebski717b8312006-07-31 21:22:15 +02003935## ======================================================================
3936## ======================================================================
3937## actions
3938
Jakub Narebski717b8312006-07-31 21:22:15 +02003939sub git_project_list {
Jakub Narebski6326b602006-08-01 02:59:12 +02003940 my $order = $cgi->param('o');
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02003941 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02003942 die_error(400, "Unknown order parameter");
Jakub Narebski6326b602006-08-01 02:59:12 +02003943 }
3944
Jakub Narebski847e01f2006-08-14 02:05:47 +02003945 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02003946 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02003947 die_error(404, "No projects found");
Jakub Narebski717b8312006-07-31 21:22:15 +02003948 }
Jakub Narebski6326b602006-08-01 02:59:12 +02003949
Jakub Narebski717b8312006-07-31 21:22:15 +02003950 git_header_html();
3951 if (-f $home_text) {
3952 print "<div class=\"index_include\">\n";
3953 open (my $fd, $home_text);
3954 print <$fd>;
3955 close $fd;
3956 print "</div>\n";
3957 }
Petr Baudise30496d2006-10-24 05:33:17 +02003958 git_project_list_body(\@list, $order);
3959 git_footer_html();
3960}
3961
3962sub git_forks {
3963 my $order = $cgi->param('o');
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02003964 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02003965 die_error(400, "Unknown order parameter");
Jakub Narebski717b8312006-07-31 21:22:15 +02003966 }
Petr Baudise30496d2006-10-24 05:33:17 +02003967
3968 my @list = git_get_projects_list($project);
3969 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02003970 die_error(404, "No forks found");
Jakub Narebski717b8312006-07-31 21:22:15 +02003971 }
Petr Baudise30496d2006-10-24 05:33:17 +02003972
3973 git_header_html();
3974 git_print_page_nav('','');
3975 git_print_header_div('summary', "$project forks");
3976 git_project_list_body(\@list, $order);
Jakub Narebski717b8312006-07-31 21:22:15 +02003977 git_footer_html();
3978}
3979
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003980sub git_project_index {
Petr Baudise30496d2006-10-24 05:33:17 +02003981 my @projects = git_get_projects_list($project);
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003982
3983 print $cgi->header(
3984 -type => 'text/plain',
3985 -charset => 'utf-8',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02003986 -content_disposition => 'inline; filename="index.aux"');
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003987
3988 foreach my $pr (@projects) {
3989 if (!exists $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02003990 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02003991 }
3992
3993 my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
3994 # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
3995 $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
3996 $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
3997 $path =~ s/ /\+/g;
3998 $owner =~ s/ /\+/g;
3999
4000 print "$path $owner\n";
4001 }
4002}
4003
Kay Sieversede5e102005-08-07 20:23:12 +02004004sub git_summary {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004005 my $descr = git_get_project_description($project) || "none";
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00004006 my %co = parse_commit("HEAD");
Jakub Narebski785cdea2007-05-13 12:39:22 +02004007 my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00004008 my $head = $co{'id'};
Kay Sieversede5e102005-08-07 20:23:12 +02004009
Jakub Narebski1e0cf032006-08-14 02:10:06 +02004010 my $owner = git_get_project_owner($project);
Kay Sieversede5e102005-08-07 20:23:12 +02004011
Jakub Narebskicd146402006-11-02 20:23:11 +01004012 my $refs = git_get_references();
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004013 # These get_*_list functions return one more to allow us to see if
4014 # there are more ...
4015 my @taglist = git_get_tags_list(16);
4016 my @headlist = git_get_heads_list(16);
Petr Baudise30496d2006-10-24 05:33:17 +02004017 my @forklist;
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08004018 my ($check_forks) = gitweb_check_feature('forks');
4019
4020 if ($check_forks) {
Petr Baudise30496d2006-10-24 05:33:17 +02004021 @forklist = git_get_projects_list($project);
4022 }
Jakub Narebski120ddde2006-09-19 14:33:22 +02004023
Kay Sieversede5e102005-08-07 20:23:12 +02004024 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004025 git_print_page_nav('summary','', $head);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004026
Kay Sievers19806692005-08-07 20:26:27 +02004027 print "<div class=\"title\">&nbsp;</div>\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01004028 print "<table class=\"projects_list\">\n" .
Kay Sievers40c13812005-11-19 17:41:29 +01004029 "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
Jakub Narebskice312af2007-08-28 16:05:43 +02004030 "<tr><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02004031 if (defined $cd{'rfc2822'}) {
4032 print "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
4033 }
4034
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02004035 # use per project git URL list in $projectroot/$project/cloneurl
4036 # or make project git URL from git base URL and project name
Jakub Narebski19a87212006-08-15 23:03:17 +02004037 my $url_tag = "URL";
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02004038 my @url_list = git_get_project_url_list($project);
4039 @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
4040 foreach my $git_url (@url_list) {
4041 next unless $git_url;
4042 print "<tr><td>$url_tag</td><td>$git_url</td></tr>\n";
Jakub Narebski19a87212006-08-15 23:03:17 +02004043 $url_tag = "";
4044 }
4045 print "</table>\n";
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004046
Petr Baudis447ef092006-10-24 05:23:46 +02004047 if (-s "$projectroot/$project/README.html") {
4048 if (open my $fd, "$projectroot/$project/README.html") {
Jakub Narebski9d066742007-11-19 14:16:12 +01004049 print "<div class=\"title\">readme</div>\n" .
4050 "<div class=\"readme\">\n";
Petr Baudis447ef092006-10-24 05:23:46 +02004051 print $_ while (<$fd>);
Jakub Narebski9d066742007-11-19 14:16:12 +01004052 print "\n</div>\n"; # class="readme"
Petr Baudis447ef092006-10-24 05:23:46 +02004053 close $fd;
4054 }
4055 }
4056
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004057 # we need to request one more than 16 (0..15) to check if
4058 # those 16 are all
Jakub Narebski785cdea2007-05-13 12:39:22 +02004059 my @commitlist = $head ? parse_commits($head, 17) : ();
4060 if (@commitlist) {
4061 git_print_header_div('shortlog');
4062 git_shortlog_body(\@commitlist, 0, 15, $refs,
4063 $#commitlist <= 15 ? undef :
4064 $cgi->a({-href => href(action=>"shortlog")}, "..."));
4065 }
Kay Sieversede5e102005-08-07 20:23:12 +02004066
Jakub Narebski120ddde2006-09-19 14:33:22 +02004067 if (@taglist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004068 git_print_header_div('tags');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004069 git_tags_body(\@taglist, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004070 $#taglist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004071 $cgi->a({-href => href(action=>"tags")}, "..."));
Kay Sieversede5e102005-08-07 20:23:12 +02004072 }
Kay Sievers0db37972005-08-07 20:24:35 +02004073
Jakub Narebski120ddde2006-09-19 14:33:22 +02004074 if (@headlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004075 git_print_header_div('heads');
Jakub Narebski120ddde2006-09-19 14:33:22 +02004076 git_heads_body(\@headlist, $head, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00004077 $#headlist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004078 $cgi->a({-href => href(action=>"heads")}, "..."));
Kay Sievers0db37972005-08-07 20:24:35 +02004079 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004080
Petr Baudise30496d2006-10-24 05:33:17 +02004081 if (@forklist) {
4082 git_print_header_div('forks');
4083 git_project_list_body(\@forklist, undef, 0, 15,
Robert Fitzsimonsaaca9672006-12-22 19:38:12 +00004084 $#forklist <= 15 ? undef :
Petr Baudise30496d2006-10-24 05:33:17 +02004085 $cgi->a({-href => href(action=>"forks")}, "..."),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004086 'noheader');
Petr Baudise30496d2006-10-24 05:33:17 +02004087 }
4088
Kay Sieversede5e102005-08-07 20:23:12 +02004089 git_footer_html();
4090}
4091
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004092sub git_tag {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004093 my $head = git_get_head_hash($project);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004094 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004095 git_print_page_nav('','', $head,undef,$head);
4096 my %tag = parse_tag($hash);
Jakub Narebski198a2a82007-05-12 21:16:34 +02004097
4098 if (! %tag) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004099 die_error(404, "Unknown tag object");
Jakub Narebski198a2a82007-05-12 21:16:34 +02004100 }
4101
Jakub Narebski847e01f2006-08-14 02:05:47 +02004102 git_print_header_div('commit', esc_html($tag{'name'}), $hash);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004103 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01004104 "<table class=\"object_header\">\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004105 "<tr>\n" .
4106 "<td>object</td>\n" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004107 "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4108 $tag{'object'}) . "</td>\n" .
4109 "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
4110 $tag{'type'}) . "</td>\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02004111 "</tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004112 if (defined($tag{'author'})) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004113 my %ad = parse_date($tag{'epoch'}, $tag{'tz'});
Kay Sievers40c13812005-11-19 17:41:29 +01004114 print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004115 print "<tr><td></td><td>" . $ad{'rfc2822'} .
4116 sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) .
4117 "</td></tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004118 }
4119 print "</table>\n\n" .
4120 "</div>\n";
4121 print "<div class=\"page_body\">";
4122 my $comment = $tag{'comment'};
4123 foreach my $line (@$comment) {
Junio C Hamano70022432006-11-24 14:04:01 -08004124 chomp $line;
Jakub Narebski793c4002006-11-24 22:25:50 +01004125 print esc_html($line, -nbsp=>1) . "<br/>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004126 }
4127 print "</div>\n";
4128 git_footer_html();
4129}
4130
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004131sub git_blame2 {
4132 my $fd;
4133 my $ftype;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304134
Lea Wiemann074afaa2008-06-19 22:03:21 +02004135 gitweb_check_feature('blame')
4136 or die_error(403, "Blame view not allowed");
4137
4138 die_error(400, "No file name given") unless $file_name;
Jakub Narebski847e01f2006-08-14 02:05:47 +02004139 $hash_base ||= git_get_head_hash($project);
Lea Wiemann074afaa2008-06-19 22:03:21 +02004140 die_error(404, "Couldn't find base commit") unless ($hash_base);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004141 my %co = parse_commit($hash_base)
Lea Wiemann074afaa2008-06-19 22:03:21 +02004142 or die_error(404, "Commit not found");
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004143 if (!defined $hash) {
4144 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02004145 or die_error(404, "Error looking up file");
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004146 }
4147 $ftype = git_get_type($hash);
4148 if ($ftype !~ "blob") {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004149 die_error(400, "Object is not a blob");
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004150 }
Junio C Hamano48fd6882006-10-12 00:47:03 -07004151 open ($fd, "-|", git_cmd(), "blame", '-p', '--',
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004152 $file_name, $hash_base)
Lea Wiemann074afaa2008-06-19 22:03:21 +02004153 or die_error(500, "Open git-blame failed");
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004154 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004155 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01004156 $cgi->a({-href => href(action=>"blob", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02004157 "blob") .
4158 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01004159 $cgi->a({-href => href(action=>"history", -replay=>1)},
4160 "history") .
Petr Baudiscae18622006-09-22 03:19:41 +02004161 " | " .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004162 $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004163 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004164 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4165 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004166 git_print_page_path($file_name, $ftype, $hash_base);
Luben Tuikov82f930d2006-08-04 15:09:59 -07004167 my @rev_color = (qw(light2 dark2));
Luben Tuikovcc1bf972006-07-23 13:37:53 -07004168 my $num_colors = scalar(@rev_color);
4169 my $current_color = 0;
4170 my $last_rev;
Jakub Narebski59b9f612006-08-22 23:42:53 +02004171 print <<HTML;
4172<div class="page_body">
4173<table class="blame">
4174<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
4175HTML
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004176 my %metainfo = ();
4177 while (1) {
4178 $_ = <$fd>;
4179 last unless defined $_;
Luben Tuikovd15c55a2006-10-11 00:30:05 -07004180 my ($full_rev, $orig_lineno, $lineno, $group_size) =
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004181 /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
4182 if (!exists $metainfo{$full_rev}) {
4183 $metainfo{$full_rev} = {};
4184 }
4185 my $meta = $metainfo{$full_rev};
4186 while (<$fd>) {
4187 last if (s/^\t//);
4188 if (/^(\S+) (.*)$/) {
4189 $meta->{$1} = $2;
4190 }
4191 }
4192 my $data = $_;
Junio C Hamano70022432006-11-24 14:04:01 -08004193 chomp $data;
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004194 my $rev = substr($full_rev, 0, 8);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004195 my $author = $meta->{'author'};
4196 my %date = parse_date($meta->{'author-time'},
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004197 $meta->{'author-tz'});
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004198 my $date = $date{'iso-tz'};
4199 if ($group_size) {
Luben Tuikovcc1bf972006-07-23 13:37:53 -07004200 $current_color = ++$current_color % $num_colors;
4201 }
4202 print "<tr class=\"$rev_color[$current_color]\">\n";
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004203 if ($group_size) {
4204 print "<td class=\"sha1\"";
Luben Tuikov5ad08282006-10-30 12:37:54 -08004205 print " title=\"". esc_html($author) . ", $date\"";
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004206 print " rowspan=\"$group_size\"" if ($group_size > 1);
4207 print ">";
4208 print $cgi->a({-href => href(action=>"commit",
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004209 hash=>$full_rev,
4210 file_name=>$file_name)},
4211 esc_html($rev));
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004212 print "</td>\n";
Luben Tuikov9dc5f8c2006-10-04 00:12:17 -07004213 }
Luben Tuikov244a70e2007-01-04 18:37:45 -08004214 open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
Lea Wiemann074afaa2008-06-19 22:03:21 +02004215 or die_error(500, "Open git-rev-parse failed");
Luben Tuikov244a70e2007-01-04 18:37:45 -08004216 my $parent_commit = <$dd>;
4217 close $dd;
4218 chomp($parent_commit);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004219 my $blamed = href(action => 'blame',
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004220 file_name => $meta->{'filename'},
4221 hash_base => $parent_commit);
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004222 print "<td class=\"linenr\">";
4223 print $cgi->a({ -href => "$blamed#l$orig_lineno",
Jakub Narebskia23f0a72007-04-01 22:21:38 +02004224 -id => "l$lineno",
4225 -class => "linenr" },
4226 esc_html($lineno));
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07004227 print "</td>";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004228 print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
4229 print "</tr>\n";
4230 }
4231 print "</table>\n";
4232 print "</div>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004233 close $fd
4234 or print "Reading blob failed\n";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07004235 git_footer_html();
4236}
4237
Florian Forstere34ef622006-06-11 17:45:19 +02004238sub git_blame {
4239 my $fd;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304240
Aneesh Kumar K.V1d3fc682006-09-01 09:13:32 +05304241 my ($have_blame) = gitweb_check_feature('blame');
4242 if (!$have_blame) {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304243 die_error('403 Permission denied', "Permission denied");
4244 }
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004245 die_error('404 Not Found', "File name not defined") if (!$file_name);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004246 $hash_base ||= git_get_head_hash($project);
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004247 die_error(undef, "Couldn't find base commit") unless ($hash_base);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004248 my %co = parse_commit($hash_base)
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004249 or die_error(undef, "Reading commit failed");
Florian Forstere34ef622006-06-11 17:45:19 +02004250 if (!defined $hash) {
4251 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004252 or die_error(undef, "Error lookup file");
Florian Forstere34ef622006-06-11 17:45:19 +02004253 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004254 open ($fd, "-|", git_cmd(), "annotate", '-l', '-t', '-r', $file_name, $hash_base)
Jakub Narebskicac4bd92006-08-05 13:13:53 +02004255 or die_error(undef, "Open git-annotate failed");
Florian Forstere34ef622006-06-11 17:45:19 +02004256 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004257 my $formats_nav =
Jakub Narebski952c65f2006-08-22 16:52:50 +02004258 $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
4259 "blob") .
4260 " | " .
Petr Baudiscae18622006-09-22 03:19:41 +02004261 $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
4262 "history") .
4263 " | " .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004264 $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004265 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004266 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4267 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004268 git_print_page_path($file_name, 'blob', $hash_base);
Florian Forstere34ef622006-06-11 17:45:19 +02004269 print "<div class=\"page_body\">\n";
4270 print <<HTML;
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004271<table class="blame">
Florian Forstere34ef622006-06-11 17:45:19 +02004272 <tr>
4273 <th>Commit</th>
4274 <th>Age</th>
4275 <th>Author</th>
4276 <th>Line</th>
4277 <th>Data</th>
4278 </tr>
4279HTML
4280 my @line_class = (qw(light dark));
4281 my $line_class_len = scalar (@line_class);
4282 my $line_class_num = $#line_class;
4283 while (my $line = <$fd>) {
4284 my $long_rev;
4285 my $short_rev;
4286 my $author;
4287 my $time;
4288 my $lineno;
4289 my $data;
4290 my $age;
4291 my $age_str;
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004292 my $age_class;
Florian Forstere34ef622006-06-11 17:45:19 +02004293
4294 chomp $line;
4295 $line_class_num = ($line_class_num + 1) % $line_class_len;
4296
Jakub Narebski030b5202006-08-26 02:13:05 +02004297 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 +02004298 $long_rev = $1;
4299 $author = $2;
4300 $time = $3;
4301 $lineno = $4;
4302 $data = $5;
4303 } else {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004304 print qq( <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
Florian Forstere34ef622006-06-11 17:45:19 +02004305 next;
4306 }
4307 $short_rev = substr ($long_rev, 0, 8);
4308 $age = time () - $time;
4309 $age_str = age_string ($age);
4310 $age_str =~ s/ /&nbsp;/g;
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004311 $age_class = age_class($age);
Florian Forstere34ef622006-06-11 17:45:19 +02004312 $author = esc_html ($author);
4313 $author =~ s/ /&nbsp;/g;
Jakub Narebskif16db172006-08-06 02:08:31 +02004314
4315 $data = untabify($data);
Florian Forstere34ef622006-06-11 17:45:19 +02004316 $data = esc_html ($data);
Florian Forstere34ef622006-06-11 17:45:19 +02004317
4318 print <<HTML;
4319 <tr class="$line_class[$line_class_num]">
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004320 <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td>
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004321 <td class="$age_class">$age_str</td>
Florian Forstere34ef622006-06-11 17:45:19 +02004322 <td>$author</td>
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004323 <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
4324 <td class="pre">$data</td>
Florian Forstere34ef622006-06-11 17:45:19 +02004325 </tr>
4326HTML
4327 } # while (my $line = <$fd>)
4328 print "</table>\n\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004329 close $fd
4330 or print "Reading blob failed.\n";
Florian Forstere34ef622006-06-11 17:45:19 +02004331 print "</div>";
4332 git_footer_html();
4333}
4334
Kay Sieversede5e102005-08-07 20:23:12 +02004335sub git_tags {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004336 my $head = git_get_head_hash($project);
Kay Sieversede5e102005-08-07 20:23:12 +02004337 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004338 git_print_page_nav('','', $head,undef,$head);
4339 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004340
Jakub Narebskicd146402006-11-02 20:23:11 +01004341 my @tagslist = git_get_tags_list();
4342 if (@tagslist) {
4343 git_tags_body(\@tagslist);
Kay Sieversede5e102005-08-07 20:23:12 +02004344 }
Kay Sieversede5e102005-08-07 20:23:12 +02004345 git_footer_html();
4346}
4347
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02004348sub git_heads {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004349 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02004350 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004351 git_print_page_nav('','', $head,undef,$head);
4352 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004353
Jakub Narebskicd146402006-11-02 20:23:11 +01004354 my @headslist = git_get_heads_list();
4355 if (@headslist) {
4356 git_heads_body(\@headslist, $head);
Kay Sievers0db37972005-08-07 20:24:35 +02004357 }
Kay Sievers0db37972005-08-07 20:24:35 +02004358 git_footer_html();
4359}
4360
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004361sub git_blob_plain {
Jakub Narebski7f718e82008-06-03 16:47:10 +02004362 my $type = shift;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004363 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004364
Luben Tuikovcff07712006-07-23 13:28:55 -07004365 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004366 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004367 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004368 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02004369 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004370 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004371 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004372 }
Martin Waitz800764c2006-09-16 23:09:02 +02004373 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4374 # blobs defined by non-textual hash id's can be cached
4375 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004376 }
Martin Waitz800764c2006-09-16 23:09:02 +02004377
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004378 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02004379 or die_error(500, "Open git-cat-file blob '$hash' failed");
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004380
Jakub Narebski7f718e82008-06-03 16:47:10 +02004381 # content-type (can include charset)
4382 $type = blob_contenttype($fd, $file_name, $type);
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004383
Jakub Narebski7f718e82008-06-03 16:47:10 +02004384 # "save as" filename, even when no $file_name is given
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004385 my $save_as = "$hash";
4386 if (defined $file_name) {
4387 $save_as = $file_name;
4388 } elsif ($type =~ m/^text\//) {
4389 $save_as .= '.txt';
4390 }
4391
Jakub Narebskif2e73302006-08-26 19:14:25 +02004392 print $cgi->header(
Jakub Narebski7f718e82008-06-03 16:47:10 +02004393 -type => $type,
4394 -expires => $expires,
4395 -content_disposition => 'inline; filename="' . $save_as . '"');
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004396 undef $/;
4397 binmode STDOUT, ':raw';
4398 print <$fd>;
4399 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
4400 $/ = "\n";
4401 close $fd;
4402}
4403
Kay Sievers09bd7892005-08-07 20:21:23 +02004404sub git_blob {
Jakub Narebskif2e73302006-08-26 19:14:25 +02004405 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02004406
Luben Tuikovcff07712006-07-23 13:28:55 -07004407 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004408 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004409 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004410 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02004411 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004412 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004413 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004414 }
Martin Waitz800764c2006-09-16 23:09:02 +02004415 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4416 # blobs defined by non-textual hash id's can be cached
4417 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02004418 }
Martin Waitz800764c2006-09-16 23:09:02 +02004419
Aneesh Kumar K.V1d3fc682006-09-01 09:13:32 +05304420 my ($have_blame) = gitweb_check_feature('blame');
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004421 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02004422 or die_error(500, "Couldn't cat $file_name, $hash");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004423 my $mimetype = blob_mimetype($fd, $file_name);
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004424 if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
Luben Tuikov930cf7d2006-07-09 20:18:57 -07004425 close $fd;
4426 return git_blob_plain($mimetype);
4427 }
Jakub Narebski5a4cf332006-12-04 23:47:22 +01004428 # we can have blame only for text/* mimetype
4429 $have_blame &&= ($mimetype =~ m!^text/!);
4430
Jakub Narebskif2e73302006-08-26 19:14:25 +02004431 git_header_html(undef, $expires);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004432 my $formats_nav = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02004433 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Kay Sievers93129442005-10-17 03:27:54 +02004434 if (defined $file_name) {
Florian Forster5996ca02006-06-12 10:31:57 +02004435 if ($have_blame) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004436 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004437 $cgi->a({-href => href(action=>"blame", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02004438 "blame") .
4439 " | ";
Florian Forster5996ca02006-06-12 10:31:57 +02004440 }
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004441 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004442 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02004443 "history") .
4444 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01004445 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02004446 "raw") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004447 " | " .
4448 $cgi->a({-href => href(action=>"blob",
4449 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004450 "HEAD");
Kay Sievers93129442005-10-17 03:27:54 +02004451 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004452 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01004453 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
4454 "raw");
Kay Sievers93129442005-10-17 03:27:54 +02004455 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004456 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4457 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02004458 } else {
4459 print "<div class=\"page_nav\">\n" .
4460 "<br/><br/></div>\n" .
4461 "<div class=\"title\">$hash</div>\n";
4462 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004463 git_print_page_path($file_name, "blob", $hash_base);
Kay Sieversc07ad4b2005-08-07 20:22:44 +02004464 print "<div class=\"page_body\">\n";
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004465 if ($mimetype =~ m!^image/!) {
Jakub Narebski5a4cf332006-12-04 23:47:22 +01004466 print qq!<img type="$mimetype"!;
4467 if ($file_name) {
4468 print qq! alt="$file_name" title="$file_name"!;
4469 }
4470 print qq! src="! .
4471 href(action=>"blob_plain", hash=>$hash,
4472 hash_base=>$hash_base, file_name=>$file_name) .
4473 qq!" />\n!;
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01004474 } else {
4475 my $nr;
4476 while (my $line = <$fd>) {
4477 chomp $line;
4478 $nr++;
4479 $line = untabify($line);
4480 printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
4481 $nr, $nr, $nr, esc_html($line, -nbsp=>1);
4482 }
Kay Sievers161332a2005-08-07 19:49:46 +02004483 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02004484 close $fd
4485 or print "Reading blob failed.\n";
Kay Sieversfbb592a2005-08-07 20:12:11 +02004486 print "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02004487 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004488}
4489
4490sub git_tree {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004491 if (!defined $hash_base) {
4492 $hash_base = "HEAD";
4493 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02004494 if (!defined $hash) {
Kay Sievers09bd7892005-08-07 20:21:23 +02004495 if (defined $file_name) {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004496 $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
4497 } else {
4498 $hash = $hash_base;
Kay Sievers10dba282005-08-07 20:25:27 +02004499 }
Kay Sieverse925f382005-08-07 20:23:35 +02004500 }
Kay Sievers232ff552005-11-24 16:56:55 +01004501 $/ = "\0";
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004502 open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02004503 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski0881d2d2006-07-30 14:58:11 +02004504 my @entries = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02004505 close $fd or die_error(404, "Reading tree failed");
Kay Sievers232ff552005-11-24 16:56:55 +01004506 $/ = "\n";
Kay Sieversd63577d2005-08-07 20:18:13 +02004507
Jakub Narebski847e01f2006-08-14 02:05:47 +02004508 my $refs = git_get_references();
4509 my $ref = format_ref_marker($refs, $hash_base);
Kay Sievers12a88f22005-08-07 20:02:47 +02004510 git_header_html();
Jakub Narebski300454f2006-10-21 17:53:09 +02004511 my $basedir = '';
Aneesh Kumar K.V1d3fc682006-09-01 09:13:32 +05304512 my ($have_blame) = gitweb_check_feature('blame');
Jakub Narebski847e01f2006-08-14 02:05:47 +02004513 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Petr Baudiscae18622006-09-22 03:19:41 +02004514 my @views_nav = ();
4515 if (defined $file_name) {
4516 push @views_nav,
Jakub Narebskia3823e52007-11-01 13:06:29 +01004517 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02004518 "history"),
4519 $cgi->a({-href => href(action=>"tree",
4520 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02004521 "HEAD"),
Petr Baudiscae18622006-09-22 03:19:41 +02004522 }
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004523 my $snapshot_links = format_snapshot_links($hash);
4524 if (defined $snapshot_links) {
Petr Baudiscae18622006-09-22 03:19:41 +02004525 # FIXME: Should be available when we have no hash base as well.
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004526 push @views_nav, $snapshot_links;
Petr Baudiscae18622006-09-22 03:19:41 +02004527 }
4528 git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
Jakub Narebski847e01f2006-08-14 02:05:47 +02004529 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
Kay Sieversd63577d2005-08-07 20:18:13 +02004530 } else {
Jakub Narebskifa702002006-08-31 00:35:07 +02004531 undef $hash_base;
Kay Sieversd63577d2005-08-07 20:18:13 +02004532 print "<div class=\"page_nav\">\n";
4533 print "<br/><br/></div>\n";
4534 print "<div class=\"title\">$hash</div>\n";
4535 }
Kay Sievers09bd7892005-08-07 20:21:23 +02004536 if (defined $file_name) {
Jakub Narebski300454f2006-10-21 17:53:09 +02004537 $basedir = $file_name;
4538 if ($basedir ne '' && substr($basedir, -1) ne '/') {
4539 $basedir .= '/';
4540 }
Kay Sievers09bd7892005-08-07 20:21:23 +02004541 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07004542 git_print_page_path($file_name, 'tree', $hash_base);
Kay Sieversfbb592a2005-08-07 20:12:11 +02004543 print "<div class=\"page_body\">\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01004544 print "<table class=\"tree\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004545 my $alternate = 1;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02004546 # '..' (top directory) link if possible
4547 if (defined $hash_base &&
4548 defined $file_name && $file_name =~ m![^/]+$!) {
4549 if ($alternate) {
4550 print "<tr class=\"dark\">\n";
4551 } else {
4552 print "<tr class=\"light\">\n";
4553 }
4554 $alternate ^= 1;
4555
4556 my $up = $file_name;
4557 $up =~ s!/?[^/]+$!!;
4558 undef $up unless $up;
4559 # based on git_print_tree_entry
4560 print '<td class="mode">' . mode_str('040000') . "</td>\n";
4561 print '<td class="list">';
4562 print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
4563 file_name=>$up)},
4564 "..");
4565 print "</td>\n";
4566 print "<td class=\"link\"></td>\n";
4567
4568 print "</tr>\n";
4569 }
Kay Sievers161332a2005-08-07 19:49:46 +02004570 foreach my $line (@entries) {
Jakub Narebskicb849b42006-08-31 00:32:15 +02004571 my %t = parse_ls_tree_line($line, -z => 1);
4572
Kay Sieversbddec012005-08-07 20:25:42 +02004573 if ($alternate) {
Kay Sieversc994d622005-08-07 20:27:18 +02004574 print "<tr class=\"dark\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004575 } else {
Kay Sieversc994d622005-08-07 20:27:18 +02004576 print "<tr class=\"light\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004577 }
4578 $alternate ^= 1;
Jakub Narebskicb849b42006-08-31 00:32:15 +02004579
Jakub Narebski300454f2006-10-21 17:53:09 +02004580 git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
Jakub Narebskifa702002006-08-31 00:35:07 +02004581
Kay Sievers42f7eb92005-08-07 20:21:46 +02004582 print "</tr>\n";
Kay Sievers161332a2005-08-07 19:49:46 +02004583 }
Kay Sievers42f7eb92005-08-07 20:21:46 +02004584 print "</table>\n" .
4585 "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02004586 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004587}
4588
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304589sub git_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004590 my @supported_fmts = gitweb_check_feature('snapshot');
Jakub Narebskia7817852007-07-22 23:41:20 +02004591 @supported_fmts = filter_snapshot_fmts(@supported_fmts);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004592
4593 my $format = $cgi->param('sf');
Jakub Narebski3473e7d2007-07-25 01:19:58 +02004594 if (!@supported_fmts) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004595 die_error(403, "Snapshots not allowed");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02004596 }
4597 # default to first supported snapshot format
4598 $format ||= $supported_fmts[0];
4599 if ($format !~ m/^[a-z0-9]+$/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004600 die_error(400, "Invalid snapshot format parameter");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02004601 } elsif (!exists($known_snapshot_formats{$format})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004602 die_error(400, "Unknown snapshot format");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02004603 } elsif (!grep($_ eq $format, @supported_fmts)) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004604 die_error(403, "Unsupported snapshot format");
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304605 }
4606
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304607 if (!defined $hash) {
4608 $hash = git_get_head_hash($project);
4609 }
4610
Mark Levedahl072570e2007-05-20 11:46:46 -04004611 my $name = $project;
Matthias Lederhofer9a7d9412007-06-07 11:27:08 +02004612 $name =~ s,([^/])/*\.git$,$1,;
4613 $name = basename($name);
4614 my $filename = to_utf8($name);
Mark Levedahl072570e2007-05-20 11:46:46 -04004615 $name =~ s/\047/\047\\\047\047/g;
Mark Levedahl072570e2007-05-20 11:46:46 -04004616 my $cmd;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004617 $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
Lea Wiemann516381d2008-06-17 23:46:35 +02004618 $cmd = quote_command(
4619 git_cmd(), 'archive',
4620 "--format=$known_snapshot_formats{$format}{'format'}",
4621 "--prefix=$name/", $hash);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004622 if (exists $known_snapshot_formats{$format}{'compressor'}) {
Lea Wiemann516381d2008-06-17 23:46:35 +02004623 $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
Mark Levedahl072570e2007-05-20 11:46:46 -04004624 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304625
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02004626 print $cgi->header(
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004627 -type => $known_snapshot_formats{$format}{'type'},
Luben Tuikova2a3bf72006-09-28 16:51:43 -07004628 -content_disposition => 'inline; filename="' . "$filename" . '"',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02004629 -status => '200 OK');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304630
Mark Levedahl072570e2007-05-20 11:46:46 -04004631 open my $fd, "-|", $cmd
Lea Wiemann074afaa2008-06-19 22:03:21 +02004632 or die_error(500, "Execute git-archive failed");
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304633 binmode STDOUT, ':raw';
4634 print <$fd>;
4635 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
4636 close $fd;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304637}
4638
Kay Sievers09bd7892005-08-07 20:21:23 +02004639sub git_log {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004640 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02004641 if (!defined $hash) {
Kay Sievers19806692005-08-07 20:26:27 +02004642 $hash = $head;
Kay Sievers0db37972005-08-07 20:24:35 +02004643 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02004644 if (!defined $page) {
4645 $page = 0;
Kay Sieversb87d78d2005-08-07 20:21:04 +02004646 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004647 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02004648
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004649 my @commitlist = parse_commits($hash, 101, (100 * $page));
Kay Sieversea4a6df2005-08-07 20:26:49 +02004650
Lea Wiemann1f684dc2008-05-28 01:25:42 +02004651 my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004652
4653 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02004654 git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02004655
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004656 if (!@commitlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004657 my %co = parse_commit($hash);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02004658
Jakub Narebski847e01f2006-08-14 02:05:47 +02004659 git_print_header_div('summary', $project);
Kay Sieverse925f382005-08-07 20:23:35 +02004660 print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
Kay Sievers034df392005-08-07 20:20:07 +02004661 }
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004662 my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
4663 for (my $i = 0; $i <= $to; $i++) {
4664 my %co = %{$commitlist[$i]};
Kay Sieversb87d78d2005-08-07 20:21:04 +02004665 next if !%co;
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004666 my $commit = $co{'id'};
4667 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski847e01f2006-08-14 02:05:47 +02004668 my %ad = parse_date($co{'author_epoch'});
4669 git_print_header_div('commit',
Jakub Narebski26298b52006-08-10 12:38:47 +02004670 "<span class=\"age\">$co{'age_string'}</span>" .
4671 esc_html($co{'title'}) . $ref,
4672 $commit);
Kay Sievers034df392005-08-07 20:20:07 +02004673 print "<div class=\"title_text\">\n" .
4674 "<div class=\"log_link\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004675 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004676 " | " .
4677 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
Petr Baudis6ef4cb22006-09-22 03:19:48 +02004678 " | " .
Petr Baudisd7267202006-09-22 16:56:43 -07004679 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
Kay Sieverseb282402005-08-07 20:21:34 +02004680 "<br/>\n" .
Kay Sievers034df392005-08-07 20:20:07 +02004681 "</div>\n" .
Kay Sievers40c13812005-11-19 17:41:29 +01004682 "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" .
Jakub Narebskid16d0932006-08-17 11:21:23 +02004683 "</div>\n";
4684
4685 print "<div class=\"log_body\">\n";
Jakub Narebskif2069412006-10-24 13:52:46 +02004686 git_print_log($co{'comment'}, -final_empty_line=> 1);
Kay Sievers09bd7892005-08-07 20:21:23 +02004687 print "</div>\n";
Kay Sievers034df392005-08-07 20:20:07 +02004688 }
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004689 if ($#commitlist >= 100) {
4690 print "<div class=\"page_nav\">\n";
Jakub Narebski7afd77b2007-11-01 13:06:28 +01004691 print $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimons719dad22006-12-24 14:31:45 +00004692 -accesskey => "n", -title => "Alt-n"}, "next");
4693 print "</div>\n";
4694 }
Kay Sievers034df392005-08-07 20:20:07 +02004695 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004696}
4697
4698sub git_commit {
Jakub Narebski9954f772006-11-18 23:35:41 +01004699 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02004700 my %co = parse_commit($hash)
4701 or die_error(404, "Unknown commit object");
Jakub Narebski847e01f2006-08-14 02:05:47 +02004702 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
4703 my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
Kay Sievers161332a2005-08-07 19:49:46 +02004704
Jakub Narebskic9d193d2006-12-15 21:57:16 +01004705 my $parent = $co{'parent'};
4706 my $parents = $co{'parents'}; # listref
4707
4708 # we need to prepare $formats_nav before any parameter munging
4709 my $formats_nav;
4710 if (!defined $parent) {
4711 # --root commitdiff
4712 $formats_nav .= '(initial)';
4713 } elsif (@$parents == 1) {
4714 # single parent commit
4715 $formats_nav .=
4716 '(parent: ' .
4717 $cgi->a({-href => href(action=>"commit",
4718 hash=>$parent)},
4719 esc_html(substr($parent, 0, 7))) .
4720 ')';
4721 } else {
4722 # merge commit
4723 $formats_nav .=
4724 '(merge: ' .
4725 join(' ', map {
Jakub Narebskif9308a12007-03-23 21:04:31 +01004726 $cgi->a({-href => href(action=>"commit",
Jakub Narebskic9d193d2006-12-15 21:57:16 +01004727 hash=>$_)},
4728 esc_html(substr($_, 0, 7)));
4729 } @$parents ) .
4730 ')';
4731 }
4732
Kay Sieversd8a20ba2005-08-07 20:28:53 +02004733 if (!defined $parent) {
Jakub Narebskib9182982006-07-30 18:28:34 -07004734 $parent = "--root";
Kay Sievers6191f8e2005-08-07 20:19:56 +02004735 }
Jakub Narebski549ab4a2006-12-15 17:53:45 +01004736 my @difftree;
Jakub Narebski208ecb22007-05-07 01:10:08 +02004737 open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
4738 @diff_opts,
4739 (@$parents <= 1 ? $parent : '-c'),
4740 $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02004741 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski208ecb22007-05-07 01:10:08 +02004742 @difftree = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02004743 close $fd or die_error(404, "Reading git-diff-tree failed");
Kay Sievers11044292005-10-19 03:18:45 +02004744
4745 # non-textual hash id's can be cached
4746 my $expires;
4747 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
4748 $expires = "+1d";
4749 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02004750 my $refs = git_get_references();
4751 my $ref = format_ref_marker($refs, $co{'id'});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304752
Jakub Narebski594e2122006-07-31 02:21:52 +02004753 git_header_html(undef, $expires);
Petr Baudisa1441542006-10-06 18:59:33 +02004754 git_print_page_nav('commit', '',
Jakub Narebski952c65f2006-08-22 16:52:50 +02004755 $hash, $co{'tree'}, $hash,
Jakub Narebskic9d193d2006-12-15 21:57:16 +01004756 $formats_nav);
Luben Tuikov4f7b34c2006-07-23 13:36:32 -07004757
Kay Sieversb87d78d2005-08-07 20:21:04 +02004758 if (defined $co{'parent'}) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004759 git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02004760 } else {
Jakub Narebski847e01f2006-08-14 02:05:47 +02004761 git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02004762 }
Kay Sievers6191f8e2005-08-07 20:19:56 +02004763 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01004764 "<table class=\"object_header\">\n";
Kay Sievers40c13812005-11-19 17:41:29 +01004765 print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
Kay Sieversbddec012005-08-07 20:25:42 +02004766 "<tr>" .
4767 "<td></td><td> $ad{'rfc2822'}";
Kay Sievers927dcec2005-08-07 20:18:44 +02004768 if ($ad{'hour_local'} < 6) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004769 printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
4770 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
Kay Sieversb87d78d2005-08-07 20:21:04 +02004771 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004772 printf(" (%02d:%02d %s)",
4773 $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
Kay Sievers927dcec2005-08-07 20:18:44 +02004774 }
Kay Sieversbddec012005-08-07 20:25:42 +02004775 print "</td>" .
4776 "</tr>\n";
Kay Sievers40c13812005-11-19 17:41:29 +01004777 print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004778 print "<tr><td></td><td> $cd{'rfc2822'}" .
4779 sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) .
4780 "</td></tr>\n";
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004781 print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
Kay Sieversbddec012005-08-07 20:25:42 +02004782 print "<tr>" .
4783 "<td>tree</td>" .
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00004784 "<td class=\"sha1\">" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004785 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
4786 class => "list"}, $co{'tree'}) .
Kay Sievers19806692005-08-07 20:26:27 +02004787 "</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004788 "<td class=\"link\">" .
4789 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
4790 "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004791 my $snapshot_links = format_snapshot_links($hash);
4792 if (defined $snapshot_links) {
4793 print " | " . $snapshot_links;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304794 }
4795 print "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02004796 "</tr>\n";
Jakub Narebski549ab4a2006-12-15 17:53:45 +01004797
Kay Sievers3e029292005-08-07 20:05:15 +02004798 foreach my $par (@$parents) {
Kay Sieversbddec012005-08-07 20:25:42 +02004799 print "<tr>" .
4800 "<td>parent</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004801 "<td class=\"sha1\">" .
4802 $cgi->a({-href => href(action=>"commit", hash=>$par),
4803 class => "list"}, $par) .
4804 "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02004805 "<td class=\"link\">" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004806 $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02004807 " | " .
Jakub Narebskif2e60942006-09-03 23:43:03 +02004808 $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
Kay Sieversbddec012005-08-07 20:25:42 +02004809 "</td>" .
4810 "</tr>\n";
Kay Sievers3e029292005-08-07 20:05:15 +02004811 }
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02004812 print "</table>".
Kay Sieversb87d78d2005-08-07 20:21:04 +02004813 "</div>\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02004814
Kay Sieversfbb592a2005-08-07 20:12:11 +02004815 print "<div class=\"page_body\">\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02004816 git_print_log($co{'comment'});
Kay Sievers927dcec2005-08-07 20:18:44 +02004817 print "</div>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004818
Jakub Narebski208ecb22007-05-07 01:10:08 +02004819 git_difftree_body(\@difftree, $hash, @$parents);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004820
Kay Sievers12a88f22005-08-07 20:02:47 +02004821 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02004822}
4823
Jakub Narebskica946012006-12-10 13:25:47 +01004824sub git_object {
4825 # object is defined by:
4826 # - hash or hash_base alone
4827 # - hash_base and file_name
4828 my $type;
4829
4830 # - hash or hash_base alone
4831 if ($hash || ($hash_base && !defined $file_name)) {
4832 my $object_id = $hash || $hash_base;
4833
Lea Wiemann516381d2008-06-17 23:46:35 +02004834 open my $fd, "-|", quote_command(
4835 git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
Lea Wiemann074afaa2008-06-19 22:03:21 +02004836 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01004837 $type = <$fd>;
4838 chomp $type;
4839 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02004840 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01004841
4842 # - hash_base and file_name
4843 } elsif ($hash_base && defined $file_name) {
4844 $file_name =~ s,/+$,,;
4845
4846 system(git_cmd(), "cat-file", '-e', $hash_base) == 0
Lea Wiemann074afaa2008-06-19 22:03:21 +02004847 or die_error(404, "Base object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01004848
4849 # here errors should not hapen
4850 open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02004851 or die_error(500, "Open git-ls-tree failed");
Jakub Narebskica946012006-12-10 13:25:47 +01004852 my $line = <$fd>;
4853 close $fd;
4854
4855 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
4856 unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004857 die_error(404, "File or directory for given base does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01004858 }
4859 $type = $2;
4860 $hash = $3;
4861 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004862 die_error(400, "Not enough information to find object");
Jakub Narebskica946012006-12-10 13:25:47 +01004863 }
4864
4865 print $cgi->redirect(-uri => href(action=>$type, -full=>1,
4866 hash=>$hash, hash_base=>$hash_base,
4867 file_name=>$file_name),
4868 -status => '302 Found');
4869}
4870
Kay Sievers09bd7892005-08-07 20:21:23 +02004871sub git_blobdiff {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004872 my $format = shift || 'html';
4873
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004874 my $fd;
4875 my @difftree;
4876 my %diffinfo;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004877 my $expires;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004878
4879 # preparing $fd and %diffinfo for git_patchset_body
4880 # new style URI
4881 if (defined $hash_base && defined $hash_parent_base) {
4882 if (defined $file_name) {
4883 # read raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01004884 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
4885 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02004886 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02004887 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004888 @difftree = map { chomp; $_ } <$fd>;
4889 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02004890 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004891 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02004892 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004893
Jakub Narebski0aea3372006-08-27 23:45:26 +02004894 } elsif (defined $hash &&
4895 $hash =~ /[0-9a-fA-F]{40}/) {
4896 # try to find filename from $hash
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004897
4898 # read filtered raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01004899 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
4900 $hash_parent_base, $hash_base, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02004901 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004902 @difftree =
4903 # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
4904 # $hash == to_id
4905 grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
4906 map { chomp; $_ } <$fd>;
4907 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02004908 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004909 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02004910 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004911
4912 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004913 die_error(400, "Missing one of the blob diff parameters");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004914 }
4915
4916 if (@difftree > 1) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004917 die_error(400, "Ambiguous blob diff specification");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004918 }
4919
4920 %diffinfo = parse_difftree_raw_line($difftree[0]);
Jakub Narebski9d301452007-11-01 12:38:08 +01004921 $file_parent ||= $diffinfo{'from_file'} || $file_name;
4922 $file_name ||= $diffinfo{'to_file'};
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004923
4924 $hash_parent ||= $diffinfo{'from_id'};
4925 $hash ||= $diffinfo{'to_id'};
4926
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004927 # non-textual hash id's can be cached
4928 if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
4929 $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
4930 $expires = '+1d';
4931 }
4932
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004933 # open patch output
Dennis Stosberg25691fb2006-08-28 17:49:58 +02004934 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski957d6ea2007-04-05 13:45:41 +02004935 '-p', ($format eq 'html' ? "--full-index" : ()),
4936 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02004937 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02004938 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004939 }
4940
4941 # old/legacy style URI
4942 if (!%diffinfo && # if new style URI failed
4943 defined $hash && defined $hash_parent) {
4944 # fake git-diff-tree raw output
4945 $diffinfo{'from_mode'} = $diffinfo{'to_mode'} = "blob";
4946 $diffinfo{'from_id'} = $hash_parent;
4947 $diffinfo{'to_id'} = $hash;
4948 if (defined $file_name) {
4949 if (defined $file_parent) {
4950 $diffinfo{'status'} = '2';
Petr Baudis83915482006-09-24 14:57:40 -07004951 $diffinfo{'from_file'} = $file_parent;
4952 $diffinfo{'to_file'} = $file_name;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004953 } else { # assume not renamed
4954 $diffinfo{'status'} = '1';
Petr Baudis83915482006-09-24 14:57:40 -07004955 $diffinfo{'from_file'} = $file_name;
4956 $diffinfo{'to_file'} = $file_name;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004957 }
4958 } else { # no filename given
4959 $diffinfo{'status'} = '2';
4960 $diffinfo{'from_file'} = $hash_parent;
4961 $diffinfo{'to_file'} = $hash;
4962 }
4963
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004964 # non-textual hash id's can be cached
4965 if ($hash =~ m/^[0-9a-fA-F]{40}$/ &&
4966 $hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
4967 $expires = '+1d';
4968 }
4969
4970 # open patch output
Jakub Narebski957d6ea2007-04-05 13:45:41 +02004971 open $fd, "-|", git_cmd(), "diff", @diff_opts,
4972 '-p', ($format eq 'html' ? "--full-index" : ()),
Jakub Narebski45bd0c82006-10-30 22:29:06 +01004973 $hash_parent, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02004974 or die_error(500, "Open git-diff failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004975 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02004976 die_error(400, "Missing one of the blob diff parameters")
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02004977 unless %diffinfo;
4978 }
4979
4980 # header
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004981 if ($format eq 'html') {
4982 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01004983 $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02004984 "raw");
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02004985 git_header_html(undef, $expires);
4986 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
4987 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
4988 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
4989 } else {
4990 print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
4991 print "<div class=\"title\">$hash vs $hash_parent</div>\n";
4992 }
4993 if (defined $file_name) {
4994 git_print_page_path($file_name, "blob", $hash_base);
4995 } else {
4996 print "<div class=\"page_path\"></div>\n";
4997 }
4998
4999 } elsif ($format eq 'plain') {
5000 print $cgi->header(
5001 -type => 'text/plain',
5002 -charset => 'utf-8',
5003 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07005004 -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005005
5006 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
5007
Kay Sievers09bd7892005-08-07 20:21:23 +02005008 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005009 die_error(400, "Unknown blobdiff format");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005010 }
5011
5012 # patch
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005013 if ($format eq 'html') {
5014 print "<div class=\"page_body\">\n";
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005015
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005016 git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
5017 close $fd;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02005018
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005019 print "</div>\n"; # class="page_body"
5020 git_footer_html();
5021
5022 } else {
5023 while (my $line = <$fd>) {
Jakub Narebski403d0902006-11-08 11:48:56 +01005024 $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
5025 $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005026
5027 print $line;
5028
5029 last if $line =~ m!^\+\+\+!;
5030 }
5031 local $/ = undef;
5032 print <$fd>;
5033 close $fd;
5034 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005035}
5036
Kay Sievers19806692005-08-07 20:26:27 +02005037sub git_blobdiff_plain {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02005038 git_blobdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02005039}
5040
Kay Sievers09bd7892005-08-07 20:21:23 +02005041sub git_commitdiff {
Jakub Narebskieee08902006-08-24 00:15:14 +02005042 my $format = shift || 'html';
Jakub Narebski9954f772006-11-18 23:35:41 +01005043 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02005044 my %co = parse_commit($hash)
5045 or die_error(404, "Unknown commit object");
Jakub Narebski151602d2006-10-23 00:37:56 +02005046
Jakub Narebskicd030c32007-06-08 13:33:28 +02005047 # choose format for commitdiff for merge
5048 if (! defined $hash_parent && @{$co{'parents'}} > 1) {
5049 $hash_parent = '--cc';
5050 }
5051 # we need to prepare $formats_nav before almost any parameter munging
Jakub Narebski151602d2006-10-23 00:37:56 +02005052 my $formats_nav;
5053 if ($format eq 'html') {
5054 $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01005055 $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
Jakub Narebski151602d2006-10-23 00:37:56 +02005056 "raw");
5057
Jakub Narebskicd030c32007-06-08 13:33:28 +02005058 if (defined $hash_parent &&
5059 $hash_parent ne '-c' && $hash_parent ne '--cc') {
Jakub Narebski151602d2006-10-23 00:37:56 +02005060 # commitdiff with two commits given
5061 my $hash_parent_short = $hash_parent;
5062 if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
5063 $hash_parent_short = substr($hash_parent, 0, 7);
5064 }
5065 $formats_nav .=
Jakub Narebskiada3e1f2007-06-08 13:26:31 +02005066 ' (from';
5067 for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
5068 if ($co{'parents'}[$i] eq $hash_parent) {
5069 $formats_nav .= ' parent ' . ($i+1);
5070 last;
5071 }
5072 }
5073 $formats_nav .= ': ' .
Jakub Narebski151602d2006-10-23 00:37:56 +02005074 $cgi->a({-href => href(action=>"commitdiff",
5075 hash=>$hash_parent)},
5076 esc_html($hash_parent_short)) .
5077 ')';
5078 } elsif (!$co{'parent'}) {
5079 # --root commitdiff
5080 $formats_nav .= ' (initial)';
5081 } elsif (scalar @{$co{'parents'}} == 1) {
5082 # single parent commit
5083 $formats_nav .=
5084 ' (parent: ' .
5085 $cgi->a({-href => href(action=>"commitdiff",
5086 hash=>$co{'parent'})},
5087 esc_html(substr($co{'parent'}, 0, 7))) .
5088 ')';
5089 } else {
5090 # merge commit
Jakub Narebskicd030c32007-06-08 13:33:28 +02005091 if ($hash_parent eq '--cc') {
5092 $formats_nav .= ' | ' .
5093 $cgi->a({-href => href(action=>"commitdiff",
5094 hash=>$hash, hash_parent=>'-c')},
5095 'combined');
5096 } else { # $hash_parent eq '-c'
5097 $formats_nav .= ' | ' .
5098 $cgi->a({-href => href(action=>"commitdiff",
5099 hash=>$hash, hash_parent=>'--cc')},
5100 'compact');
5101 }
Jakub Narebski151602d2006-10-23 00:37:56 +02005102 $formats_nav .=
5103 ' (merge: ' .
5104 join(' ', map {
5105 $cgi->a({-href => href(action=>"commitdiff",
5106 hash=>$_)},
5107 esc_html(substr($_, 0, 7)));
5108 } @{$co{'parents'}} ) .
5109 ')';
5110 }
5111 }
5112
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005113 my $hash_parent_param = $hash_parent;
Jakub Narebskicd030c32007-06-08 13:33:28 +02005114 if (!defined $hash_parent_param) {
5115 # --cc for multiple parents, --root for parentless
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005116 $hash_parent_param =
Jakub Narebskicd030c32007-06-08 13:33:28 +02005117 @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
Kay Sieversbddec012005-08-07 20:25:42 +02005118 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005119
5120 # read commitdiff
5121 my $fd;
5122 my @difftree;
Jakub Narebskieee08902006-08-24 00:15:14 +02005123 if ($format eq 'html') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005124 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski45bd0c82006-10-30 22:29:06 +01005125 "--no-commit-id", "--patch-with-raw", "--full-index",
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005126 $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005127 or die_error(500, "Open git-diff-tree failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02005128
Jakub Narebski04408c32006-11-18 23:35:38 +01005129 while (my $line = <$fd>) {
5130 chomp $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02005131 # empty line ends raw part of diff-tree output
5132 last unless $line;
Jakub Narebski493e01d2007-05-07 01:10:06 +02005133 push @difftree, scalar parse_difftree_raw_line($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02005134 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005135
Jakub Narebskieee08902006-08-24 00:15:14 +02005136 } elsif ($format eq 'plain') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005137 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskifb1dde42007-05-07 01:10:07 +02005138 '-p', $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02005139 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski157e43b2006-08-24 19:34:36 +02005140
Jakub Narebskieee08902006-08-24 00:15:14 +02005141 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005142 die_error(400, "Unknown commitdiff format");
Jakub Narebskieee08902006-08-24 00:15:14 +02005143 }
Kay Sievers4c02e3c2005-08-07 19:52:52 +02005144
Kay Sievers11044292005-10-19 03:18:45 +02005145 # non-textual hash id's can be cached
5146 my $expires;
5147 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5148 $expires = "+1d";
5149 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005150
Jakub Narebskieee08902006-08-24 00:15:14 +02005151 # write commit message
5152 if ($format eq 'html') {
5153 my $refs = git_get_references();
5154 my $ref = format_ref_marker($refs, $co{'id'});
Kay Sievers19806692005-08-07 20:26:27 +02005155
Jakub Narebskieee08902006-08-24 00:15:14 +02005156 git_header_html(undef, $expires);
5157 git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
5158 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
Jakub Narebski6fd92a22006-08-28 14:48:12 +02005159 git_print_authorship(\%co);
Jakub Narebskieee08902006-08-24 00:15:14 +02005160 print "<div class=\"page_body\">\n";
Jakub Narebski82560982006-10-24 13:55:33 +02005161 if (@{$co{'comment'}} > 1) {
5162 print "<div class=\"log\">\n";
5163 git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
5164 print "</div>\n"; # class="log"
5165 }
Kay Sievers1b1cd422005-08-07 20:28:01 +02005166
Jakub Narebskieee08902006-08-24 00:15:14 +02005167 } elsif ($format eq 'plain') {
5168 my $refs = git_get_references("tags");
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005169 my $tagname = git_get_rev_name_tags($hash);
Jakub Narebskieee08902006-08-24 00:15:14 +02005170 my $filename = basename($project) . "-$hash.patch";
5171
5172 print $cgi->header(
5173 -type => 'text/plain',
5174 -charset => 'utf-8',
5175 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07005176 -content_disposition => 'inline; filename="' . "$filename" . '"');
Jakub Narebskieee08902006-08-24 00:15:14 +02005177 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
Yasushi SHOJI77202242008-01-29 21:16:02 +09005178 print "From: " . to_utf8($co{'author'}) . "\n";
5179 print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
5180 print "Subject: " . to_utf8($co{'title'}) . "\n";
5181
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005182 print "X-Git-Tag: $tagname\n" if $tagname;
Jakub Narebskieee08902006-08-24 00:15:14 +02005183 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
Jakub Narebskiedf735a2006-08-24 19:45:30 +02005184
Jakub Narebskieee08902006-08-24 00:15:14 +02005185 foreach my $line (@{$co{'comment'}}) {
Yasushi SHOJI77202242008-01-29 21:16:02 +09005186 print to_utf8($line) . "\n";
Kay Sievers19806692005-08-07 20:26:27 +02005187 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005188 print "---\n\n";
Kay Sievers19806692005-08-07 20:26:27 +02005189 }
Jakub Narebskieee08902006-08-24 00:15:14 +02005190
5191 # write patch
5192 if ($format eq 'html') {
Jakub Narebskicd030c32007-06-08 13:33:28 +02005193 my $use_parents = !defined $hash_parent ||
5194 $hash_parent eq '-c' || $hash_parent eq '--cc';
5195 git_difftree_body(\@difftree, $hash,
5196 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebskib4657e72006-08-28 14:48:14 +02005197 print "<br/>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02005198
Jakub Narebskicd030c32007-06-08 13:33:28 +02005199 git_patchset_body($fd, \@difftree, $hash,
5200 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebski157e43b2006-08-24 19:34:36 +02005201 close $fd;
Jakub Narebskieee08902006-08-24 00:15:14 +02005202 print "</div>\n"; # class="page_body"
5203 git_footer_html();
5204
5205 } elsif ($format eq 'plain') {
5206 local $/ = undef;
5207 print <$fd>;
5208 close $fd
5209 or print "Reading git-diff-tree failed\n";
5210 }
5211}
5212
5213sub git_commitdiff_plain {
5214 git_commitdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02005215}
5216
Kay Sievers09bd7892005-08-07 20:21:23 +02005217sub git_history {
Luben Tuikovc6e1d9e2006-07-23 13:26:30 -07005218 if (!defined $hash_base) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005219 $hash_base = git_get_head_hash($project);
Kay Sievers09bd7892005-08-07 20:21:23 +02005220 }
Jakub Narebski8be68352006-09-11 00:36:04 +02005221 if (!defined $page) {
5222 $page = 0;
5223 }
Luben Tuikov63433102006-07-23 13:31:15 -07005224 my $ftype;
Lea Wiemann074afaa2008-06-19 22:03:21 +02005225 my %co = parse_commit($hash_base)
5226 or die_error(404, "Unknown commit object");
Jakub Narebski8be68352006-09-11 00:36:04 +02005227
Jakub Narebski847e01f2006-08-14 02:05:47 +02005228 my $refs = git_get_references();
Jakub Narebski8be68352006-09-11 00:36:04 +02005229 my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
5230
Jakub Narebski5634cf22008-04-13 14:12:15 +02005231 my @commitlist = parse_commits($hash_base, 101, (100 * $page),
Lea Wiemann074afaa2008-06-19 22:03:21 +02005232 $file_name, "--full-history")
5233 or die_error(404, "No such file or directory on given branch");
Jakub Narebski5634cf22008-04-13 14:12:15 +02005234
Luben Tuikov93d5f062006-07-23 13:30:08 -07005235 if (!defined $hash && defined $file_name) {
Jakub Narebski5634cf22008-04-13 14:12:15 +02005236 # some commits could have deleted file in question,
5237 # and not have it in tree, but one of them has to have it
5238 for (my $i = 0; $i <= @commitlist; $i++) {
5239 $hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
5240 last if defined $hash;
5241 }
Luben Tuikov93d5f062006-07-23 13:30:08 -07005242 }
Luben Tuikovcff07712006-07-23 13:28:55 -07005243 if (defined $hash) {
Luben Tuikov63433102006-07-23 13:31:15 -07005244 $ftype = git_get_type($hash);
Luben Tuikovcff07712006-07-23 13:28:55 -07005245 }
Jakub Narebski5634cf22008-04-13 14:12:15 +02005246 if (!defined $ftype) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005247 die_error(500, "Unknown type of object");
Jakub Narebski5634cf22008-04-13 14:12:15 +02005248 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005249
Jakub Narebski8be68352006-09-11 00:36:04 +02005250 my $paging_nav = '';
5251 if ($page > 0) {
5252 $paging_nav .=
5253 $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
5254 file_name=>$file_name)},
5255 "first");
5256 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005257 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski8be68352006-09-11 00:36:04 +02005258 -accesskey => "p", -title => "Alt-p"}, "prev");
5259 } else {
5260 $paging_nav .= "first";
5261 $paging_nav .= " &sdot; prev";
5262 }
Jakub Narebski8be68352006-09-11 00:36:04 +02005263 my $next_link = '';
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005264 if ($#commitlist >= 100) {
Jakub Narebski8be68352006-09-11 00:36:04 +02005265 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005266 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005267 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005268 $paging_nav .= " &sdot; $next_link";
5269 } else {
5270 $paging_nav .= " &sdot; next";
Jakub Narebski8be68352006-09-11 00:36:04 +02005271 }
Jakub Narebski581860e2006-08-14 02:09:08 +02005272
Jakub Narebski8be68352006-09-11 00:36:04 +02005273 git_header_html();
5274 git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
5275 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
5276 git_print_page_path($file_name, $ftype, $hash_base);
5277
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00005278 git_history_body(\@commitlist, 0, 99,
Jakub Narebski8be68352006-09-11 00:36:04 +02005279 $refs, $hash_base, $ftype, $next_link);
5280
Kay Sieversd51e9022005-08-07 20:16:07 +02005281 git_footer_html();
Kay Sievers161332a2005-08-07 19:49:46 +02005282}
Kay Sievers19806692005-08-07 20:26:27 +02005283
5284sub git_search {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005285 gitweb_check_feature('search') or die_error(403, "Search is disabled");
Kay Sievers19806692005-08-07 20:26:27 +02005286 if (!defined $searchtext) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005287 die_error(400, "Text field is empty");
Kay Sievers19806692005-08-07 20:26:27 +02005288 }
5289 if (!defined $hash) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005290 $hash = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02005291 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005292 my %co = parse_commit($hash);
Kay Sievers19806692005-08-07 20:26:27 +02005293 if (!%co) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005294 die_error(404, "Unknown commit object");
Kay Sievers19806692005-08-07 20:26:27 +02005295 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005296 if (!defined $page) {
5297 $page = 0;
5298 }
Jakub Narebski04f7a942006-09-11 00:29:27 +02005299
Petr Baudis88ad7292006-10-24 05:15:46 +02005300 $searchtype ||= 'commit';
5301 if ($searchtype eq 'pickaxe') {
Jakub Narebski04f7a942006-09-11 00:29:27 +02005302 # pickaxe may take all resources of your box and run for several minutes
5303 # with every query - so decide by yourself how public you make this feature
Lea Wiemann074afaa2008-06-19 22:03:21 +02005304 gitweb_check_feature('pickaxe')
5305 or die_error(403, "Pickaxe is disabled");
Kay Sieversc994d622005-08-07 20:27:18 +02005306 }
Petr Baudise7738552007-05-17 04:31:12 +02005307 if ($searchtype eq 'grep') {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005308 gitweb_check_feature('grep')
5309 or die_error(403, "Grep is disabled");
Petr Baudise7738552007-05-17 04:31:12 +02005310 }
Petr Baudis88ad7292006-10-24 05:15:46 +02005311
Kay Sievers19806692005-08-07 20:26:27 +02005312 git_header_html();
Kay Sievers19806692005-08-07 20:26:27 +02005313
Petr Baudis88ad7292006-10-24 05:15:46 +02005314 if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
Robert Fitzsimons8e574fb2006-12-23 03:35:14 +00005315 my $greptype;
5316 if ($searchtype eq 'commit') {
5317 $greptype = "--grep=";
5318 } elsif ($searchtype eq 'author') {
5319 $greptype = "--author=";
5320 } elsif ($searchtype eq 'committer') {
5321 $greptype = "--committer=";
5322 }
Jakub Narebski0270cd02008-02-26 13:22:07 +01005323 $greptype .= $searchtext;
5324 my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
Petr Baudis0e559912008-02-26 13:22:08 +01005325 $greptype, '--regexp-ignore-case',
5326 $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005327
5328 my $paging_nav = '';
5329 if ($page > 0) {
5330 $paging_nav .=
5331 $cgi->a({-href => href(action=>"search", hash=>$hash,
Jakub Narebski0270cd02008-02-26 13:22:07 +01005332 searchtext=>$searchtext,
5333 searchtype=>$searchtype)},
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005334 "first");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005335 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005336 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005337 -accesskey => "p", -title => "Alt-p"}, "prev");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005338 } else {
5339 $paging_nav .= "first";
5340 $paging_nav .= " &sdot; prev";
5341 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005342 my $next_link = '';
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00005343 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005344 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005345 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02005346 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005347 $paging_nav .= " &sdot; $next_link";
5348 } else {
5349 $paging_nav .= " &sdot; next";
5350 }
5351
5352 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005353 }
5354
5355 git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
5356 git_print_header_div('commit', esc_html($co{'title'}), $hash);
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00005357 git_search_grep_body(\@commitlist, 0, 99, $next_link);
Kay Sieversc994d622005-08-07 20:27:18 +02005358 }
5359
Petr Baudis88ad7292006-10-24 05:15:46 +02005360 if ($searchtype eq 'pickaxe') {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005361 git_print_page_nav('','', $hash,$co{'tree'},$hash);
5362 git_print_header_div('commit', esc_html($co{'title'}), $hash);
5363
Jakub Narebski591ebf62007-11-19 14:16:11 +01005364 print "<table class=\"pickaxe search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005365 my $alternate = 1;
Kay Sieversc994d622005-08-07 20:27:18 +02005366 $/ = "\n";
Jakub Narebskic582aba2008-03-05 09:31:55 +01005367 open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
5368 '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
5369 ($search_use_regexp ? '--pickaxe-regex' : ());
Kay Sieversc994d622005-08-07 20:27:18 +02005370 undef %co;
5371 my @files;
5372 while (my $line = <$fd>) {
Jakub Narebskic582aba2008-03-05 09:31:55 +01005373 chomp $line;
5374 next unless $line;
5375
5376 my %set = parse_difftree_raw_line($line);
5377 if (defined $set{'commit'}) {
5378 # finish previous commit
Kay Sieversc994d622005-08-07 20:27:18 +02005379 if (%co) {
Kay Sieversc994d622005-08-07 20:27:18 +02005380 print "</td>\n" .
5381 "<td class=\"link\">" .
Martin Waitz756d2f02006-08-17 00:28:36 +02005382 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005383 " | " .
5384 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
Kay Sieversc994d622005-08-07 20:27:18 +02005385 print "</td>\n" .
5386 "</tr>\n";
5387 }
Jakub Narebskic582aba2008-03-05 09:31:55 +01005388
5389 if ($alternate) {
5390 print "<tr class=\"dark\">\n";
5391 } else {
5392 print "<tr class=\"light\">\n";
5393 }
5394 $alternate ^= 1;
5395 %co = parse_commit($set{'commit'});
5396 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
5397 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
5398 "<td><i>$author</i></td>\n" .
5399 "<td>" .
5400 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
5401 -class => "list subject"},
5402 chop_and_escape_str($co{'title'}, 50) . "<br/>");
5403 } elsif (defined $set{'to_id'}) {
5404 next if ($set{'to_id'} =~ m/^0{40}$/);
5405
5406 print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
5407 hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
5408 -class => "list"},
5409 "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
5410 "<br/>\n";
Kay Sieversc994d622005-08-07 20:27:18 +02005411 }
5412 }
5413 close $fd;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005414
Jakub Narebskic582aba2008-03-05 09:31:55 +01005415 # finish last commit (warning: repetition!)
5416 if (%co) {
5417 print "</td>\n" .
5418 "<td class=\"link\">" .
5419 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
5420 " | " .
5421 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
5422 print "</td>\n" .
5423 "</tr>\n";
5424 }
5425
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005426 print "</table>\n";
Kay Sievers19806692005-08-07 20:26:27 +02005427 }
Petr Baudise7738552007-05-17 04:31:12 +02005428
5429 if ($searchtype eq 'grep') {
5430 git_print_page_nav('','', $hash,$co{'tree'},$hash);
5431 git_print_header_div('commit', esc_html($co{'title'}), $hash);
5432
Jakub Narebski591ebf62007-11-19 14:16:11 +01005433 print "<table class=\"grep_search\">\n";
Petr Baudise7738552007-05-17 04:31:12 +02005434 my $alternate = 1;
5435 my $matches = 0;
5436 $/ = "\n";
Petr Baudis0e559912008-02-26 13:22:08 +01005437 open my $fd, "-|", git_cmd(), 'grep', '-n',
5438 $search_use_regexp ? ('-E', '-i') : '-F',
5439 $searchtext, $co{'tree'};
Petr Baudise7738552007-05-17 04:31:12 +02005440 my $lastfile = '';
5441 while (my $line = <$fd>) {
5442 chomp $line;
5443 my ($file, $lno, $ltext, $binary);
5444 last if ($matches++ > 1000);
5445 if ($line =~ /^Binary file (.+) matches$/) {
5446 $file = $1;
5447 $binary = 1;
5448 } else {
5449 (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
5450 }
5451 if ($file ne $lastfile) {
5452 $lastfile and print "</td></tr>\n";
5453 if ($alternate++) {
5454 print "<tr class=\"dark\">\n";
5455 } else {
5456 print "<tr class=\"light\">\n";
5457 }
5458 print "<td class=\"list\">".
5459 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
5460 file_name=>"$file"),
5461 -class => "list"}, esc_path($file));
5462 print "</td><td>\n";
5463 $lastfile = $file;
5464 }
5465 if ($binary) {
5466 print "<div class=\"binary\">Binary file</div>\n";
5467 } else {
5468 $ltext = untabify($ltext);
Petr Baudis0e559912008-02-26 13:22:08 +01005469 if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
Petr Baudise7738552007-05-17 04:31:12 +02005470 $ltext = esc_html($1, -nbsp=>1);
5471 $ltext .= '<span class="match">';
5472 $ltext .= esc_html($2, -nbsp=>1);
5473 $ltext .= '</span>';
5474 $ltext .= esc_html($3, -nbsp=>1);
5475 } else {
5476 $ltext = esc_html($ltext, -nbsp=>1);
5477 }
5478 print "<div class=\"pre\">" .
5479 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
5480 file_name=>"$file").'#l'.$lno,
5481 -class => "linenr"}, sprintf('%4i', $lno))
5482 . ' ' . $ltext . "</div>\n";
5483 }
5484 }
5485 if ($lastfile) {
5486 print "</td></tr>\n";
5487 if ($matches > 1000) {
5488 print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
5489 }
5490 } else {
5491 print "<div class=\"diff nodifferences\">No matches found</div>\n";
5492 }
5493 close $fd;
5494
5495 print "</table>\n";
5496 }
Kay Sievers19806692005-08-07 20:26:27 +02005497 git_footer_html();
5498}
5499
Petr Baudis88ad7292006-10-24 05:15:46 +02005500sub git_search_help {
5501 git_header_html();
5502 git_print_page_nav('','', $hash,$hash,$hash);
5503 print <<EOT;
Petr Baudis0e559912008-02-26 13:22:08 +01005504<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
5505regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
5506the pattern entered is recognized as the POSIX extended
5507<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
5508insensitive).</p>
Petr Baudis88ad7292006-10-24 05:15:46 +02005509<dl>
5510<dt><b>commit</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005511<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02005512EOT
5513 my ($have_grep) = gitweb_check_feature('grep');
5514 if ($have_grep) {
5515 print <<EOT;
5516<dt><b>grep</b></dt>
5517<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
Petr Baudis0e559912008-02-26 13:22:08 +01005518 a different one) are searched for the given pattern. On large trees, this search can take
5519a while and put some strain on the server, so please use it with some consideration. Note that
5520due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
5521case-sensitive.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02005522EOT
5523 }
5524 print <<EOT;
Petr Baudis88ad7292006-10-24 05:15:46 +02005525<dt><b>author</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005526<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 +02005527<dt><b>committer</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01005528<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 +02005529EOT
5530 my ($have_pickaxe) = gitweb_check_feature('pickaxe');
5531 if ($have_pickaxe) {
5532 print <<EOT;
5533<dt><b>pickaxe</b></dt>
5534<dd>All commits that caused the string to appear or disappear from any file (changes that
5535added, removed or "modified" the string) will be listed. This search can take a while and
Petr Baudis0e559912008-02-26 13:22:08 +01005536takes a lot of strain on the server, so please use it wisely. Note that since you may be
5537interested even in changes just changing the case as well, this search is case sensitive.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02005538EOT
5539 }
5540 print "</dl>\n";
5541 git_footer_html();
5542}
5543
Kay Sievers19806692005-08-07 20:26:27 +02005544sub git_shortlog {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005545 my $head = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02005546 if (!defined $hash) {
5547 $hash = $head;
5548 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02005549 if (!defined $page) {
5550 $page = 0;
5551 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005552 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02005553
Robert Fitzsimonsd2229842007-01-03 20:25:55 +00005554 my @commitlist = parse_commits($hash, 101, (100 * $page));
Kay Sieversea4a6df2005-08-07 20:26:49 +02005555
Lea Wiemann1f684dc2008-05-28 01:25:42 +02005556 my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#commitlist >= 100);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005557 my $next_link = '';
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005558 if ($#commitlist >= 100) {
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005559 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01005560 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005561 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005562 }
5563
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005564 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005565 git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
5566 git_print_header_div('summary', $project);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005567
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00005568 git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005569
Kay Sievers19806692005-08-07 20:26:27 +02005570 git_footer_html();
5571}
Jakub Narebski717b8312006-07-31 21:22:15 +02005572
5573## ......................................................................
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005574## feeds (RSS, Atom; OPML)
Jakub Narebski717b8312006-07-31 21:22:15 +02005575
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005576sub git_feed {
5577 my $format = shift || 'atom';
5578 my ($have_blame) = gitweb_check_feature('blame');
5579
5580 # Atom: http://www.atomenabled.org/developers/syndication/
5581 # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
5582 if ($format ne 'rss' && $format ne 'atom') {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005583 die_error(400, "Unknown web feed format");
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005584 }
5585
5586 # log/feed of current (HEAD) branch, log of given branch, history of file/directory
5587 my $head = $hash || 'HEAD';
Jakub Narebski311e5522008-02-26 13:22:06 +01005588 my @commitlist = parse_commits($head, 150, 0, $file_name);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005589
5590 my %latest_commit;
5591 my %latest_date;
5592 my $content_type = "application/$format+xml";
5593 if (defined $cgi->http('HTTP_ACCEPT') &&
5594 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
5595 # browser (feed reader) prefers text/xml
5596 $content_type = 'text/xml';
5597 }
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00005598 if (defined($commitlist[0])) {
5599 %latest_commit = %{$commitlist[0]};
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01005600 %latest_date = parse_date($latest_commit{'author_epoch'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005601 print $cgi->header(
5602 -type => $content_type,
5603 -charset => 'utf-8',
5604 -last_modified => $latest_date{'rfc2822'});
5605 } else {
5606 print $cgi->header(
5607 -type => $content_type,
5608 -charset => 'utf-8');
5609 }
5610
5611 # Optimization: skip generating the body if client asks only
5612 # for Last-Modified date.
5613 return if ($cgi->request_method() eq 'HEAD');
5614
5615 # header variables
5616 my $title = "$site_name - $project/$action";
5617 my $feed_type = 'log';
5618 if (defined $hash) {
5619 $title .= " - '$hash'";
5620 $feed_type = 'branch log';
5621 if (defined $file_name) {
5622 $title .= " :: $file_name";
5623 $feed_type = 'history';
5624 }
5625 } elsif (defined $file_name) {
5626 $title .= " - $file_name";
5627 $feed_type = 'history';
5628 }
5629 $title .= " $feed_type";
5630 my $descr = git_get_project_description($project);
5631 if (defined $descr) {
5632 $descr = esc_html($descr);
5633 } else {
5634 $descr = "$project " .
5635 ($format eq 'rss' ? 'RSS' : 'Atom') .
5636 " feed";
5637 }
5638 my $owner = git_get_project_owner($project);
5639 $owner = esc_html($owner);
5640
5641 #header
5642 my $alt_url;
5643 if (defined $file_name) {
5644 $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
5645 } elsif (defined $hash) {
5646 $alt_url = href(-full=>1, action=>"log", hash=>$hash);
5647 } else {
5648 $alt_url = href(-full=>1, action=>"summary");
5649 }
5650 print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
5651 if ($format eq 'rss') {
5652 print <<XML;
Jakub Narebski59b9f612006-08-22 23:42:53 +02005653<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
5654<channel>
Jakub Narebski59b9f612006-08-22 23:42:53 +02005655XML
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005656 print "<title>$title</title>\n" .
5657 "<link>$alt_url</link>\n" .
5658 "<description>$descr</description>\n" .
5659 "<language>en</language>\n";
5660 } elsif ($format eq 'atom') {
5661 print <<XML;
5662<feed xmlns="http://www.w3.org/2005/Atom">
5663XML
5664 print "<title>$title</title>\n" .
5665 "<subtitle>$descr</subtitle>\n" .
5666 '<link rel="alternate" type="text/html" href="' .
5667 $alt_url . '" />' . "\n" .
5668 '<link rel="self" type="' . $content_type . '" href="' .
5669 $cgi->self_url() . '" />' . "\n" .
5670 "<id>" . href(-full=>1) . "</id>\n" .
5671 # use project owner for feed author
5672 "<author><name>$owner</name></author>\n";
5673 if (defined $favicon) {
5674 print "<icon>" . esc_url($favicon) . "</icon>\n";
5675 }
5676 if (defined $logo_url) {
5677 # not twice as wide as tall: 72 x 27 pixels
Jakub Narebskie1147262006-12-04 14:09:43 +01005678 print "<logo>" . esc_url($logo) . "</logo>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005679 }
5680 if (! %latest_date) {
5681 # dummy date to keep the feed valid until commits trickle in:
5682 print "<updated>1970-01-01T00:00:00Z</updated>\n";
5683 } else {
5684 print "<updated>$latest_date{'iso-8601'}</updated>\n";
5685 }
5686 }
Jakub Narebski717b8312006-07-31 21:22:15 +02005687
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005688 # contents
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00005689 for (my $i = 0; $i <= $#commitlist; $i++) {
5690 my %co = %{$commitlist[$i]};
5691 my $commit = $co{'id'};
Jakub Narebski717b8312006-07-31 21:22:15 +02005692 # we read 150, we always show 30 and the ones more recent than 48 hours
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01005693 if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02005694 last;
5695 }
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01005696 my %cd = parse_date($co{'author_epoch'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005697
5698 # get list of changed files
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00005699 open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskic906b182007-05-19 02:47:51 +02005700 $co{'parent'} || "--root",
5701 $co{'id'}, "--", (defined $file_name ? $file_name : ())
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02005702 or next;
Jakub Narebski717b8312006-07-31 21:22:15 +02005703 my @difftree = map { chomp; $_ } <$fd>;
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02005704 close $fd
5705 or next;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005706
5707 # print element (entry, item)
Florian La Rochee62a6412008-02-03 12:38:46 +01005708 my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005709 if ($format eq 'rss') {
5710 print "<item>\n" .
5711 "<title>" . esc_html($co{'title'}) . "</title>\n" .
5712 "<author>" . esc_html($co{'author'}) . "</author>\n" .
5713 "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
5714 "<guid isPermaLink=\"true\">$co_url</guid>\n" .
5715 "<link>$co_url</link>\n" .
5716 "<description>" . esc_html($co{'title'}) . "</description>\n" .
5717 "<content:encoded>" .
5718 "<![CDATA[\n";
5719 } elsif ($format eq 'atom') {
5720 print "<entry>\n" .
5721 "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
5722 "<updated>$cd{'iso-8601'}</updated>\n" .
Jakub Narebskiab23c192006-11-25 15:54:33 +01005723 "<author>\n" .
5724 " <name>" . esc_html($co{'author_name'}) . "</name>\n";
5725 if ($co{'author_email'}) {
5726 print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
5727 }
5728 print "</author>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005729 # use committer for contributor
Jakub Narebskiab23c192006-11-25 15:54:33 +01005730 "<contributor>\n" .
5731 " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
5732 if ($co{'committer_email'}) {
5733 print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
5734 }
5735 print "</contributor>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005736 "<published>$cd{'iso-8601'}</published>\n" .
5737 "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
5738 "<id>$co_url</id>\n" .
5739 "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
5740 "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
5741 }
Jakub Narebski717b8312006-07-31 21:22:15 +02005742 my $comment = $co{'comment'};
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005743 print "<pre>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02005744 foreach my $line (@$comment) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005745 $line = esc_html($line);
5746 print "$line\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02005747 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005748 print "</pre><ul>\n";
5749 foreach my $difftree_line (@difftree) {
5750 my %difftree = parse_difftree_raw_line($difftree_line);
5751 next if !$difftree{'from_id'};
5752
5753 my $file = $difftree{'file'} || $difftree{'to_file'};
5754
5755 print "<li>" .
5756 "[" .
5757 $cgi->a({-href => href(-full=>1, action=>"blobdiff",
5758 hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
5759 hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
5760 file_name=>$file, file_parent=>$difftree{'from_file'}),
5761 -title => "diff"}, 'D');
5762 if ($have_blame) {
5763 print $cgi->a({-href => href(-full=>1, action=>"blame",
5764 file_name=>$file, hash_base=>$commit),
5765 -title => "blame"}, 'B');
Jakub Narebski717b8312006-07-31 21:22:15 +02005766 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005767 # if this is not a feed of a file history
5768 if (!defined $file_name || $file_name ne $file) {
5769 print $cgi->a({-href => href(-full=>1, action=>"history",
5770 file_name=>$file, hash=>$commit),
5771 -title => "history"}, 'H');
5772 }
5773 $file = esc_path($file);
5774 print "] ".
5775 "$file</li>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02005776 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005777 if ($format eq 'rss') {
5778 print "</ul>]]>\n" .
5779 "</content:encoded>\n" .
5780 "</item>\n";
5781 } elsif ($format eq 'atom') {
5782 print "</ul>\n</div>\n" .
5783 "</content>\n" .
5784 "</entry>\n";
5785 }
Jakub Narebski717b8312006-07-31 21:22:15 +02005786 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01005787
5788 # end of feed
5789 if ($format eq 'rss') {
5790 print "</channel>\n</rss>\n";
5791 } elsif ($format eq 'atom') {
5792 print "</feed>\n";
5793 }
5794}
5795
5796sub git_rss {
5797 git_feed('rss');
5798}
5799
5800sub git_atom {
5801 git_feed('atom');
Jakub Narebski717b8312006-07-31 21:22:15 +02005802}
5803
5804sub git_opml {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005805 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02005806
5807 print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
Jakub Narebski59b9f612006-08-22 23:42:53 +02005808 print <<XML;
5809<?xml version="1.0" encoding="utf-8"?>
5810<opml version="1.0">
5811<head>
Petr Baudis8be28902006-10-24 05:18:39 +02005812 <title>$site_name OPML Export</title>
Jakub Narebski59b9f612006-08-22 23:42:53 +02005813</head>
5814<body>
5815<outline text="git RSS feeds">
5816XML
Jakub Narebski717b8312006-07-31 21:22:15 +02005817
5818 foreach my $pr (@list) {
5819 my %proj = %$pr;
Jakub Narebski847e01f2006-08-14 02:05:47 +02005820 my $head = git_get_head_hash($proj{'path'});
Jakub Narebski717b8312006-07-31 21:22:15 +02005821 if (!defined $head) {
5822 next;
5823 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005824 $git_dir = "$projectroot/$proj{'path'}";
Jakub Narebski847e01f2006-08-14 02:05:47 +02005825 my %co = parse_commit($head);
Jakub Narebski717b8312006-07-31 21:22:15 +02005826 if (!%co) {
5827 next;
5828 }
5829
5830 my $path = esc_html(chop_str($proj{'path'}, 25, 5));
5831 my $rss = "$my_url?p=$proj{'path'};a=rss";
5832 my $html = "$my_url?p=$proj{'path'};a=summary";
5833 print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
5834 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02005835 print <<XML;
5836</outline>
5837</body>
5838</opml>
5839XML
Jakub Narebski717b8312006-07-31 21:22:15 +02005840}