blob: 9fcbbb2d6c9c6913cccb3186657947457570c398 [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
Ævar Arnfjörð Bjarmasond48b2842010-09-24 20:00:52 +000010use 5.008;
Kay Sievers161332a2005-08-07 19:49:46 +020011use strict;
12use warnings;
Kay Sievers19806692005-08-07 20:26:27 +020013use CGI qw(:standard :escapeHTML -nosticky);
Kay Sievers7403d502005-08-07 20:23:49 +020014use CGI::Util qw(unescape);
Jakub Narebski7a597452010-04-24 16:00:04 +020015use CGI::Carp qw(fatalsToBrowser set_message);
Kay Sievers40c13812005-11-19 17:41:29 +010016use Encode;
Kay Sieversb87d78d2005-08-07 20:21:04 +020017use Fcntl ':mode';
Junio C Hamano7a13b992006-07-31 19:18:34 -070018use File::Find qw();
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +053019use File::Basename qw(basename);
Kay Sievers10bb9032005-11-23 04:26:40 +010020binmode STDOUT, ':utf8';
Kay Sievers161332a2005-08-07 19:49:46 +020021
Jakub Narebskiaa7dd052009-09-01 13:39:16 +020022our $t0;
23if (eval { require Time::HiRes; 1; }) {
24 $t0 = [Time::HiRes::gettimeofday()];
25}
26our $number_of_git_cmds = 0;
27
Jakub Narebskib1f5f642006-12-28 00:00:52 +010028BEGIN {
Jakub Narebski3be8e722007-04-01 22:22:21 +020029 CGI->compile() if $ENV{'MOD_PERL'};
Jakub Narebskib1f5f642006-12-28 00:00:52 +010030}
31
Junio C Hamano06c084d2006-08-02 13:50:20 -070032our $version = "++GIT_VERSION++";
Kay Sievers44ad2972005-08-07 19:59:24 +020033
Jakub Narebskic2394fe2010-05-07 14:54:04 +020034our ($my_url, $my_uri, $base_url, $path_info, $home_link);
35sub evaluate_uri {
36 our $cgi;
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010037
Jakub Narebskic2394fe2010-05-07 14:54:04 +020038 our $my_url = $cgi->url();
39 our $my_uri = $cgi->url(-absolute => 1);
40
41 # Base URL for relative URLs in gitweb ($logo, $favicon, ...),
42 # needed and used only for URLs with nonempty PATH_INFO
43 our $base_url = $my_url;
44
45 # When the script is used as DirectoryIndex, the URL does not contain the name
46 # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
47 # have to do it ourselves. We make $path_info global because it's also used
48 # later on.
49 #
50 # Another issue with the script being the DirectoryIndex is that the resulting
51 # $my_url data is not the full script URL: this is good, because we want
52 # generated links to keep implying the script name if it wasn't explicitly
53 # indicated in the URL we're handling, but it means that $my_url cannot be used
54 # as base URL.
55 # Therefore, if we needed to strip PATH_INFO, then we know that we have
56 # to build the base URL ourselves:
57 our $path_info = $ENV{"PATH_INFO"};
58 if ($path_info) {
59 if ($my_url =~ s,\Q$path_info\E$,, &&
60 $my_uri =~ s,\Q$path_info\E$,, &&
61 defined $ENV{'SCRIPT_NAME'}) {
62 $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
63 }
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +010064 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +020065
66 # target of the home link on top of all pages
67 our $home_link = $my_uri || "/";
Giuseppe Bilottab65910f2008-09-29 15:07:42 +020068}
69
Alp Tokere130dda2006-07-12 23:55:10 +010070# core git executable to use
71# this can just be "git" if your webserver has a sensible PATH
Junio C Hamano06c084d2006-08-02 13:50:20 -070072our $GIT = "++GIT_BINDIR++/git";
Jakub Narebski3f7f27102006-06-21 09:48:04 +020073
Kay Sieversb87d78d2005-08-07 20:21:04 +020074# absolute fs-path which will be prepended to the project path
Dennis Stosberg4a87b432006-06-21 15:07:08 +020075#our $projectroot = "/pub/scm";
Junio C Hamano06c084d2006-08-02 13:50:20 -070076our $projectroot = "++GITWEB_PROJECTROOT++";
Kay Sieversb87d78d2005-08-07 20:21:04 +020077
Luke Luca5e9492007-10-16 20:45:25 -070078# fs traversing limit for getting project list
79# the number is relative to the projectroot
80our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
81
Yasushi SHOJI2de21fa2006-08-15 07:50:49 +090082# string of the home link on top of all pages
83our $home_link_str = "++GITWEB_HOME_LINK_STR++";
84
Alp Toker49da1da2006-07-11 21:10:26 +010085# name of your site or organization to appear in page titles
86# replace this with something more descriptive for clearer bookmarks
Petr Baudis8be28902006-10-24 05:18:39 +020087our $site_name = "++GITWEB_SITENAME++"
88 || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
Alp Toker49da1da2006-07-11 21:10:26 +010089
Alan Chandlerb2d34762006-10-03 13:49:03 +010090# filename of html text to include at top of each page
91our $site_header = "++GITWEB_SITE_HEADER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020092# html text to include at home page
Junio C Hamano06c084d2006-08-02 13:50:20 -070093our $home_text = "++GITWEB_HOMETEXT++";
Alan Chandlerb2d34762006-10-03 13:49:03 +010094# filename of html text to include at bottom of each page
95our $site_footer = "++GITWEB_SITE_FOOTER++";
Kay Sievers8ab1da22005-08-07 20:22:53 +020096
Alan Chandlerb2d34762006-10-03 13:49:03 +010097# URI of stylesheets
98our @stylesheets = ("++GITWEB_CSS++");
Petr Baudis887a6122006-10-26 14:41:25 +020099# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
100our $stylesheet = undef;
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200101# URI of GIT logo (72x27 size)
Junio C Hamano06c084d2006-08-02 13:50:20 -0700102our $logo = "++GITWEB_LOGO++";
Jakub Narebski0b5deba2006-09-04 20:32:13 +0200103# URI of GIT favicon, assumed to be image/png type
104our $favicon = "++GITWEB_FAVICON++";
Jakub Narebski4af819d2009-09-01 13:39:17 +0200105# URI of gitweb.js (JavaScript code for gitweb)
106our $javascript = "++GITWEB_JS++";
Jakub Narebskiaedd9422006-06-17 11:23:56 +0200107
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200108# URI and label (title) of GIT logo link
109#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
110#our $logo_label = "git documentation";
Wincent Colaiuta69fb8282009-07-12 14:31:28 +0200111our $logo_url = "http://git-scm.com/";
Jakub Narebski9a7a62f2006-10-06 12:31:05 +0200112our $logo_label = "git homepage";
Junio C Hamano51a7c662006-09-23 12:36:01 -0700113
Kay Sievers09bd7892005-08-07 20:21:23 +0200114# source of projects list
Junio C Hamano06c084d2006-08-02 13:50:20 -0700115our $projects_list = "++GITWEB_LIST++";
Kay Sieversb87d78d2005-08-07 20:21:04 +0200116
Michael Hendricks55feb122007-07-04 18:36:48 -0600117# the width (in characters) of the projects list "Description" column
118our $projects_list_description_width = 25;
119
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +0200120# default order of projects list
121# valid values are none, project, descr, owner, and age
122our $default_projects_order = "project";
123
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200124# show repository only if this file exists
125# (only effective if this variable evaluates to true)
126our $export_ok = "++GITWEB_EXPORT_OK++";
127
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300128# show repository only if this subroutine returns true
129# when given the path to the project, for example:
130# sub { return -e "$_[0]/git-daemon-export-ok"; }
131our $export_auth_hook = undef;
132
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +0200133# only allow viewing of repositories also shown on the overview page
134our $strict_export = "++GITWEB_STRICT_EXPORT++";
135
Jakub Narebski19a87212006-08-15 23:03:17 +0200136# list of git base URLs used for URL to where fetch project from,
137# i.e. full URL is "$git_base_url/$project"
Jakub Narebskid6b7e0b2006-10-26 12:26:44 +0200138our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
Jakub Narebski19a87212006-08-15 23:03:17 +0200139
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200140# default blob_plain mimetype and default charset for text/plain blob
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200141our $default_blob_plain_mimetype = 'text/plain';
142our $default_text_plain_charset = undef;
Jakub Narebskif5aa79d2006-06-17 13:32:15 +0200143
Petr Baudis2d007372006-06-18 00:01:06 +0200144# file to use for guessing MIME types before trying /etc/mime.types
145# (relative to the current git repository)
Dennis Stosberg4a87b432006-06-21 15:07:08 +0200146our $mimetypes_file = undef;
Petr Baudis2d007372006-06-18 00:01:06 +0200147
Martin Koegler00f429a2007-06-03 17:42:44 +0200148# assume this charset if line contains non-UTF-8 characters;
149# it should be valid encoding (see Encoding::Supported(3pm) for list),
150# for which encoding all byte sequences are valid, for example
151# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
152# could be even 'utf-8' for the old behavior)
153our $fallback_encoding = 'latin1';
154
Jakub Narebski69a9b412007-07-20 02:15:09 +0200155# rename detection options for git-diff and git-diff-tree
156# - default is '-M', with the cost proportional to
157# (number of removed files) * (number of new files).
158# - more costly is '-C' (which implies '-M'), with the cost proportional to
159# (number of changed files + number of removed files) * (number of new files)
160# - even more costly is '-C', '--find-copies-harder' with cost
161# (number of files in the original tree) * (number of new files)
162# - one might want to include '-B' option, e.g. '-B', '-M'
163our @diff_opts = ('-M'); # taken from git_commit
164
Matt McCutchen7e1100e2009-02-07 19:00:09 -0500165# Disables features that would allow repository owners to inject script into
166# the gitweb domain.
167our $prevent_xss = 0;
168
Christopher Wilson7ce896b2010-09-21 00:25:19 -0700169# Path to the highlight executable to use (must be the one from
170# http://www.andre-simon.de due to assumptions about parameters and output).
171# Useful if highlight is not installed on your webserver's PATH.
172# [Default: highlight]
173our $highlight_bin = "++HIGHLIGHT_BIN++";
174
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200175# information about snapshot formats that gitweb is capable of serving
176our %known_snapshot_formats = (
177 # name => {
178 # 'display' => display name,
179 # 'type' => mime type,
180 # 'suffix' => filename suffix,
181 # 'format' => --format for git-archive,
182 # 'compressor' => [compressor command and arguments]
Mark A Rada1bfd3632009-08-06 10:25:39 -0400183 # (array reference, optional)
184 # 'disabled' => boolean (optional)}
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200185 #
186 'tgz' => {
187 'display' => 'tar.gz',
188 'type' => 'application/x-gzip',
189 'suffix' => '.tar.gz',
190 'format' => 'tar',
191 'compressor' => ['gzip']},
192
193 'tbz2' => {
194 'display' => 'tar.bz2',
195 'type' => 'application/x-bzip2',
196 'suffix' => '.tar.bz2',
197 'format' => 'tar',
198 'compressor' => ['bzip2']},
199
Mark A Radacbdefb52009-08-06 10:28:25 -0400200 'txz' => {
201 'display' => 'tar.xz',
202 'type' => 'application/x-xz',
203 'suffix' => '.tar.xz',
204 'format' => 'tar',
205 'compressor' => ['xz'],
206 'disabled' => 1},
207
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200208 'zip' => {
209 'display' => 'zip',
210 'type' => 'application/x-zip',
211 'suffix' => '.zip',
212 'format' => 'zip'},
213);
214
215# Aliases so we understand old gitweb.snapshot values in repository
216# configuration.
217our %known_snapshot_format_aliases = (
218 'gzip' => 'tgz',
219 'bzip2' => 'tbz2',
Mark A Radacbdefb52009-08-06 10:28:25 -0400220 'xz' => 'txz',
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200221
222 # backward compatibility: legacy gitweb config support
223 'x-gzip' => undef, 'gz' => undef,
224 'x-bzip2' => undef, 'bz2' => undef,
225 'x-zip' => undef, '' => undef,
226);
227
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200228# Pixel sizes for icons and avatars. If the default font sizes or lineheights
229# are changed, it may be appropriate to change these values too via
230# $GITWEB_CONFIG.
231our %avatar_size = (
232 'default' => 16,
233 'double' => 32
234);
235
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100236# Used to set the maximum load that we will still respond to gitweb queries.
237# If server load exceed this value then return "503 server busy" error.
238# If gitweb cannot determined server load, it is taken to be 0.
239# Leave it undefined (or set to 'undef') to turn off load checking.
240our $maxload = 300;
241
Alejandro R. Sedeño61bf1262010-07-28 14:40:53 -0400242# configuration for 'highlight' (http://www.andre-simon.de/)
243# match by basename
244our %highlight_basename = (
245 #'Program' => 'py',
246 #'Library' => 'py',
247 'SConstruct' => 'py', # SCons equivalent of Makefile
248 'Makefile' => 'make',
249);
250# match by extension
251our %highlight_ext = (
252 # main extensions, defining name of syntax;
253 # see files in /usr/share/highlight/langDefs/ directory
254 map { $_ => $_ }
255 qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
256 # alternate extensions, see /etc/highlight/filetypes.conf
257 'h' => 'c',
258 map { $_ => 'cpp' } qw(cxx c++ cc),
259 map { $_ => 'php' } qw(php3 php4),
260 map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi'
261 'mak' => 'make',
262 map { $_ => 'xml' } qw(xhtml html htm),
263);
264
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530265# You define site-wide feature defaults here; override them with
266# $GITWEB_CONFIG as necessary.
Jakub Narebski952c65f2006-08-22 16:52:50 +0200267our %feature = (
Jakub Narebski17848fc2006-08-26 19:14:22 +0200268 # feature => {
269 # 'sub' => feature-sub (subroutine),
270 # 'override' => allow-override (boolean),
271 # 'default' => [ default options...] (array reference)}
272 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200273 # if feature is overridable (it means that allow-override has true value),
Jakub Narebski17848fc2006-08-26 19:14:22 +0200274 # then feature-sub will be called with default options as parameters;
275 # return value of feature-sub indicates if to enable specified feature
276 #
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200277 # if there is no 'sub' key (no feature-sub), then feature cannot be
Ralf Wildenhues22e5e582010-08-22 13:12:12 +0200278 # overridden
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200279 #
Giuseppe Bilottaff3c0ff2008-12-02 14:57:28 -0800280 # use gitweb_get_feature(<feature>) to retrieve the <feature> value
281 # (an array) or gitweb_check_feature(<feature>) to check if <feature>
282 # is enabled
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530283
Petr Baudis45a3b122006-10-07 15:17:47 +0200284 # Enable the 'blame' blob view, showing the last commit that modified
285 # each line in the file. This can be very CPU-intensive.
286
287 # To enable system wide have in $GITWEB_CONFIG
288 # $feature{'blame'}{'default'} = [1];
289 # To have project specific config enable override in $GITWEB_CONFIG
290 # $feature{'blame'}{'override'} = 1;
291 # and in project config gitweb.blame = 0|1;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200292 'blame' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800293 'sub' => sub { feature_bool('blame', @_) },
Jakub Narebski952c65f2006-08-22 16:52:50 +0200294 'override' => 0,
295 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530296
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200297 # Enable the 'snapshot' link, providing a compressed archive of any
Petr Baudis45a3b122006-10-07 15:17:47 +0200298 # tree. This can potentially generate high traffic if you have large
299 # project.
300
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200301 # Value is a list of formats defined in %known_snapshot_formats that
302 # you wish to offer.
Petr Baudis45a3b122006-10-07 15:17:47 +0200303 # To disable system wide have in $GITWEB_CONFIG
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200304 # $feature{'snapshot'}{'default'} = [];
Petr Baudis45a3b122006-10-07 15:17:47 +0200305 # To have project specific config enable override in $GITWEB_CONFIG
Uwe Zeisbergerbbee1d92006-12-08 12:44:31 +0100306 # $feature{'snapshot'}{'override'} = 1;
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200307 # and in project config, a comma-separated list of formats or "none"
308 # to disable. Example: gitweb.snapshot = tbz2,zip;
Jakub Narebski952c65f2006-08-22 16:52:50 +0200309 'snapshot' => {
310 'sub' => \&feature_snapshot,
311 'override' => 0,
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200312 'default' => ['tgz']},
Jakub Narebski04f7a942006-09-11 00:29:27 +0200313
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000314 # Enable text search, which will list the commits which match author,
315 # committer or commit text to a given string. Enabled by default.
Jakub Narebskib4b20b22007-05-15 01:55:44 +0200316 # Project specific override is not supported.
Robert Fitzsimons6be93512006-12-23 03:35:16 +0000317 'search' => {
318 'override' => 0,
319 'default' => [1]},
320
Petr Baudise7738552007-05-17 04:31:12 +0200321 # Enable grep search, which will list the files in currently selected
322 # tree containing the given string. Enabled by default. This can be
323 # potentially CPU-intensive, of course.
324
325 # To enable system wide have in $GITWEB_CONFIG
326 # $feature{'grep'}{'default'} = [1];
327 # To have project specific config enable override in $GITWEB_CONFIG
328 # $feature{'grep'}{'override'} = 1;
329 # and in project config gitweb.grep = 0|1;
330 'grep' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800331 'sub' => sub { feature_bool('grep', @_) },
Petr Baudise7738552007-05-17 04:31:12 +0200332 'override' => 0,
333 'default' => [1]},
334
Petr Baudis45a3b122006-10-07 15:17:47 +0200335 # Enable the pickaxe search, which will list the commits that modified
336 # a given string in a file. This can be practical and quite faster
337 # alternative to 'blame', but still potentially CPU-intensive.
338
339 # To enable system wide have in $GITWEB_CONFIG
340 # $feature{'pickaxe'}{'default'} = [1];
341 # To have project specific config enable override in $GITWEB_CONFIG
342 # $feature{'pickaxe'}{'override'} = 1;
343 # and in project config gitweb.pickaxe = 0|1;
Jakub Narebski04f7a942006-09-11 00:29:27 +0200344 'pickaxe' => {
Matt Kraaicdad8172008-12-15 22:16:19 -0800345 'sub' => sub { feature_bool('pickaxe', @_) },
Jakub Narebski04f7a942006-09-11 00:29:27 +0200346 'override' => 0,
347 'default' => [1]},
Martin Waitz9e756902006-10-01 23:57:48 +0200348
Jakub Narebskie4b48ea2009-09-07 14:40:00 +0200349 # Enable showing size of blobs in a 'tree' view, in a separate
350 # column, similar to what 'ls -l' does. This cost a bit of IO.
351
352 # To disable system wide have in $GITWEB_CONFIG
353 # $feature{'show-sizes'}{'default'} = [0];
354 # To have project specific config enable override in $GITWEB_CONFIG
355 # $feature{'show-sizes'}{'override'} = 1;
356 # and in project config gitweb.showsizes = 0|1;
357 'show-sizes' => {
358 'sub' => sub { feature_bool('showsizes', @_) },
359 'override' => 0,
360 'default' => [1]},
361
Petr Baudis45a3b122006-10-07 15:17:47 +0200362 # Make gitweb use an alternative format of the URLs which can be
363 # more readable and natural-looking: project name is embedded
364 # directly in the path and the query string contains other
365 # auxiliary information. All gitweb installations recognize
366 # URL in either format; this configures in which formats gitweb
367 # generates links.
368
369 # To enable system wide have in $GITWEB_CONFIG
370 # $feature{'pathinfo'}{'default'} = [1];
371 # Project specific override is not supported.
372
373 # Note that you will need to change the default location of CSS,
374 # favicon, logo and possibly other files to an absolute URL. Also,
375 # if gitweb.cgi serves as your indexfile, you will need to force
376 # $my_uri to contain the script name in your $GITWEB_CONFIG.
Martin Waitz9e756902006-10-01 23:57:48 +0200377 'pathinfo' => {
378 'override' => 0,
379 'default' => [0]},
Petr Baudise30496d2006-10-24 05:33:17 +0200380
381 # Make gitweb consider projects in project root subdirectories
382 # to be forks of existing projects. Given project $projname.git,
383 # projects matching $projname/*.git will not be shown in the main
384 # projects list, instead a '+' mark will be added to $projname
385 # there and a 'forks' view will be enabled for the project, listing
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +0200386 # all the forks. If project list is taken from a file, forks have
387 # to be listed after the main project.
Petr Baudise30496d2006-10-24 05:33:17 +0200388
389 # To enable system wide have in $GITWEB_CONFIG
390 # $feature{'forks'}{'default'} = [1];
391 # Project specific override is not supported.
392 'forks' => {
393 'override' => 0,
394 'default' => [0]},
Petr Baudisd627f682008-10-02 16:36:52 +0200395
396 # Insert custom links to the action bar of all project pages.
397 # This enables you mainly to link to third-party scripts integrating
398 # into gitweb; e.g. git-browser for graphical history representation
399 # or custom web-based repository administration interface.
400
401 # The 'default' value consists of a list of triplets in the form
402 # (label, link, position) where position is the label after which
Jakub Narebski2b11e052008-10-12 00:02:32 +0200403 # to insert the link and link is a format string where %n expands
Petr Baudisd627f682008-10-02 16:36:52 +0200404 # to the project name, %f to the project path within the filesystem,
405 # %h to the current hash (h gitweb parameter) and %b to the current
Jakub Narebski2b11e052008-10-12 00:02:32 +0200406 # hash base (hb gitweb parameter); %% expands to %.
Petr Baudisd627f682008-10-02 16:36:52 +0200407
408 # To enable system wide have in $GITWEB_CONFIG e.g.
409 # $feature{'actions'}{'default'} = [('graphiclog',
410 # '/git-browser/by-commit.html?r=%n', 'summary')];
411 # Project specific override is not supported.
412 'actions' => {
413 'override' => 0,
414 'default' => []},
Shawn O. Pearce3e3d4ee2008-10-03 07:41:25 -0700415
Petr Baudisaed93de2008-10-02 17:13:02 +0200416 # Allow gitweb scan project content tags described in ctags/
417 # of project repository, and display the popular Web 2.0-ish
418 # "tag cloud" near the project list. Note that this is something
419 # COMPLETELY different from the normal Git tags.
420
421 # gitweb by itself can show existing tags, but it does not handle
422 # tagging itself; you need an external application for that.
423 # For an example script, check Girocco's cgi/tagproj.cgi.
424 # You may want to install the HTML::TagCloud Perl module to get
425 # a pretty tag cloud instead of just a list of tags.
426
427 # To enable system wide have in $GITWEB_CONFIG
428 # $feature{'ctags'}{'default'} = ['path_to_tag_script'];
429 # Project specific override is not supported.
430 'ctags' => {
431 'override' => 0,
432 'default' => [0]},
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100433
434 # The maximum number of patches in a patchset generated in patch
435 # view. Set this to 0 or undef to disable patch view, or to a
436 # negative number to remove any limit.
437
438 # To disable system wide have in $GITWEB_CONFIG
439 # $feature{'patches'}{'default'} = [0];
440 # To have project specific config enable override in $GITWEB_CONFIG
441 # $feature{'patches'}{'override'} = 1;
442 # and in project config gitweb.patches = 0|n;
443 # where n is the maximum number of patches allowed in a patchset.
444 'patches' => {
445 'sub' => \&feature_patches,
446 'override' => 0,
447 'default' => [16]},
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200448
449 # Avatar support. When this feature is enabled, views such as
450 # shortlog or commit will display an avatar associated with
451 # the email of the committer(s) and/or author(s).
452
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200453 # Currently available providers are gravatar and picon.
454 # If an unknown provider is specified, the feature is disabled.
455
456 # Gravatar depends on Digest::MD5.
457 # Picon currently relies on the indiana.edu database.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200458
459 # To enable system wide have in $GITWEB_CONFIG
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200460 # $feature{'avatar'}{'default'} = ['<provider>'];
461 # where <provider> is either gravatar or picon.
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200462 # To have project specific config enable override in $GITWEB_CONFIG
463 # $feature{'avatar'}{'override'} = 1;
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +0200464 # and in project config gitweb.avatar = <provider>;
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200465 'avatar' => {
466 'sub' => \&feature_avatar,
467 'override' => 0,
468 'default' => ['']},
Jakub Narebskiaa7dd052009-09-01 13:39:16 +0200469
470 # Enable displaying how much time and how many git commands
471 # it took to generate and display page. Disabled by default.
472 # Project specific override is not supported.
473 'timed' => {
474 'override' => 0,
475 'default' => [0]},
Jakub Narebskie627e502009-11-26 21:12:15 +0100476
477 # Enable turning some links into links to actions which require
478 # JavaScript to run (like 'blame_incremental'). Not enabled by
479 # default. Project specific override is currently not supported.
480 'javascript-actions' => {
481 'override' => 0,
482 'default' => [0]},
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200483
484 # Syntax highlighting support. This is based on Daniel Svensson's
485 # and Sham Chukoury's work in gitweb-xmms2.git.
Jakub Narebski592ea412010-04-27 21:34:45 +0200486 # It requires the 'highlight' program present in $PATH,
487 # and therefore is disabled by default.
Johannes Schindelinb331fe52010-04-27 21:34:44 +0200488
489 # To enable system wide have in $GITWEB_CONFIG
490 # $feature{'highlight'}{'default'} = [1];
491
492 'highlight' => {
493 'sub' => sub { feature_bool('highlight', @_) },
494 'override' => 0,
495 'default' => [0]},
Giuseppe Bilotta60efa242010-11-11 13:26:09 +0100496
497 # Enable displaying of remote heads in the heads list
498
499 # To enable system wide have in $GITWEB_CONFIG
500 # $feature{'remote_heads'}{'default'} = [1];
501 # To have project specific config enable override in $GITWEB_CONFIG
502 # $feature{'remote_heads'}{'override'} = 1;
503 # and in project config gitweb.remote_heads = 0|1;
504 'remote_heads' => {
505 'sub' => sub { feature_bool('remote_heads', @_) },
506 'override' => 0,
507 'default' => [0]},
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530508);
509
Junio C Hamanoa7c5a282008-11-29 13:02:08 -0800510sub gitweb_get_feature {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530511 my ($name) = @_;
Jakub Narebskidd1ad5f2006-09-26 01:56:17 +0200512 return unless exists $feature{$name};
Jakub Narebski952c65f2006-08-22 16:52:50 +0200513 my ($sub, $override, @defaults) = (
514 $feature{$name}{'sub'},
515 $feature{$name}{'override'},
516 @{$feature{$name}{'default'}});
Jakub Narebski9be36142010-03-01 22:51:34 +0100517 # project specific override is possible only if we have project
518 our $git_dir; # global variable, declared later
519 if (!$override || !defined $git_dir) {
520 return @defaults;
521 }
Martin Waitza9455912006-10-03 20:07:43 +0200522 if (!defined $sub) {
Nanako Shiraishi93197892009-08-28 12:18:49 +0900523 warn "feature $name is not overridable";
Martin Waitza9455912006-10-03 20:07:43 +0200524 return @defaults;
525 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530526 return $sub->(@defaults);
527}
528
Giuseppe Bilotta25b27902008-11-29 13:07:29 -0800529# A wrapper to check if a given feature is enabled.
530# With this, you can say
531#
532# my $bool_feat = gitweb_check_feature('bool_feat');
533# gitweb_check_feature('bool_feat') or somecode;
534#
535# instead of
536#
537# my ($bool_feat) = gitweb_get_feature('bool_feat');
538# (gitweb_get_feature('bool_feat'))[0] or somecode;
539#
540sub gitweb_check_feature {
541 return (gitweb_get_feature(@_))[0];
542}
543
544
Matt Kraaicdad8172008-12-15 22:16:19 -0800545sub feature_bool {
546 my $key = shift;
547 my ($val) = git_get_project_config($key, '--bool');
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530548
Marcel M. Carydf5d10a2009-02-18 14:09:41 +0100549 if (!defined $val) {
550 return ($_[0]);
551 } elsif ($val eq 'true') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800552 return (1);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530553 } elsif ($val eq 'false') {
Matt Kraaicdad8172008-12-15 22:16:19 -0800554 return (0);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530555 }
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530556}
557
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530558sub feature_snapshot {
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200559 my (@fmts) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530560
561 my ($val) = git_get_project_config('snapshot');
562
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200563 if ($val) {
564 @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +0530565 }
566
Matt McCutchena3c8ab32007-07-22 01:30:27 +0200567 return @fmts;
Luben Tuikovde9272f2006-09-28 16:49:21 -0700568}
569
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100570sub feature_patches {
571 my @val = (git_get_project_config('patches', '--int'));
572
573 if (@val) {
574 return @val;
575 }
576
577 return ($_[0]);
578}
579
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +0200580sub feature_avatar {
581 my @val = (git_get_project_config('avatar'));
582
583 return @val ? @val : @_;
584}
585
Junio C Hamano2172ce42006-10-03 02:30:47 -0700586# checking HEAD file with -e is fragile if the repository was
587# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
588# and then pruned.
589sub check_head_link {
590 my ($dir) = @_;
591 my $headfile = "$dir/HEAD";
592 return ((-e $headfile) ||
593 (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
594}
595
596sub check_export_ok {
597 my ($dir) = @_;
598 return (check_head_link($dir) &&
Alexander Gavrilovdd7f5f12008-11-06 01:36:23 +0300599 (!$export_ok || -e "$dir/$export_ok") &&
600 (!$export_auth_hook || $export_auth_hook->($dir)));
Junio C Hamano2172ce42006-10-03 02:30:47 -0700601}
602
Jakub Narebskia7817852007-07-22 23:41:20 +0200603# process alternate names for backward compatibility
604# filter out unsupported (unknown) snapshot formats
605sub filter_snapshot_fmts {
606 my @fmts = @_;
607
608 @fmts = map {
609 exists $known_snapshot_format_aliases{$_} ?
610 $known_snapshot_format_aliases{$_} : $_} @fmts;
Jakub Narebski68cedb12009-05-10 02:40:37 +0200611 @fmts = grep {
Mark A Rada1bfd3632009-08-06 10:25:39 -0400612 exists $known_snapshot_formats{$_} &&
613 !$known_snapshot_formats{$_}{'disabled'}} @fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +0200614}
615
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200616our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
617sub evaluate_gitweb_config {
618 our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
619 our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
620 # die if there are errors parsing config file
621 if (-e $GITWEB_CONFIG) {
622 do $GITWEB_CONFIG;
623 die $@ if $@;
624 } elsif (-e $GITWEB_CONFIG_SYSTEM) {
625 do $GITWEB_CONFIG_SYSTEM;
626 die $@ if $@;
627 }
Gerrit Pape17a8b252008-03-26 18:11:19 +0000628}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400629
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100630# Get loadavg of system, to compare against $maxload.
631# Currently it requires '/proc/loadavg' present to get loadavg;
632# if it is not present it returns 0, which means no load checking.
633sub get_loadavg {
634 if( -e '/proc/loadavg' ){
635 open my $fd, '<', '/proc/loadavg'
636 or return 0;
637 my @load = split(/\s+/, scalar <$fd>);
638 close $fd;
639
640 # The first three columns measure CPU and IO utilization of the last one,
641 # five, and 10 minute periods. The fourth column shows the number of
642 # currently running processes and the total number of processes in the m/n
643 # format. The last column displays the last process ID used.
644 return $load[0] || 0;
645 }
646 # additional checks for load average should go here for things that don't export
647 # /proc/loadavg
648
649 return 0;
650}
651
Jeff Kingc8d138a2006-08-02 15:23:34 -0400652# version of the core git binary
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200653our $git_version;
654sub evaluate_git_version {
655 our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
656 $number_of_git_cmds++;
657}
Jeff Kingc8d138a2006-08-02 15:23:34 -0400658
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200659sub check_loadavg {
660 if (defined $maxload && get_loadavg() > $maxload) {
661 die_error(503, "The load average on the server is too high");
662 }
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +0100663}
664
Jakub Narebski154b4d72006-08-05 12:55:20 +0200665# ======================================================================
Kay Sievers09bd7892005-08-07 20:21:23 +0200666# input validation and dispatch
Kay Sieversb87d78d2005-08-07 20:21:04 +0200667
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200668# input parameters can be collected from a variety of sources (presently, CGI
669# and PATH_INFO), so we define an %input_params hash that collects them all
670# together during validation: this allows subsequent uses (e.g. href()) to be
671# agnostic of the parameter origin
Kay Sievers6191f8e2005-08-07 20:19:56 +0200672
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300673our %input_params = ();
Martin Waitz5c95fab2006-08-17 00:28:38 +0200674
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200675# input parameters are stored with the long parameter name as key. This will
676# also be used in the href subroutine to convert parameters to their CGI
677# equivalent, and since the href() usage is the most frequent one, we store
678# the name -> CGI key mapping here, instead of the reverse.
679#
680# XXX: Warning: If you touch this, check the search form for updating,
681# too.
Jakub Narebski24d06932006-09-26 01:57:02 +0200682
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300683our @cgi_param_mapping = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200684 project => "p",
685 action => "a",
686 file_name => "f",
687 file_parent => "fp",
688 hash => "h",
689 hash_parent => "hp",
690 hash_base => "hb",
691 hash_parent_base => "hpb",
692 page => "pg",
693 order => "o",
694 searchtext => "s",
695 searchtype => "st",
696 snapshot_format => "sf",
697 extra_options => "opt",
698 search_use_regexp => "sr",
Jakub Narebskic4ccf612009-09-01 13:39:19 +0200699 # this must be last entry (for manipulation from JavaScript)
700 javascript => "js"
Miklos Vajna868bc062007-07-12 20:39:27 +0200701);
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300702our %cgi_param_mapping = @cgi_param_mapping;
Miklos Vajna868bc062007-07-12 20:39:27 +0200703
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200704# we will also need to know the possible actions, for validation
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300705our %actions = (
Rafael Garcia-Suarez3a5b9192008-06-06 09:53:32 +0200706 "blame" => \&git_blame,
Jakub Narebski4af819d2009-09-01 13:39:17 +0200707 "blame_incremental" => \&git_blame_incremental,
708 "blame_data" => \&git_blame_data,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200709 "blobdiff" => \&git_blobdiff,
710 "blobdiff_plain" => \&git_blobdiff_plain,
711 "blob" => \&git_blob,
712 "blob_plain" => \&git_blob_plain,
713 "commitdiff" => \&git_commitdiff,
714 "commitdiff_plain" => \&git_commitdiff_plain,
715 "commit" => \&git_commit,
Petr Baudise30496d2006-10-24 05:33:17 +0200716 "forks" => \&git_forks,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200717 "heads" => \&git_heads,
718 "history" => \&git_history,
719 "log" => \&git_log,
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +0100720 "patch" => \&git_patch,
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +0100721 "patches" => \&git_patches,
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +0100722 "remotes" => \&git_remotes,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200723 "rss" => \&git_rss,
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +0100724 "atom" => \&git_atom,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200725 "search" => \&git_search,
Petr Baudis88ad7292006-10-24 05:15:46 +0200726 "search_help" => \&git_search_help,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200727 "shortlog" => \&git_shortlog,
728 "summary" => \&git_summary,
729 "tag" => \&git_tag,
730 "tags" => \&git_tags,
731 "tree" => \&git_tree,
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +0530732 "snapshot" => \&git_snapshot,
Jakub Narebskica946012006-12-10 13:25:47 +0100733 "object" => \&git_object,
Jakub Narebski77a153f2006-08-22 16:59:20 +0200734 # those below don't need $project
735 "opml" => \&git_opml,
736 "project_list" => \&git_project_list,
Jakub Narebskifc2b2be2006-09-15 04:56:03 +0200737 "project_index" => \&git_project_index,
Matthias Lederhofer8e85cdc2006-07-31 23:46:25 +0200738);
739
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200740# finally, we have the hash of allowed extra_options for the commands that
741# allow them
Alexander Gavrilovdde80d92008-11-06 01:10:07 +0300742our %allowed_options = (
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200743 "--no-merges" => [ qw(rss atom log shortlog history) ],
744);
745
746# fill %input_params with the CGI parameters. All values except for 'opt'
747# should be single values, but opt can be an array. We should probably
748# build an array of parameters that can be multi-valued, but since for the time
749# being it's only this one, we just single it out
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200750sub evaluate_query_params {
751 our $cgi;
752
753 while (my ($name, $symbol) = each %cgi_param_mapping) {
754 if ($symbol eq 'opt') {
755 $input_params{$name} = [ $cgi->param($symbol) ];
756 } else {
757 $input_params{$name} = $cgi->param($symbol);
758 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200759 }
760}
761
762# now read PATH_INFO and update the parameter list for missing parameters
763sub evaluate_path_info {
764 return if defined $input_params{'project'};
765 return if !$path_info;
766 $path_info =~ s,^/+,,;
767 return if !$path_info;
768
769 # find which part of PATH_INFO is project
770 my $project = $path_info;
771 $project =~ s,/+$,,;
772 while ($project && !check_head_link("$projectroot/$project")) {
773 $project =~ s,/*[^/]*$,,;
774 }
775 return unless $project;
776 $input_params{'project'} = $project;
777
778 # do not change any parameters if an action is given using the query string
779 return if $input_params{'action'};
780 $path_info =~ s,^\Q$project\E/*,,;
781
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200782 # next, check if we have an action
783 my $action = $path_info;
784 $action =~ s,/.*$,,;
785 if (exists $actions{$action}) {
786 $path_info =~ s,^$action/*,,;
787 $input_params{'action'} = $action;
788 }
789
790 # list of actions that want hash_base instead of hash, but can have no
791 # pathname (f) parameter
792 my @wants_base = (
793 'tree',
794 'history',
795 );
796
Jakub Narebski7e00dc52010-10-13 13:33:48 +0200797 # we want to catch, among others
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200798 # [$hash_parent_base[:$file_parent]..]$hash_parent[:$file_name]
799 my ($parentrefname, $parentpathname, $refname, $pathname) =
Jakub Narebski7e00dc52010-10-13 13:33:48 +0200800 ($path_info =~ /^(?:(.+?)(?::(.+))?\.\.)?([^:]+?)?(?::(.+))?$/);
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200801
802 # first, analyze the 'current' part
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200803 if (defined $pathname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200804 # we got "branch:filename" or "branch:dir/"
805 # we could use git_get_type(branch:pathname), but:
806 # - it needs $git_dir
807 # - it does a git() call
808 # - the convention of terminating directories with a slash
809 # makes it superfluous
810 # - embedding the action in the PATH_INFO would make it even
811 # more superfluous
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200812 $pathname =~ s,^/+,,;
813 if (!$pathname || substr($pathname, -1) eq "/") {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200814 $input_params{'action'} ||= "tree";
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200815 $pathname =~ s,/$,,;
816 } else {
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200817 # the default action depends on whether we had parent info
818 # or not
819 if ($parentrefname) {
820 $input_params{'action'} ||= "blobdiff_plain";
821 } else {
822 $input_params{'action'} ||= "blob_plain";
823 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200824 }
825 $input_params{'hash_base'} ||= $refname;
826 $input_params{'file_name'} ||= $pathname;
827 } elsif (defined $refname) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200828 # we got "branch". In this case we have to choose if we have to
829 # set hash or hash_base.
830 #
831 # Most of the actions without a pathname only want hash to be
832 # set, except for the ones specified in @wants_base that want
833 # hash_base instead. It should also be noted that hand-crafted
834 # links having 'history' as an action and no pathname or hash
835 # set will fail, but that happens regardless of PATH_INFO.
Jakub Narebskid0af3732010-10-13 13:35:20 +0200836 if (defined $parentrefname) {
837 # if there is parent let the default be 'shortlog' action
838 # (for http://git.example.com/repo.git/A..B links); if there
839 # is no parent, dispatch will detect type of object and set
840 # action appropriately if required (if action is not set)
841 $input_params{'action'} ||= "shortlog";
842 }
843 if ($input_params{'action'} &&
844 grep { $_ eq $input_params{'action'} } @wants_base) {
Giuseppe Bilottad8c28822008-10-21 21:34:50 +0200845 $input_params{'hash_base'} ||= $refname;
846 } else {
847 $input_params{'hash'} ||= $refname;
848 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200849 }
Giuseppe Bilottab0be3832008-10-21 21:34:53 +0200850
851 # next, handle the 'parent' part, if present
852 if (defined $parentrefname) {
853 # a missing pathspec defaults to the 'current' filename, allowing e.g.
854 # someproject/blobdiff/oldrev..newrev:/filename
855 if ($parentpathname) {
856 $parentpathname =~ s,^/+,,;
857 $parentpathname =~ s,/$,,;
858 $input_params{'file_parent'} ||= $parentpathname;
859 } else {
860 $input_params{'file_parent'} ||= $input_params{'file_name'};
861 }
862 # we assume that hash_parent_base is wanted if a path was specified,
863 # or if the action wants hash_base instead of hash
864 if (defined $input_params{'file_parent'} ||
865 grep { $_ eq $input_params{'action'} } @wants_base) {
866 $input_params{'hash_parent_base'} ||= $parentrefname;
867 } else {
868 $input_params{'hash_parent'} ||= $parentrefname;
869 }
870 }
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100871
872 # for the snapshot action, we allow URLs in the form
873 # $project/snapshot/$hash.ext
874 # where .ext determines the snapshot and gets removed from the
875 # passed $refname to provide the $hash.
876 #
877 # To be able to tell that $refname includes the format extension, we
878 # require the following two conditions to be satisfied:
879 # - the hash input parameter MUST have been set from the $refname part
880 # of the URL (i.e. they must be equal)
881 # - the snapshot format MUST NOT have been defined already (e.g. from
882 # CGI parameter sf)
883 # It's also useless to try any matching unless $refname has a dot,
884 # so we check for that too
885 if (defined $input_params{'action'} &&
886 $input_params{'action'} eq 'snapshot' &&
887 defined $refname && index($refname, '.') != -1 &&
888 $refname eq $input_params{'hash'} &&
889 !defined $input_params{'snapshot_format'}) {
890 # We loop over the known snapshot formats, checking for
891 # extensions. Allowed extensions are both the defined suffix
892 # (which includes the initial dot already) and the snapshot
893 # format key itself, with a prepended dot
Holger Weißccb4b532009-03-31 18:16:36 +0200894 while (my ($fmt, $opt) = each %known_snapshot_formats) {
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100895 my $hash = $refname;
Jakub Narebski095e9142009-05-11 19:42:47 +0200896 unless ($hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//) {
897 next;
898 }
899 my $sfx = $1;
Giuseppe Bilotta1ec2fb52008-11-02 10:21:38 +0100900 # a valid suffix was found, so set the snapshot format
901 # and reset the hash parameter
902 $input_params{'snapshot_format'} = $fmt;
903 $input_params{'hash'} = $hash;
904 # we also set the format suffix to the one requested
905 # in the URL: this way a request for e.g. .tgz returns
906 # a .tgz instead of a .tar.gz
907 $known_snapshot_formats{$fmt}{'suffix'} = $sfx;
908 last;
909 }
910 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200911}
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200912
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200913our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
914 $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
915 $searchtext, $search_regexp);
916sub evaluate_and_validate_params {
917 our $action = $input_params{'action'};
918 if (defined $action) {
919 if (!validate_action($action)) {
920 die_error(400, "Invalid action parameter");
921 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200922 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200923
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200924 # parameters which are pathnames
925 our $project = $input_params{'project'};
926 if (defined $project) {
927 if (!validate_project($project)) {
928 undef $project;
929 die_error(404, "No such project");
930 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200931 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200932
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200933 our $file_name = $input_params{'file_name'};
934 if (defined $file_name) {
935 if (!validate_pathname($file_name)) {
936 die_error(400, "Invalid file parameter");
937 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200938 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200939
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200940 our $file_parent = $input_params{'file_parent'};
941 if (defined $file_parent) {
942 if (!validate_pathname($file_parent)) {
943 die_error(400, "Invalid file parent parameter");
944 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200945 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200946
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200947 # parameters which are refnames
948 our $hash = $input_params{'hash'};
949 if (defined $hash) {
950 if (!validate_refname($hash)) {
951 die_error(400, "Invalid hash parameter");
952 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200953 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200954
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200955 our $hash_parent = $input_params{'hash_parent'};
956 if (defined $hash_parent) {
957 if (!validate_refname($hash_parent)) {
958 die_error(400, "Invalid hash parent parameter");
959 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200960 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200961
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200962 our $hash_base = $input_params{'hash_base'};
963 if (defined $hash_base) {
964 if (!validate_refname($hash_base)) {
965 die_error(400, "Invalid hash base parameter");
966 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200967 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200968
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200969 our @extra_options = @{$input_params{'extra_options'}};
970 # @extra_options is always defined, since it can only be (currently) set from
971 # CGI, and $cgi->param() returns the empty array in array context if the param
972 # is not set
973 foreach my $opt (@extra_options) {
974 if (not exists $allowed_options{$opt}) {
975 die_error(400, "Invalid option parameter");
976 }
977 if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
978 die_error(400, "Invalid option parameter for this action");
979 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200980 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200981
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200982 our $hash_parent_base = $input_params{'hash_parent_base'};
983 if (defined $hash_parent_base) {
984 if (!validate_refname($hash_parent_base)) {
985 die_error(400, "Invalid hash parent base parameter");
986 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200987 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200988
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200989 # other parameters
990 our $page = $input_params{'page'};
991 if (defined $page) {
992 if ($page =~ m/[^0-9]/) {
993 die_error(400, "Invalid page parameter");
994 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200995 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +0200996
Jakub Narebskic2394fe2010-05-07 14:54:04 +0200997 our $searchtype = $input_params{'searchtype'};
998 if (defined $searchtype) {
999 if ($searchtype =~ m/[^a-z]/) {
1000 die_error(400, "Invalid searchtype parameter");
1001 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001002 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001003
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001004 our $search_use_regexp = $input_params{'search_use_regexp'};
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001005
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001006 our $searchtext = $input_params{'searchtext'};
1007 our $search_regexp;
1008 if (defined $searchtext) {
1009 if (length($searchtext) < 2) {
1010 die_error(403, "At least two characters are required for search parameter");
1011 }
1012 $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001013 }
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001014}
1015
1016# path to the current git repository
1017our $git_dir;
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001018sub evaluate_git_dir {
1019 our $git_dir = "$projectroot/$project" if $project;
1020}
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001021
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001022our (@snapshot_fmts, $git_avatar);
1023sub configure_gitweb_features {
1024 # list of supported snapshot formats
1025 our @snapshot_fmts = gitweb_get_feature('snapshot');
1026 @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01001027
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001028 # check that the avatar feature is set to a known provider name,
1029 # and for each provider check if the dependencies are satisfied.
1030 # if the provider name is invalid or the dependencies are not met,
1031 # reset $git_avatar to the empty string.
1032 our ($git_avatar) = gitweb_get_feature('avatar');
1033 if ($git_avatar eq 'gravatar') {
1034 $git_avatar = '' unless (eval { require Digest::MD5; 1; });
1035 } elsif ($git_avatar eq 'picon') {
1036 # no dependencies
1037 } else {
1038 $git_avatar = '';
1039 }
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001040}
1041
Jakub Narebski7a597452010-04-24 16:00:04 +02001042# custom error handler: 'die <message>' is Internal Server Error
1043sub handle_errors_html {
1044 my $msg = shift; # it is already HTML escaped
1045
1046 # to avoid infinite loop where error occurs in die_error,
1047 # change handler to default handler, disabling handle_errors_html
1048 set_message("Error occured when inside die_error:\n$msg");
1049
1050 # you cannot jump out of die_error when called as error handler;
1051 # the subroutine set via CGI::Carp::set_message is called _after_
1052 # HTTP headers are already written, so it cannot write them itself
1053 die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
1054}
1055set_message(\&handle_errors_html);
1056
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001057# dispatch
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001058sub dispatch {
1059 if (!defined $action) {
1060 if (defined $hash) {
1061 $action = git_get_type($hash);
1062 } elsif (defined $hash_base && defined $file_name) {
1063 $action = git_get_type("$hash_base:$file_name");
1064 } elsif (defined $project) {
1065 $action = 'summary';
1066 } else {
1067 $action = 'project_list';
1068 }
Gerrit Pape7f9778b2007-05-10 07:32:07 +00001069 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001070 if (!defined($actions{$action})) {
1071 die_error(400, "Unknown action");
1072 }
1073 if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
1074 !$project) {
1075 die_error(400, "Project needed");
1076 }
1077 $actions{$action}->();
Jakub Narebski77a153f2006-08-22 16:59:20 +02001078}
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001079
Jakub Narebski869d5882010-07-05 20:52:43 +02001080sub reset_timer {
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001081 our $t0 = [Time::HiRes::gettimeofday()]
1082 if defined $t0;
Jakub Narebski869d5882010-07-05 20:52:43 +02001083 our $number_of_git_cmds = 0;
1084}
1085
1086sub run_request {
1087 reset_timer();
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001088
1089 evaluate_uri();
Jonathan Nieder7f425db2010-07-30 22:01:59 -05001090 evaluate_gitweb_config();
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001091 check_loadavg();
1092
Jonathan Nieder7f425db2010-07-30 22:01:59 -05001093 # $projectroot and $projects_list might be set in gitweb config file
1094 $projects_list ||= $projectroot;
1095
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001096 evaluate_query_params();
1097 evaluate_path_info();
1098 evaluate_and_validate_params();
1099 evaluate_git_dir();
1100
1101 configure_gitweb_features();
1102
1103 dispatch();
Kay Sievers09bd7892005-08-07 20:21:23 +02001104}
Sam Vilaina0446e72010-05-07 14:54:05 +02001105
1106our $is_last_request = sub { 1 };
1107our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
1108our $CGI = 'CGI';
1109our $cgi;
Jakub Narebski45aa9892010-06-05 23:11:18 +02001110sub configure_as_fcgi {
1111 require CGI::Fast;
1112 our $CGI = 'CGI::Fast';
1113
1114 my $request_number = 0;
1115 # let each child service 100 requests
1116 our $is_last_request = sub { ++$request_number > 100 };
Jakub Narebskid04d3d42006-09-19 21:53:22 +02001117}
Sam Vilaina0446e72010-05-07 14:54:05 +02001118sub evaluate_argv {
Jakub Narebski45aa9892010-06-05 23:11:18 +02001119 my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
1120 configure_as_fcgi()
1121 if $script_name =~ /\.fcgi$/;
1122
Sam Vilaina0446e72010-05-07 14:54:05 +02001123 return unless (@ARGV);
1124
1125 require Getopt::Long;
1126 Getopt::Long::GetOptions(
Jakub Narebski45aa9892010-06-05 23:11:18 +02001127 'fastcgi|fcgi|f' => \&configure_as_fcgi,
Sam Vilaina0446e72010-05-07 14:54:05 +02001128 'nproc|n=i' => sub {
1129 my ($arg, $val) = @_;
1130 return unless eval { require FCGI::ProcManager; 1; };
1131 my $proc_manager = FCGI::ProcManager->new({
1132 n_processes => $val,
1133 });
1134 our $pre_listen_hook = sub { $proc_manager->pm_manage() };
1135 our $pre_dispatch_hook = sub { $proc_manager->pm_pre_dispatch() };
1136 our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() };
1137 },
1138 );
1139}
1140
1141sub run {
1142 evaluate_argv();
Jakub Narebski869d5882010-07-05 20:52:43 +02001143 evaluate_git_version();
1144
Sam Vilaina0446e72010-05-07 14:54:05 +02001145 $pre_listen_hook->()
1146 if $pre_listen_hook;
1147
1148 REQUEST:
1149 while ($cgi = $CGI->new()) {
1150 $pre_dispatch_hook->()
1151 if $pre_dispatch_hook;
1152
1153 run_request();
1154
Jakub Narebski0b450102010-08-02 22:21:47 +02001155 $post_dispatch_hook->()
Sam Vilaina0446e72010-05-07 14:54:05 +02001156 if $post_dispatch_hook;
1157
1158 last REQUEST if ($is_last_request->());
1159 }
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001160
1161 DONE_GITWEB:
1162 1;
Kay Sievers09bd7892005-08-07 20:21:23 +02001163}
Sam Vilaina0446e72010-05-07 14:54:05 +02001164
Jakub Narebskic2394fe2010-05-07 14:54:04 +02001165run();
Kay Sievers09bd7892005-08-07 20:21:23 +02001166
Jakub Narebski5ed2ec12010-06-13 12:09:32 +02001167if (defined caller) {
1168 # wrapped in a subroutine processing requests,
1169 # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
1170 return;
1171} else {
1172 # pure CGI script, serving single request
1173 exit;
1174}
Kay Sievers823d5dc2005-08-07 19:57:58 +02001175
Jakub Narebski717b8312006-07-31 21:22:15 +02001176## ======================================================================
Martin Waitz06a9d862006-08-16 00:23:50 +02001177## action links
1178
Jakub Narebski377bee32010-04-24 15:53:19 +02001179# possible values of extra options
1180# -full => 0|1 - use absolute/full URL ($my_uri/$my_url as base)
1181# -replay => 1 - start from a current view (replay with modifications)
1182# -path_info => 0|1 - don't use/use path_info URL (if possible)
Jakub Narebski74fd8722009-05-07 19:11:29 +02001183sub href {
Jakub Narebski498fe002006-08-22 19:05:25 +02001184 my %params = @_;
Jakub Narebskibd5d1e42006-11-19 15:05:21 +01001185 # default is to use -absolute url() i.e. $my_uri
1186 my $href = $params{-full} ? $my_url : $my_uri;
Jakub Narebski498fe002006-08-22 19:05:25 +02001187
Jakub Narebskiafa9b622008-02-14 09:22:30 +01001188 $params{'project'} = $project unless exists $params{'project'};
1189
Jakub Narebski1cad2832007-11-01 13:06:27 +01001190 if ($params{-replay}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001191 while (my ($name, $symbol) = each %cgi_param_mapping) {
Jakub Narebski1cad2832007-11-01 13:06:27 +01001192 if (!exists $params{$name}) {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001193 $params{$name} = $input_params{$name};
Jakub Narebski1cad2832007-11-01 13:06:27 +01001194 }
1195 }
1196 }
1197
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08001198 my $use_pathinfo = gitweb_check_feature('pathinfo');
Jakub Narebski377bee32010-04-24 15:53:19 +02001199 if (defined $params{'project'} &&
1200 (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001201 # try to put as many parameters as possible in PATH_INFO:
1202 # - project name
1203 # - action
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001204 # - hash_parent or hash_parent_base:/file_parent
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +02001205 # - hash or hash_base:/filename
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001206 # - the snapshot_format as an appropriate suffix
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001207
1208 # When the script is the root DirectoryIndex for the domain,
1209 # $href here would be something like http://gitweb.example.com/
1210 # Thus, we strip any trailing / from $href, to spare us double
1211 # slashes in the final URL
1212 $href =~ s,/$,,;
1213
1214 # Then add the project name, if present
Giuseppe Bilottafb098a92009-01-02 12:34:40 +01001215 $href .= "/".esc_url($params{'project'});
Martin Waitz9e756902006-10-01 23:57:48 +02001216 delete $params{'project'};
1217
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001218 # since we destructively absorb parameters, we keep this
1219 # boolean that remembers if we're handling a snapshot
1220 my $is_snapshot = $params{'action'} eq 'snapshot';
1221
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001222 # Summary just uses the project path URL, any other action is
1223 # added to the URL
1224 if (defined $params{'action'}) {
1225 $href .= "/".esc_url($params{'action'}) unless $params{'action'} eq 'summary';
Martin Waitz9e756902006-10-01 23:57:48 +02001226 delete $params{'action'};
1227 }
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001228
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001229 # Next, we put hash_parent_base:/file_parent..hash_base:/file_name,
1230 # stripping nonexistent or useless pieces
1231 $href .= "/" if ($params{'hash_base'} || $params{'hash_parent_base'}
1232 || $params{'hash_parent'} || $params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001233 if (defined $params{'hash_base'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001234 if (defined $params{'hash_parent_base'}) {
1235 $href .= esc_url($params{'hash_parent_base'});
1236 # skip the file_parent if it's the same as the file_name
Giuseppe Bilottab7da7212009-07-31 08:48:49 +02001237 if (defined $params{'file_parent'}) {
1238 if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) {
1239 delete $params{'file_parent'};
1240 } elsif ($params{'file_parent'} !~ /\.\./) {
1241 $href .= ":/".esc_url($params{'file_parent'});
1242 delete $params{'file_parent'};
1243 }
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001244 }
1245 $href .= "..";
1246 delete $params{'hash_parent'};
1247 delete $params{'hash_parent_base'};
1248 } elsif (defined $params{'hash_parent'}) {
1249 $href .= esc_url($params{'hash_parent'}). "..";
1250 delete $params{'hash_parent'};
1251 }
1252
1253 $href .= esc_url($params{'hash_base'});
1254 if (defined $params{'file_name'} && $params{'file_name'} !~ /\.\./) {
Giuseppe Bilotta3550ea72008-10-21 21:34:52 +02001255 $href .= ":/".esc_url($params{'file_name'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001256 delete $params{'file_name'};
1257 }
1258 delete $params{'hash'};
1259 delete $params{'hash_base'};
1260 } elsif (defined $params{'hash'}) {
Giuseppe Bilotta8db49a72008-10-21 21:34:54 +02001261 $href .= esc_url($params{'hash'});
Giuseppe Bilottab02bd7a2008-10-21 21:34:51 +02001262 delete $params{'hash'};
1263 }
Giuseppe Bilottac752a0e2008-11-02 10:21:39 +01001264
1265 # If the action was a snapshot, we can absorb the
1266 # snapshot_format parameter too
1267 if ($is_snapshot) {
1268 my $fmt = $params{'snapshot_format'};
1269 # snapshot_format should always be defined when href()
1270 # is called, but just in case some code forgets, we
1271 # fall back to the default
1272 $fmt ||= $snapshot_fmts[0];
1273 $href .= $known_snapshot_formats{$fmt}{'suffix'};
1274 delete $params{'snapshot_format'};
1275 }
Martin Waitz9e756902006-10-01 23:57:48 +02001276 }
1277
1278 # now encode the parameters explicitly
Jakub Narebski498fe002006-08-22 19:05:25 +02001279 my @result = ();
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001280 for (my $i = 0; $i < @cgi_param_mapping; $i += 2) {
1281 my ($name, $symbol) = ($cgi_param_mapping[$i], $cgi_param_mapping[$i+1]);
Jakub Narebski498fe002006-08-22 19:05:25 +02001282 if (defined $params{$name}) {
Jakub Narebskif22cca42007-07-29 01:04:09 +02001283 if (ref($params{$name}) eq "ARRAY") {
1284 foreach my $par (@{$params{$name}}) {
1285 push @result, $symbol . "=" . esc_param($par);
1286 }
1287 } else {
1288 push @result, $symbol . "=" . esc_param($params{$name});
1289 }
Jakub Narebski498fe002006-08-22 19:05:25 +02001290 }
1291 }
Martin Waitz9e756902006-10-01 23:57:48 +02001292 $href .= "?" . join(';', @result) if scalar @result;
1293
1294 return $href;
Martin Waitz06a9d862006-08-16 00:23:50 +02001295}
1296
1297
1298## ======================================================================
Jakub Narebski717b8312006-07-31 21:22:15 +02001299## validation, quoting/unquoting and escaping
1300
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001301sub validate_action {
1302 my $input = shift || return undef;
1303 return undef unless exists $actions{$input};
1304 return $input;
1305}
1306
1307sub validate_project {
1308 my $input = shift || return undef;
1309 if (!validate_pathname($input) ||
1310 !(-d "$projectroot/$input") ||
Alexander Gavrilovec26f092008-11-06 01:15:56 +03001311 !check_export_ok("$projectroot/$input") ||
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02001312 ($strict_export && !project_in_list($input))) {
1313 return undef;
1314 } else {
1315 return $input;
1316 }
1317}
1318
Jakub Narebski24d06932006-09-26 01:57:02 +02001319sub validate_pathname {
1320 my $input = shift || return undef;
Jakub Narebski717b8312006-07-31 21:22:15 +02001321
Jakub Narebski24d06932006-09-26 01:57:02 +02001322 # no '.' or '..' as elements of path, i.e. no '.' nor '..'
1323 # at the beginning, at the end, and between slashes.
1324 # also this catches doubled slashes
1325 if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
1326 return undef;
1327 }
1328 # no null characters
1329 if ($input =~ m!\0!) {
1330 return undef;
1331 }
1332 return $input;
1333}
1334
1335sub validate_refname {
1336 my $input = shift || return undef;
1337
1338 # textual hashes are O.K.
Jakub Narebski717b8312006-07-31 21:22:15 +02001339 if ($input =~ m/^[0-9a-fA-F]{40}$/) {
1340 return $input;
1341 }
Jakub Narebski24d06932006-09-26 01:57:02 +02001342 # it must be correct pathname
1343 $input = validate_pathname($input)
1344 or return undef;
1345 # restrictions on ref name according to git-check-ref-format
1346 if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001347 return undef;
1348 }
1349 return $input;
1350}
1351
Martin Koegler00f429a2007-06-03 17:42:44 +02001352# decode sequences of octets in utf8 into Perl's internal form,
1353# which is utf-8 with utf8 flag set if needed. gitweb writes out
1354# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
1355sub to_utf8 {
1356 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001357 return undef unless defined $str;
İsmail Dönmeze5d3de52007-12-04 10:55:41 +02001358 if (utf8::valid($str)) {
1359 utf8::decode($str);
1360 return $str;
Martin Koegler00f429a2007-06-03 17:42:44 +02001361 } else {
1362 return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
1363 }
1364}
1365
Kay Sievers232ff552005-11-24 16:56:55 +01001366# quote unsafe chars, but keep the slash, even when it's not
1367# correct, but quoted slashes look too horrible in bookmarks
1368sub esc_param {
Kay Sievers353347b2005-11-14 05:47:18 +01001369 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001370 return undef unless defined $str;
Giuseppe Bilotta452e2252009-10-13 21:51:36 +02001371 $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg;
Kay Sieversa9e60b72005-11-14 15:15:12 +01001372 $str =~ s/ /\+/g;
Kay Sievers353347b2005-11-14 05:47:18 +01001373 return $str;
1374}
1375
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02001376# quote unsafe chars in whole URL, so some characters cannot be quoted
Jakub Narebskif93bff82006-09-26 01:58:41 +02001377sub esc_url {
1378 my $str = shift;
Jakub Narebski1df48762010-02-07 21:52:25 +01001379 return undef unless defined $str;
Pavan Kumar Sunkara109988f2010-07-15 12:59:01 +05301380 $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
Jakub Narebskif93bff82006-09-26 01:58:41 +02001381 $str =~ s/ /\+/g;
1382 return $str;
1383}
1384
Kay Sievers232ff552005-11-24 16:56:55 +01001385# replace invalid utf8 character with SUBSTITUTION sequence
Jakub Narebski74fd8722009-05-07 19:11:29 +02001386sub esc_html {
Kay Sievers40c13812005-11-19 17:41:29 +01001387 my $str = shift;
Jakub Narebski6255ef02006-11-01 14:33:21 +01001388 my %opts = @_;
1389
Jakub Narebski1df48762010-02-07 21:52:25 +01001390 return undef unless defined $str;
1391
Martin Koegler00f429a2007-06-03 17:42:44 +02001392 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001393 $str = $cgi->escapeHTML($str);
Jakub Narebski6255ef02006-11-01 14:33:21 +01001394 if ($opts{'-nbsp'}) {
1395 $str =~ s/ /&nbsp;/g;
1396 }
Junio C Hamano25ffbb22006-11-08 15:11:10 -08001397 $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
Kay Sievers40c13812005-11-19 17:41:29 +01001398 return $str;
1399}
1400
Jakub Narebski391862e2006-11-25 09:43:59 +01001401# quote control characters and escape filename to HTML
1402sub esc_path {
1403 my $str = shift;
1404 my %opts = @_;
1405
Jakub Narebski1df48762010-02-07 21:52:25 +01001406 return undef unless defined $str;
1407
Martin Koegler00f429a2007-06-03 17:42:44 +02001408 $str = to_utf8($str);
Li Yangc390ae92007-03-06 11:58:56 +08001409 $str = $cgi->escapeHTML($str);
Jakub Narebski391862e2006-11-25 09:43:59 +01001410 if ($opts{'-nbsp'}) {
1411 $str =~ s/ /&nbsp;/g;
1412 }
1413 $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
1414 return $str;
1415}
1416
1417# Make control characters "printable", using character escape codes (CEC)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001418sub quot_cec {
1419 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001420 my %opts = @_;
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001421 my %es = ( # character escape codes, aka escape sequences
Jakub Narebskic84c4832008-02-17 18:48:13 +01001422 "\t" => '\t', # tab (HT)
1423 "\n" => '\n', # line feed (LF)
1424 "\r" => '\r', # carrige return (CR)
1425 "\f" => '\f', # form feed (FF)
1426 "\b" => '\b', # backspace (BS)
1427 "\a" => '\a', # alarm (bell) (BEL)
1428 "\e" => '\e', # escape (ESC)
1429 "\013" => '\v', # vertical tab (VT)
1430 "\000" => '\0', # nul character (NUL)
1431 );
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001432 my $chr = ( (exists $es{$cntrl})
1433 ? $es{$cntrl}
Petr Baudis25dfd172008-10-01 22:11:54 +02001434 : sprintf('\%2x', ord($cntrl)) );
Jakub Narebskic84c4832008-02-17 18:48:13 +01001435 if ($opts{-nohtml}) {
1436 return $chr;
1437 } else {
1438 return "<span class=\"cntrl\">$chr</span>";
1439 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001440}
1441
Jakub Narebski391862e2006-11-25 09:43:59 +01001442# Alternatively use unicode control pictures codepoints,
1443# Unicode "printable representation" (PR)
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001444sub quot_upr {
1445 my $cntrl = shift;
Jakub Narebskic84c4832008-02-17 18:48:13 +01001446 my %opts = @_;
1447
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001448 my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
Jakub Narebskic84c4832008-02-17 18:48:13 +01001449 if ($opts{-nohtml}) {
1450 return $chr;
1451 } else {
1452 return "<span class=\"cntrl\">$chr</span>";
1453 }
Jakub Narebski1d3bc0c2006-11-08 11:50:07 +01001454}
1455
Kay Sievers232ff552005-11-24 16:56:55 +01001456# git may return quoted and escaped filenames
1457sub unquote {
1458 my $str = shift;
Jakub Narebski403d0902006-11-08 11:48:56 +01001459
1460 sub unq {
1461 my $seq = shift;
1462 my %es = ( # character escape codes, aka escape sequences
1463 't' => "\t", # tab (HT, TAB)
1464 'n' => "\n", # newline (NL)
1465 'r' => "\r", # return (CR)
1466 'f' => "\f", # form feed (FF)
1467 'b' => "\b", # backspace (BS)
1468 'a' => "\a", # alarm (bell) (BEL)
1469 'e' => "\e", # escape (ESC)
1470 'v' => "\013", # vertical tab (VT)
1471 );
1472
1473 if ($seq =~ m/^[0-7]{1,3}$/) {
1474 # octal char sequence
1475 return chr(oct($seq));
1476 } elsif (exists $es{$seq}) {
1477 # C escape sequence, aka character escape code
Jakub Narebskic84c4832008-02-17 18:48:13 +01001478 return $es{$seq};
Jakub Narebski403d0902006-11-08 11:48:56 +01001479 }
1480 # quoted ordinary character
1481 return $seq;
1482 }
1483
Kay Sievers232ff552005-11-24 16:56:55 +01001484 if ($str =~ m/^"(.*)"$/) {
Jakub Narebski403d0902006-11-08 11:48:56 +01001485 # needs unquoting
Kay Sievers232ff552005-11-24 16:56:55 +01001486 $str = $1;
Jakub Narebski403d0902006-11-08 11:48:56 +01001487 $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
Kay Sievers232ff552005-11-24 16:56:55 +01001488 }
1489 return $str;
1490}
1491
Jakub Narebskif16db172006-08-06 02:08:31 +02001492# escape tabs (convert tabs to spaces)
1493sub untabify {
1494 my $line = shift;
1495
1496 while ((my $pos = index($line, "\t")) != -1) {
1497 if (my $count = (8 - ($pos % 8))) {
1498 my $spaces = ' ' x $count;
1499 $line =~ s/\t/$spaces/;
1500 }
1501 }
1502
1503 return $line;
1504}
1505
Matthias Lederhofer32f4aac2006-09-17 00:31:01 +02001506sub project_in_list {
1507 my $project = shift;
1508 my @list = git_get_projects_list();
1509 return @list && scalar(grep { $_->{'path'} eq $project } @list);
1510}
1511
Jakub Narebski717b8312006-07-31 21:22:15 +02001512## ----------------------------------------------------------------------
1513## HTML aware string manipulation
1514
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001515# Try to chop given string on a word boundary between position
1516# $len and $len+$add_len. If there is no word boundary there,
1517# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
1518# (marking chopped part) would be longer than given string.
Jakub Narebski717b8312006-07-31 21:22:15 +02001519sub chop_str {
1520 my $str = shift;
1521 my $len = shift;
1522 my $add_len = shift || 10;
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001523 my $where = shift || 'right'; # 'left' | 'center' | 'right'
Jakub Narebski717b8312006-07-31 21:22:15 +02001524
Anders Waldenborgdee27752008-05-21 13:44:43 +02001525 # Make sure perl knows it is utf8 encoded so we don't
1526 # cut in the middle of a utf8 multibyte char.
1527 $str = to_utf8($str);
1528
Jakub Narebski717b8312006-07-31 21:22:15 +02001529 # allow only $len chars, but don't cut a word if it would fit in $add_len
1530 # 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 +01001531 # remove chopped character entities entirely
1532
1533 # when chopping in the middle, distribute $len into left and right part
1534 # return early if chopping wouldn't make string shorter
1535 if ($where eq 'center') {
1536 return $str if ($len + 5 >= length($str)); # filler is length 5
1537 $len = int($len/2);
1538 } else {
1539 return $str if ($len + 4 >= length($str)); # filler is length 4
Jakub Narebski717b8312006-07-31 21:22:15 +02001540 }
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001541
1542 # regexps: ending and beginning with word part up to $add_len
1543 my $endre = qr/.{$len}\w{0,$add_len}/;
1544 my $begre = qr/\w{0,$add_len}.{$len}/;
1545
1546 if ($where eq 'left') {
1547 $str =~ m/^(.*?)($begre)$/;
1548 my ($lead, $body) = ($1, $2);
1549 if (length($lead) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001550 $lead = " ...";
1551 }
1552 return "$lead$body";
1553
1554 } elsif ($where eq 'center') {
1555 $str =~ m/^($endre)(.*)$/;
1556 my ($left, $str) = ($1, $2);
1557 $str =~ m/^(.*?)($begre)$/;
1558 my ($mid, $right) = ($1, $2);
1559 if (length($mid) > 5) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001560 $mid = " ... ";
1561 }
1562 return "$left$mid$right";
1563
1564 } else {
1565 $str =~ m/^($endre)(.*)$/;
1566 my $body = $1;
1567 my $tail = $2;
1568 if (length($tail) > 4) {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001569 $tail = "... ";
1570 }
1571 return "$body$tail";
1572 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001573}
1574
David Symondsce58ec92007-10-23 11:31:22 +10001575# takes the same arguments as chop_str, but also wraps a <span> around the
1576# result with a title attribute if it does get chopped. Additionally, the
1577# string is HTML-escaped.
1578sub chop_and_escape_str {
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001579 my ($str) = @_;
David Symondsce58ec92007-10-23 11:31:22 +10001580
Jakub Narebskib8d97d02008-02-25 21:07:57 +01001581 my $chopped = chop_str(@_);
David Symondsce58ec92007-10-23 11:31:22 +10001582 if ($chopped eq $str) {
1583 return esc_html($chopped);
1584 } else {
Jakub Narebski14afe772009-05-22 17:35:46 +02001585 $str =~ s/[[:cntrl:]]/?/g;
Jakub Narebski850b90a2008-02-16 23:07:46 +01001586 return $cgi->span({-title=>$str}, esc_html($chopped));
David Symondsce58ec92007-10-23 11:31:22 +10001587 }
1588}
1589
Jakub Narebski717b8312006-07-31 21:22:15 +02001590## ----------------------------------------------------------------------
1591## functions returning short strings
1592
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001593# CSS class for given age value (in seconds)
1594sub age_class {
1595 my $age = shift;
1596
Jakub Narebski785cdea2007-05-13 12:39:22 +02001597 if (!defined $age) {
1598 return "noage";
1599 } elsif ($age < 60*60*2) {
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00001600 return "age0";
1601 } elsif ($age < 60*60*24*2) {
1602 return "age1";
1603 } else {
1604 return "age2";
1605 }
1606}
1607
Jakub Narebski717b8312006-07-31 21:22:15 +02001608# convert age in seconds to "nn units ago" string
1609sub age_string {
1610 my $age = shift;
1611 my $age_str;
1612
1613 if ($age > 60*60*24*365*2) {
1614 $age_str = (int $age/60/60/24/365);
1615 $age_str .= " years ago";
1616 } elsif ($age > 60*60*24*(365/12)*2) {
1617 $age_str = int $age/60/60/24/(365/12);
1618 $age_str .= " months ago";
1619 } elsif ($age > 60*60*24*7*2) {
1620 $age_str = int $age/60/60/24/7;
1621 $age_str .= " weeks ago";
1622 } elsif ($age > 60*60*24*2) {
1623 $age_str = int $age/60/60/24;
1624 $age_str .= " days ago";
1625 } elsif ($age > 60*60*2) {
1626 $age_str = int $age/60/60;
1627 $age_str .= " hours ago";
1628 } elsif ($age > 60*2) {
1629 $age_str = int $age/60;
1630 $age_str .= " min ago";
1631 } elsif ($age > 2) {
1632 $age_str = int $age;
1633 $age_str .= " sec ago";
1634 } else {
1635 $age_str .= " right now";
1636 }
1637 return $age_str;
1638}
1639
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001640use constant {
1641 S_IFINVALID => 0030000,
1642 S_IFGITLINK => 0160000,
1643};
1644
1645# submodule/subproject, a commit object reference
Jakub Narebski74fd8722009-05-07 19:11:29 +02001646sub S_ISGITLINK {
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001647 my $mode = shift;
1648
1649 return (($mode & S_IFMT) == S_IFGITLINK)
1650}
1651
Jakub Narebski717b8312006-07-31 21:22:15 +02001652# convert file mode in octal to symbolic file mode string
1653sub mode_str {
1654 my $mode = oct shift;
1655
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001656 if (S_ISGITLINK($mode)) {
1657 return 'm---------';
1658 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001659 return 'drwxr-xr-x';
1660 } elsif (S_ISLNK($mode)) {
1661 return 'lrwxrwxrwx';
1662 } elsif (S_ISREG($mode)) {
1663 # git cares only about the executable bit
1664 if ($mode & S_IXUSR) {
1665 return '-rwxr-xr-x';
1666 } else {
1667 return '-rw-r--r--';
1668 };
1669 } else {
1670 return '----------';
1671 }
1672}
1673
1674# convert file mode in octal to file type string
1675sub file_type {
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02001676 my $mode = shift;
1677
1678 if ($mode !~ m/^[0-7]+$/) {
1679 return $mode;
1680 } else {
1681 $mode = oct $mode;
1682 }
Jakub Narebski717b8312006-07-31 21:22:15 +02001683
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001684 if (S_ISGITLINK($mode)) {
1685 return "submodule";
1686 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02001687 return "directory";
1688 } elsif (S_ISLNK($mode)) {
1689 return "symlink";
1690 } elsif (S_ISREG($mode)) {
1691 return "file";
1692 } else {
1693 return "unknown";
1694 }
1695}
1696
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001697# convert file mode in octal to file type description string
1698sub file_type_long {
1699 my $mode = shift;
1700
1701 if ($mode !~ m/^[0-7]+$/) {
1702 return $mode;
1703 } else {
1704 $mode = oct $mode;
1705 }
1706
Jakub Narebski01ac1e32007-07-28 16:27:31 +02001707 if (S_ISGITLINK($mode)) {
1708 return "submodule";
1709 } elsif (S_ISDIR($mode & S_IFMT)) {
Jakub Narebski744d0ac2006-11-08 17:59:41 +01001710 return "directory";
1711 } elsif (S_ISLNK($mode)) {
1712 return "symlink";
1713 } elsif (S_ISREG($mode)) {
1714 if ($mode & S_IXUSR) {
1715 return "executable";
1716 } else {
1717 return "file";
1718 };
1719 } else {
1720 return "unknown";
1721 }
1722}
1723
1724
Jakub Narebski717b8312006-07-31 21:22:15 +02001725## ----------------------------------------------------------------------
1726## functions returning short HTML fragments, or transforming HTML fragments
Pavel Roskin3dff5372007-02-03 23:49:16 -05001727## which don't belong to other sections
Jakub Narebski717b8312006-07-31 21:22:15 +02001728
Junio C Hamano225932e2006-11-09 00:57:13 -08001729# format line of commit message.
Jakub Narebski717b8312006-07-31 21:22:15 +02001730sub format_log_line_html {
1731 my $line = shift;
1732
Junio C Hamano225932e2006-11-09 00:57:13 -08001733 $line = esc_html($line, -nbsp=>1);
Marcel M. Cary7d233de2009-02-17 19:00:43 -08001734 $line =~ s{\b([0-9a-fA-F]{8,40})\b}{
1735 $cgi->a({-href => href(action=>"object", hash=>$1),
1736 -class => "text"}, $1);
1737 }eg;
1738
Jakub Narebski717b8312006-07-31 21:22:15 +02001739 return $line;
1740}
1741
1742# format marker of refs pointing to given object
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001743
1744# the destination action is chosen based on object type and current context:
1745# - for annotated tags, we choose the tag view unless it's the current view
1746# already, in which case we go to shortlog view
1747# - for other refs, we keep the current view if we're in history, shortlog or
1748# log view, and select shortlog otherwise
Jakub Narebski847e01f2006-08-14 02:05:47 +02001749sub format_ref_marker {
Jakub Narebski717b8312006-07-31 21:22:15 +02001750 my ($refs, $id) = @_;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001751 my $markers = '';
Jakub Narebski717b8312006-07-31 21:22:15 +02001752
1753 if (defined $refs->{$id}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001754 foreach my $ref (@{$refs->{$id}}) {
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001755 # this code exploits the fact that non-lightweight tags are the
1756 # only indirect objects, and that they are the only objects for which
1757 # we want to use tag instead of shortlog as action
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001758 my ($type, $name) = qw();
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001759 my $indirect = ($ref =~ s/\^\{\}$//);
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001760 # e.g. tags/v2.6.11 or heads/next
1761 if ($ref =~ m!^(.*?)s?/(.*)$!) {
1762 $type = $1;
1763 $name = $2;
1764 } else {
1765 $type = "ref";
1766 $name = $ref;
1767 }
1768
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02001769 my $class = $type;
1770 $class .= " indirect" if $indirect;
1771
1772 my $dest_action = "shortlog";
1773
1774 if ($indirect) {
1775 $dest_action = "tag" unless $action eq "tag";
1776 } elsif ($action =~ /^(history|(short)?log)$/) {
1777 $dest_action = $action;
1778 }
1779
1780 my $dest = "";
1781 $dest .= "refs/" unless $ref =~ m!^refs/!;
1782 $dest .= $ref;
1783
1784 my $link = $cgi->a({
1785 -href => href(
1786 action=>$dest_action,
1787 hash=>$dest
1788 )}, $name);
1789
1790 $markers .= " <span class=\"$class\" title=\"$ref\">" .
1791 $link . "</span>";
Jakub Narebskid294e1c2006-08-14 02:14:20 +02001792 }
1793 }
1794
1795 if ($markers) {
1796 return ' <span class="refs">'. $markers . '</span>';
Jakub Narebski717b8312006-07-31 21:22:15 +02001797 } else {
1798 return "";
1799 }
1800}
1801
Jakub Narebski17d07442006-08-14 02:08:27 +02001802# format, perhaps shortened and with markers, title line
1803sub format_subject_html {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02001804 my ($long, $short, $href, $extra) = @_;
Jakub Narebski17d07442006-08-14 02:08:27 +02001805 $extra = '' unless defined($extra);
1806
1807 if (length($short) < length($long)) {
Jakub Narebski14afe772009-05-22 17:35:46 +02001808 $long =~ s/[[:cntrl:]]/?/g;
Jakub Narebski7c278012006-08-22 12:02:48 +02001809 return $cgi->a({-href => $href, -class => "list subject",
Martin Koegler00f429a2007-06-03 17:42:44 +02001810 -title => to_utf8($long)},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02001811 esc_html($short)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02001812 } else {
Jakub Narebski7c278012006-08-22 12:02:48 +02001813 return $cgi->a({-href => $href, -class => "list subject"},
Giuseppe Bilotta01b89f02009-08-23 10:28:09 +02001814 esc_html($long)) . $extra;
Jakub Narebski17d07442006-08-14 02:08:27 +02001815 }
1816}
1817
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02001818# Rather than recomputing the url for an email multiple times, we cache it
1819# after the first hit. This gives a visible benefit in views where the avatar
1820# for the same email is used repeatedly (e.g. shortlog).
1821# The cache is shared by all avatar engines (currently gravatar only), which
1822# are free to use it as preferred. Since only one avatar engine is used for any
1823# given page, there's no risk for cache conflicts.
1824our %avatar_cache = ();
1825
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02001826# Compute the picon url for a given email, by using the picon search service over at
1827# http://www.cs.indiana.edu/picons/search.html
1828sub picon_url {
1829 my $email = lc shift;
1830 if (!$avatar_cache{$email}) {
1831 my ($user, $domain) = split('@', $email);
1832 $avatar_cache{$email} =
1833 "http://www.cs.indiana.edu/cgi-pub/kinzler/piconsearch.cgi/" .
1834 "$domain/$user/" .
1835 "users+domains+unknown/up/single";
1836 }
1837 return $avatar_cache{$email};
1838}
1839
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02001840# Compute the gravatar url for a given email, if it's not in the cache already.
1841# Gravatar stores only the part of the URL before the size, since that's the
1842# one computationally more expensive. This also allows reuse of the cache for
1843# different sizes (for this particular engine).
1844sub gravatar_url {
1845 my $email = lc shift;
1846 my $size = shift;
1847 $avatar_cache{$email} ||=
1848 "http://www.gravatar.com/avatar/" .
1849 Digest::MD5::md5_hex($email) . "?s=";
1850 return $avatar_cache{$email} . $size;
1851}
1852
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001853# Insert an avatar for the given $email at the given $size if the feature
1854# is enabled.
1855sub git_get_avatar {
1856 my ($email, %opts) = @_;
1857 my $pre_white = ($opts{-pad_before} ? "&nbsp;" : "");
1858 my $post_white = ($opts{-pad_after} ? "&nbsp;" : "");
1859 $opts{-size} ||= 'default';
1860 my $size = $avatar_size{$opts{-size}} || $avatar_size{'default'};
1861 my $url = "";
1862 if ($git_avatar eq 'gravatar') {
Giuseppe Bilotta5a371b72009-06-30 00:00:52 +02001863 $url = gravatar_url($email, $size);
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02001864 } elsif ($git_avatar eq 'picon') {
1865 $url = picon_url($email);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001866 }
Giuseppe Bilotta679a1a12009-06-30 00:00:53 +02001867 # Other providers can be added by extending the if chain, defining $url
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001868 # as needed. If no variant puts something in $url, we assume avatars
1869 # are completely disabled/unavailable.
1870 if ($url) {
1871 return $pre_white .
1872 "<img width=\"$size\" " .
1873 "class=\"avatar\" " .
1874 "src=\"$url\" " .
Giuseppe Bilotta7d25ef42009-06-30 00:00:54 +02001875 "alt=\"\" " .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001876 "/>" . $post_white;
1877 } else {
1878 return "";
1879 }
1880}
1881
Stephen Boyde133d652009-10-15 21:14:59 -07001882sub format_search_author {
1883 my ($author, $searchtype, $displaytext) = @_;
1884 my $have_search = gitweb_check_feature('search');
1885
1886 if ($have_search) {
1887 my $performed = "";
1888 if ($searchtype eq 'author') {
1889 $performed = "authored";
1890 } elsif ($searchtype eq 'committer') {
1891 $performed = "committed";
1892 }
1893
1894 return $cgi->a({-href => href(action=>"search", hash=>$hash,
1895 searchtext=>$author,
1896 searchtype=>$searchtype), class=>"list",
1897 title=>"Search for commits $performed by $author"},
1898 $displaytext);
1899
1900 } else {
1901 return $displaytext;
1902 }
1903}
1904
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02001905# format the author name of the given commit with the given tag
1906# the author name is chopped and escaped according to the other
1907# optional parameters (see chop_str).
1908sub format_author_html {
1909 my $tag = shift;
1910 my $co = shift;
1911 my $author = chop_and_escape_str($co->{'author_name'}, @_);
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02001912 return "<$tag class=\"author\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07001913 format_search_author($co->{'author_name'}, "author",
1914 git_get_avatar($co->{'author_email'}, -pad_after => 1) .
1915 $author) .
1916 "</$tag>";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02001917}
1918
Jakub Narebski90921742007-06-08 13:27:42 +02001919# format git diff header line, i.e. "diff --(git|combined|cc) ..."
1920sub format_git_diff_header_line {
1921 my $line = shift;
1922 my $diffinfo = shift;
1923 my ($from, $to) = @_;
1924
1925 if ($diffinfo->{'nparents'}) {
1926 # combined diff
1927 $line =~ s!^(diff (.*?) )"?.*$!$1!;
1928 if ($to->{'href'}) {
1929 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1930 esc_path($to->{'file'}));
1931 } else { # file was deleted (no href)
1932 $line .= esc_path($to->{'file'});
1933 }
1934 } else {
1935 # "ordinary" diff
1936 $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
1937 if ($from->{'href'}) {
1938 $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
1939 'a/' . esc_path($from->{'file'}));
1940 } else { # file was added (no href)
1941 $line .= 'a/' . esc_path($from->{'file'});
1942 }
1943 $line .= ' ';
1944 if ($to->{'href'}) {
1945 $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
1946 'b/' . esc_path($to->{'file'}));
1947 } else { # file was deleted
1948 $line .= 'b/' . esc_path($to->{'file'});
1949 }
1950 }
1951
1952 return "<div class=\"diff header\">$line</div>\n";
1953}
1954
1955# format extended diff header line, before patch itself
1956sub format_extended_diff_header_line {
1957 my $line = shift;
1958 my $diffinfo = shift;
1959 my ($from, $to) = @_;
1960
1961 # match <path>
1962 if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
1963 $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
1964 esc_path($from->{'file'}));
1965 }
1966 if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
1967 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
1968 esc_path($to->{'file'}));
1969 }
1970 # match single <mode>
1971 if ($line =~ m/\s(\d{6})$/) {
1972 $line .= '<span class="info"> (' .
1973 file_type_long($1) .
1974 ')</span>';
1975 }
1976 # match <hash>
1977 if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
1978 # can match only for combined diff
1979 $line = 'index ';
1980 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
1981 if ($from->{'href'}[$i]) {
1982 $line .= $cgi->a({-href=>$from->{'href'}[$i],
1983 -class=>"hash"},
1984 substr($diffinfo->{'from_id'}[$i],0,7));
1985 } else {
1986 $line .= '0' x 7;
1987 }
1988 # separator
1989 $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
1990 }
1991 $line .= '..';
1992 if ($to->{'href'}) {
1993 $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
1994 substr($diffinfo->{'to_id'},0,7));
1995 } else {
1996 $line .= '0' x 7;
1997 }
1998
1999 } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
2000 # can match only for ordinary diff
2001 my ($from_link, $to_link);
2002 if ($from->{'href'}) {
2003 $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
2004 substr($diffinfo->{'from_id'},0,7));
2005 } else {
2006 $from_link = '0' x 7;
2007 }
2008 if ($to->{'href'}) {
2009 $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
2010 substr($diffinfo->{'to_id'},0,7));
2011 } else {
2012 $to_link = '0' x 7;
2013 }
2014 my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
2015 $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
2016 }
2017
2018 return $line . "<br/>\n";
2019}
2020
2021# format from-file/to-file diff header
2022sub format_diff_from_to_header {
Jakub Narebski91af4ce2007-06-08 13:32:44 +02002023 my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
Jakub Narebski90921742007-06-08 13:27:42 +02002024 my $line;
2025 my $result = '';
2026
2027 $line = $from_line;
2028 #assert($line =~ m/^---/) if DEBUG;
Jakub Narebskideaa01a2007-06-08 13:29:49 +02002029 # no extra formatting for "^--- /dev/null"
2030 if (! $diffinfo->{'nparents'}) {
2031 # ordinary (single parent) diff
2032 if ($line =~ m!^--- "?a/!) {
2033 if ($from->{'href'}) {
2034 $line = '--- a/' .
2035 $cgi->a({-href=>$from->{'href'}, -class=>"path"},
2036 esc_path($from->{'file'}));
2037 } else {
2038 $line = '--- a/' .
2039 esc_path($from->{'file'});
2040 }
2041 }
2042 $result .= qq!<div class="diff from_file">$line</div>\n!;
2043
2044 } else {
2045 # combined diff (merge commit)
2046 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
2047 if ($from->{'href'}[$i]) {
2048 $line = '--- ' .
Jakub Narebski91af4ce2007-06-08 13:32:44 +02002049 $cgi->a({-href=>href(action=>"blobdiff",
2050 hash_parent=>$diffinfo->{'from_id'}[$i],
2051 hash_parent_base=>$parents[$i],
2052 file_parent=>$from->{'file'}[$i],
2053 hash=>$diffinfo->{'to_id'},
2054 hash_base=>$hash,
2055 file_name=>$to->{'file'}),
2056 -class=>"path",
2057 -title=>"diff" . ($i+1)},
2058 $i+1) .
2059 '/' .
Jakub Narebskideaa01a2007-06-08 13:29:49 +02002060 $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
2061 esc_path($from->{'file'}[$i]));
2062 } else {
2063 $line = '--- /dev/null';
2064 }
2065 $result .= qq!<div class="diff from_file">$line</div>\n!;
Jakub Narebski90921742007-06-08 13:27:42 +02002066 }
2067 }
Jakub Narebski90921742007-06-08 13:27:42 +02002068
2069 $line = $to_line;
2070 #assert($line =~ m/^\+\+\+/) if DEBUG;
2071 # no extra formatting for "^+++ /dev/null"
2072 if ($line =~ m!^\+\+\+ "?b/!) {
2073 if ($to->{'href'}) {
2074 $line = '+++ b/' .
2075 $cgi->a({-href=>$to->{'href'}, -class=>"path"},
2076 esc_path($to->{'file'}));
2077 } else {
2078 $line = '+++ b/' .
2079 esc_path($to->{'file'});
2080 }
2081 }
2082 $result .= qq!<div class="diff to_file">$line</div>\n!;
2083
2084 return $result;
2085}
2086
Jakub Narebskicd030c32007-06-08 13:33:28 +02002087# create note for patch simplified by combined diff
2088sub format_diff_cc_simplified {
2089 my ($diffinfo, @parents) = @_;
2090 my $result = '';
2091
2092 $result .= "<div class=\"diff header\">" .
2093 "diff --cc ";
2094 if (!is_deleted($diffinfo)) {
2095 $result .= $cgi->a({-href => href(action=>"blob",
2096 hash_base=>$hash,
2097 hash=>$diffinfo->{'to_id'},
2098 file_name=>$diffinfo->{'to_file'}),
2099 -class => "path"},
2100 esc_path($diffinfo->{'to_file'}));
2101 } else {
2102 $result .= esc_path($diffinfo->{'to_file'});
2103 }
2104 $result .= "</div>\n" . # class="diff header"
2105 "<div class=\"diff nodifferences\">" .
2106 "Simple merge" .
2107 "</div>\n"; # class="diff nodifferences"
2108
2109 return $result;
2110}
2111
Jakub Narebski90921742007-06-08 13:27:42 +02002112# format patch (diff) line (not to be used for diff headers)
Jakub Narebskieee08902006-08-24 00:15:14 +02002113sub format_diff_line {
2114 my $line = shift;
Jakub Narebski59e3b142006-11-18 23:35:40 +01002115 my ($from, $to) = @_;
Jakub Narebskieee08902006-08-24 00:15:14 +02002116 my $diff_class = "";
2117
2118 chomp $line;
2119
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02002120 if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
2121 # combined diff
2122 my $prefix = substr($line, 0, scalar @{$from->{'href'}});
2123 if ($line =~ m/^\@{3}/) {
2124 $diff_class = " chunk_header";
2125 } elsif ($line =~ m/^\\/) {
2126 $diff_class = " incomplete";
2127 } elsif ($prefix =~ tr/+/+/) {
2128 $diff_class = " add";
2129 } elsif ($prefix =~ tr/-/-/) {
2130 $diff_class = " rem";
2131 }
2132 } else {
2133 # assume ordinary diff
2134 my $char = substr($line, 0, 1);
2135 if ($char eq '+') {
2136 $diff_class = " add";
2137 } elsif ($char eq '-') {
2138 $diff_class = " rem";
2139 } elsif ($char eq '@') {
2140 $diff_class = " chunk_header";
2141 } elsif ($char eq "\\") {
2142 $diff_class = " incomplete";
2143 }
Jakub Narebskieee08902006-08-24 00:15:14 +02002144 }
2145 $line = untabify($line);
Jakub Narebski59e3b142006-11-18 23:35:40 +01002146 if ($from && $to && $line =~ m/^\@{2} /) {
2147 my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
2148 $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
2149
2150 $from_lines = 0 unless defined $from_lines;
2151 $to_lines = 0 unless defined $to_lines;
2152
2153 if ($from->{'href'}) {
2154 $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
2155 -class=>"list"}, $from_text);
2156 }
2157 if ($to->{'href'}) {
2158 $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
2159 -class=>"list"}, $to_text);
2160 }
2161 $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
2162 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
2163 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02002164 } elsif ($from && $to && $line =~ m/^\@{3}/) {
2165 my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
2166 my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
2167
2168 @from_text = split(' ', $ranges);
2169 for (my $i = 0; $i < @from_text; ++$i) {
2170 ($from_start[$i], $from_nlines[$i]) =
2171 (split(',', substr($from_text[$i], 1)), 0);
2172 }
2173
2174 $to_text = pop @from_text;
2175 $to_start = pop @from_start;
2176 $to_nlines = pop @from_nlines;
2177
2178 $line = "<span class=\"chunk_info\">$prefix ";
2179 for (my $i = 0; $i < @from_text; ++$i) {
2180 if ($from->{'href'}[$i]) {
2181 $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
2182 -class=>"list"}, $from_text[$i]);
2183 } else {
2184 $line .= $from_text[$i];
2185 }
2186 $line .= " ";
2187 }
2188 if ($to->{'href'}) {
2189 $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
2190 -class=>"list"}, $to_text);
2191 } else {
2192 $line .= $to_text;
2193 }
2194 $line .= " $prefix</span>" .
2195 "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
2196 return "<div class=\"diff$diff_class\">$line</div>\n";
Jakub Narebski59e3b142006-11-18 23:35:40 +01002197 }
Jakub Narebski6255ef02006-11-01 14:33:21 +01002198 return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02002199}
2200
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002201# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
2202# linked. Pass the hash of the tree/commit to snapshot.
2203sub format_snapshot_links {
2204 my ($hash) = @_;
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002205 my $num_fmts = @snapshot_fmts;
2206 if ($num_fmts > 1) {
2207 # A parenthesized list of links bearing format names.
Jakub Narebskia7817852007-07-22 23:41:20 +02002208 # e.g. "snapshot (_tar.gz_ _zip_)"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002209 return "snapshot (" . join(' ', map
2210 $cgi->a({
2211 -href => href(
2212 action=>"snapshot",
2213 hash=>$hash,
2214 snapshot_format=>$_
2215 )
2216 }, $known_snapshot_formats{$_}{'display'})
2217 , @snapshot_fmts) . ")";
2218 } elsif ($num_fmts == 1) {
2219 # A single "snapshot" link whose tooltip bears the format name.
Jakub Narebskia7817852007-07-22 23:41:20 +02002220 # i.e. "_snapshot_"
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002221 my ($fmt) = @snapshot_fmts;
Jakub Narebskia7817852007-07-22 23:41:20 +02002222 return
2223 $cgi->a({
Matt McCutchena3c8ab32007-07-22 01:30:27 +02002224 -href => href(
2225 action=>"snapshot",
2226 hash=>$hash,
2227 snapshot_format=>$fmt
2228 ),
2229 -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
2230 }, "snapshot");
2231 } else { # $num_fmts == 0
2232 return undef;
2233 }
2234}
2235
Jakub Narebski35621982008-04-20 22:09:48 +02002236## ......................................................................
2237## functions returning values to be passed, perhaps after some
2238## transformation, to other functions; e.g. returning arguments to href()
2239
2240# returns hash to be passed to href to generate gitweb URL
2241# in -title key it returns description of link
2242sub get_feed_info {
2243 my $format = shift || 'Atom';
2244 my %res = (action => lc($format));
2245
2246 # feed links are possible only for project views
2247 return unless (defined $project);
2248 # some views should link to OPML, or to generic project feed,
2249 # or don't have specific feed yet (so they should use generic)
2250 return if ($action =~ /^(?:tags|heads|forks|tag|search)$/x);
2251
2252 my $branch;
2253 # branches refs uses 'refs/heads/' prefix (fullname) to differentiate
2254 # from tag links; this also makes possible to detect branch links
2255 if ((defined $hash_base && $hash_base =~ m!^refs/heads/(.*)$!) ||
2256 (defined $hash && $hash =~ m!^refs/heads/(.*)$!)) {
2257 $branch = $1;
2258 }
2259 # find log type for feed description (title)
2260 my $type = 'log';
2261 if (defined $file_name) {
2262 $type = "history of $file_name";
2263 $type .= "/" if ($action eq 'tree');
2264 $type .= " on '$branch'" if (defined $branch);
2265 } else {
2266 $type = "log of $branch" if (defined $branch);
2267 }
2268
2269 $res{-title} = $type;
2270 $res{'hash'} = (defined $branch ? "refs/heads/$branch" : undef);
2271 $res{'file_name'} = $file_name;
2272
2273 return %res;
2274}
2275
Jakub Narebski717b8312006-07-31 21:22:15 +02002276## ----------------------------------------------------------------------
2277## git utility subroutines, invoking git commands
2278
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002279# returns path to the core git executable and the --git-dir parameter as list
2280sub git_cmd {
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02002281 $number_of_git_cmds++;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002282 return $GIT, '--git-dir='.$git_dir;
2283}
2284
Lea Wiemann516381d2008-06-17 23:46:35 +02002285# quote the given arguments for passing them to the shell
2286# quote_command("command", "arg 1", "arg with ' and ! characters")
2287# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'"
2288# Try to avoid using this function wherever possible.
2289sub quote_command {
2290 return join(' ',
Jakub Narebski68cedb12009-05-10 02:40:37 +02002291 map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ );
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002292}
2293
Jakub Narebski717b8312006-07-31 21:22:15 +02002294# get HEAD ref of given project as hash
Jakub Narebski847e01f2006-08-14 02:05:47 +02002295sub git_get_head_hash {
Mark Radab6292752009-11-07 16:13:29 +01002296 return git_get_full_hash(shift, 'HEAD');
2297}
2298
2299sub git_get_full_hash {
2300 return git_get_hash(@_);
2301}
2302
2303sub git_get_short_hash {
2304 return git_get_hash(@_, '--short=7');
2305}
2306
2307sub git_get_hash {
2308 my ($project, $hash, @options) = @_;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002309 my $o_git_dir = $git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002310 my $retval = undef;
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002311 $git_dir = "$projectroot/$project";
Mark Radab6292752009-11-07 16:13:29 +01002312 if (open my $fd, '-|', git_cmd(), 'rev-parse',
2313 '--verify', '-q', @options, $hash) {
2314 $retval = <$fd>;
2315 chomp $retval if defined $retval;
Jakub Narebski717b8312006-07-31 21:22:15 +02002316 close $fd;
Jakub Narebski717b8312006-07-31 21:22:15 +02002317 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002318 if (defined $o_git_dir) {
2319 $git_dir = $o_git_dir;
Jakub Narebski717b8312006-07-31 21:22:15 +02002320 }
2321 return $retval;
2322}
2323
2324# get type of given object
2325sub git_get_type {
2326 my $hash = shift;
2327
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002328 open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02002329 my $type = <$fd>;
2330 close $fd or return;
2331 chomp $type;
2332 return $type;
2333}
2334
Jakub Narebskib2019272007-11-03 00:41:19 +01002335# repository configuration
2336our $config_file = '';
2337our %config;
2338
2339# store multiple values for single key as anonymous array reference
2340# single values stored directly in the hash, not as [ <value> ]
2341sub hash_set_multi {
2342 my ($hash, $key, $value) = @_;
2343
2344 if (!exists $hash->{$key}) {
2345 $hash->{$key} = $value;
2346 } elsif (!ref $hash->{$key}) {
2347 $hash->{$key} = [ $hash->{$key}, $value ];
2348 } else {
2349 push @{$hash->{$key}}, $value;
2350 }
2351}
2352
2353# return hash of git project configuration
2354# optionally limited to some section, e.g. 'gitweb'
2355sub git_parse_project_config {
2356 my $section_regexp = shift;
2357 my %config;
2358
2359 local $/ = "\0";
2360
2361 open my $fh, "-|", git_cmd(), "config", '-z', '-l',
2362 or return;
2363
2364 while (my $keyval = <$fh>) {
2365 chomp $keyval;
2366 my ($key, $value) = split(/\n/, $keyval, 2);
2367
2368 hash_set_multi(\%config, $key, $value)
2369 if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
2370 }
2371 close $fh;
2372
2373 return %config;
2374}
2375
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002376# convert config value to boolean: 'true' or 'false'
Jakub Narebskib2019272007-11-03 00:41:19 +01002377# no value, number > 0, 'true' and 'yes' values are true
2378# rest of values are treated as false (never as error)
2379sub config_to_bool {
2380 my $val = shift;
2381
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002382 return 1 if !defined $val; # section.key
2383
Jakub Narebskib2019272007-11-03 00:41:19 +01002384 # strip leading and trailing whitespace
2385 $val =~ s/^\s+//;
2386 $val =~ s/\s+$//;
2387
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002388 return (($val =~ /^\d+$/ && $val) || # section.key = 1
Jakub Narebskib2019272007-11-03 00:41:19 +01002389 ($val =~ /^(?:true|yes)$/i)); # section.key = true
2390}
2391
2392# convert config value to simple decimal number
2393# an optional value suffix of 'k', 'm', or 'g' will cause the value
2394# to be multiplied by 1024, 1048576, or 1073741824
2395sub config_to_int {
2396 my $val = shift;
2397
2398 # strip leading and trailing whitespace
2399 $val =~ s/^\s+//;
2400 $val =~ s/\s+$//;
2401
2402 if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
2403 $unit = lc($unit);
2404 # unknown unit is treated as 1
2405 return $num * ($unit eq 'g' ? 1073741824 :
2406 $unit eq 'm' ? 1048576 :
2407 $unit eq 'k' ? 1024 : 1);
2408 }
2409 return $val;
2410}
2411
2412# convert config value to array reference, if needed
2413sub config_to_multi {
2414 my $val = shift;
2415
Jakub Narebskid76a5852007-12-20 10:48:09 +01002416 return ref($val) ? $val : (defined($val) ? [ $val ] : []);
Jakub Narebskib2019272007-11-03 00:41:19 +01002417}
2418
Jakub Narebski717b8312006-07-31 21:22:15 +02002419sub git_get_project_config {
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05302420 my ($key, $type) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002421
Jakub Narebski7a49c252010-03-27 20:26:59 +01002422 return unless defined $git_dir;
Jakub Narebski9be36142010-03-01 22:51:34 +01002423
Jakub Narebskib2019272007-11-03 00:41:19 +01002424 # key sanity check
Jakub Narebski717b8312006-07-31 21:22:15 +02002425 return unless ($key);
2426 $key =~ s/^gitweb\.//;
2427 return if ($key =~ m/\W/);
2428
Jakub Narebskib2019272007-11-03 00:41:19 +01002429 # type sanity check
2430 if (defined $type) {
2431 $type =~ s/^--//;
2432 $type = undef
2433 unless ($type eq 'bool' || $type eq 'int');
2434 }
2435
2436 # get config
2437 if (!defined $config_file ||
2438 $config_file ne "$git_dir/config") {
2439 %config = git_parse_project_config('gitweb');
2440 $config_file = "$git_dir/config";
2441 }
2442
Marcel M. Carydf5d10a2009-02-18 14:09:41 +01002443 # check if config variable (key) exists
2444 return unless exists $config{"gitweb.$key"};
2445
Jakub Narebskib2019272007-11-03 00:41:19 +01002446 # ensure given type
2447 if (!defined $type) {
2448 return $config{"gitweb.$key"};
2449 } elsif ($type eq 'bool') {
2450 # backward compatibility: 'git config --bool' returns true/false
2451 return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
2452 } elsif ($type eq 'int') {
2453 return config_to_int($config{"gitweb.$key"});
2454 }
2455 return $config{"gitweb.$key"};
Jakub Narebski717b8312006-07-31 21:22:15 +02002456}
2457
Jakub Narebski717b8312006-07-31 21:22:15 +02002458# get hash of given path at given ref
2459sub git_get_hash_by_path {
2460 my $base = shift;
2461 my $path = shift || return undef;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002462 my $type = shift;
Jakub Narebski717b8312006-07-31 21:22:15 +02002463
Jakub Narebski4b02f482006-09-26 01:54:24 +02002464 $path =~ s,/+$,,;
Jakub Narebski717b8312006-07-31 21:22:15 +02002465
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002466 open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
Lea Wiemann074afaa2008-06-19 22:03:21 +02002467 or die_error(500, "Open git-ls-tree failed");
Jakub Narebski717b8312006-07-31 21:22:15 +02002468 my $line = <$fd>;
2469 close $fd or return undef;
2470
Jakub Narebski198a2a82007-05-12 21:16:34 +02002471 if (!defined $line) {
2472 # there is no tree or hash given by $path at $base
2473 return undef;
2474 }
2475
Jakub Narebski717b8312006-07-31 21:22:15 +02002476 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
Jakub Narebski8b4b94c2006-10-30 22:25:11 +01002477 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
Jakub Narebski1d782b02006-09-21 18:09:12 +02002478 if (defined $type && $type ne $2) {
2479 # type doesn't match
2480 return undef;
2481 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002482 return $3;
2483}
2484
Jakub Narebskied224de2007-05-07 01:10:04 +02002485# get path of entry with given hash at given tree-ish (ref)
2486# used to get 'from' filename for combined diff (merge commit) for renames
2487sub git_get_path_by_hash {
2488 my $base = shift || return;
2489 my $hash = shift || return;
2490
2491 local $/ = "\0";
2492
2493 open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
2494 or return undef;
2495 while (my $line = <$fd>) {
2496 chomp $line;
2497
2498 #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
2499 #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
2500 if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
2501 close $fd;
2502 return $1;
2503 }
2504 }
2505 close $fd;
2506 return undef;
2507}
2508
Jakub Narebski717b8312006-07-31 21:22:15 +02002509## ......................................................................
2510## git utility functions, directly accessing git repository
2511
Jakub Narebski847e01f2006-08-14 02:05:47 +02002512sub git_get_project_description {
Jakub Narebski717b8312006-07-31 21:22:15 +02002513 my $path = shift;
2514
Jakub Narebski0e121a22007-11-03 00:41:20 +01002515 $git_dir = "$projectroot/$path";
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002516 open my $fd, '<', "$git_dir/description"
Jakub Narebski0e121a22007-11-03 00:41:20 +01002517 or return git_get_project_config('description');
Jakub Narebski717b8312006-07-31 21:22:15 +02002518 my $descr = <$fd>;
2519 close $fd;
Junio C Hamano2eb54ef2007-05-16 21:04:16 -07002520 if (defined $descr) {
2521 chomp $descr;
2522 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002523 return $descr;
2524}
2525
Petr Baudisaed93de2008-10-02 17:13:02 +02002526sub git_get_project_ctags {
2527 my $path = shift;
2528 my $ctags = {};
2529
2530 $git_dir = "$projectroot/$path";
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002531 opendir my $dh, "$git_dir/ctags"
2532 or return $ctags;
2533 foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) {
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002534 open my $ct, '<', $_ or next;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002535 my $val = <$ct>;
Petr Baudisaed93de2008-10-02 17:13:02 +02002536 chomp $val;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002537 close $ct;
Petr Baudisaed93de2008-10-02 17:13:02 +02002538 my $ctag = $_; $ctag =~ s#.*/##;
2539 $ctags->{$ctag} = $val;
2540 }
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02002541 closedir $dh;
Petr Baudisaed93de2008-10-02 17:13:02 +02002542 $ctags;
2543}
2544
2545sub git_populate_project_tagcloud {
2546 my $ctags = shift;
2547
2548 # First, merge different-cased tags; tags vote on casing
2549 my %ctags_lc;
2550 foreach (keys %$ctags) {
2551 $ctags_lc{lc $_}->{count} += $ctags->{$_};
2552 if (not $ctags_lc{lc $_}->{topcount}
2553 or $ctags_lc{lc $_}->{topcount} < $ctags->{$_}) {
2554 $ctags_lc{lc $_}->{topcount} = $ctags->{$_};
2555 $ctags_lc{lc $_}->{topname} = $_;
2556 }
2557 }
2558
2559 my $cloud;
2560 if (eval { require HTML::TagCloud; 1; }) {
2561 $cloud = HTML::TagCloud->new;
2562 foreach (sort keys %ctags_lc) {
2563 # Pad the title with spaces so that the cloud looks
2564 # less crammed.
2565 my $title = $ctags_lc{$_}->{topname};
2566 $title =~ s/ /&nbsp;/g;
2567 $title =~ s/^/&nbsp;/g;
2568 $title =~ s/$/&nbsp;/g;
2569 $cloud->add($title, $home_link."?by_tag=".$_, $ctags_lc{$_}->{count});
2570 }
2571 } else {
2572 $cloud = \%ctags_lc;
2573 }
2574 $cloud;
2575}
2576
2577sub git_show_project_tagcloud {
2578 my ($cloud, $count) = @_;
2579 print STDERR ref($cloud)."..\n";
2580 if (ref $cloud eq 'HTML::TagCloud') {
2581 return $cloud->html_and_css($count);
2582 } else {
2583 my @tags = sort { $cloud->{$a}->{count} <=> $cloud->{$b}->{count} } keys %$cloud;
2584 return '<p align="center">' . join (', ', map {
2585 "<a href=\"$home_link?by_tag=$_\">$cloud->{$_}->{topname}</a>"
2586 } splice(@tags, 0, $count)) . '</p>';
2587 }
2588}
2589
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002590sub git_get_project_url_list {
2591 my $path = shift;
2592
Jakub Narebski0e121a22007-11-03 00:41:20 +01002593 $git_dir = "$projectroot/$path";
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002594 open my $fd, '<', "$git_dir/cloneurl"
Jakub Narebski0e121a22007-11-03 00:41:20 +01002595 or return wantarray ?
2596 @{ config_to_multi(git_get_project_config('url')) } :
2597 config_to_multi(git_get_project_config('url'));
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02002598 my @git_project_url_list = map { chomp; $_ } <$fd>;
2599 close $fd;
2600
2601 return wantarray ? @git_project_url_list : \@git_project_url_list;
2602}
2603
Jakub Narebski847e01f2006-08-14 02:05:47 +02002604sub git_get_projects_list {
Petr Baudise30496d2006-10-24 05:33:17 +02002605 my ($filter) = @_;
Jakub Narebski717b8312006-07-31 21:22:15 +02002606 my @list;
2607
Petr Baudise30496d2006-10-24 05:33:17 +02002608 $filter ||= '';
2609 $filter =~ s/\.git$//;
2610
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08002611 my $check_forks = gitweb_check_feature('forks');
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002612
Jakub Narebski717b8312006-07-31 21:22:15 +02002613 if (-d $projects_list) {
2614 # search in directory
Petr Baudise30496d2006-10-24 05:33:17 +02002615 my $dir = $projects_list . ($filter ? "/$filter" : '');
Aneesh Kumar K.V6768d6b2006-11-03 10:41:45 +05302616 # remove the trailing "/"
2617 $dir =~ s!/+$!!;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002618 my $pfxlen = length("$dir");
Luke Luca5e9492007-10-16 20:45:25 -07002619 my $pfxdepth = ($dir =~ tr!/!!);
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002620
2621 File::Find::find({
2622 follow_fast => 1, # follow symbolic links
Junio C Hamanod20602e2007-07-27 01:23:03 -07002623 follow_skip => 2, # ignore duplicates
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002624 dangling_symlinks => 0, # ignore dangling symlinks, silently
2625 wanted => sub {
Jakub Narebskiee1d8ee2010-04-30 18:30:31 +02002626 # global variables
2627 our $project_maxdepth;
2628 our $projectroot;
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002629 # skip project-list toplevel, if we get it.
2630 return if (m!^[/.]$!);
2631 # only directories can be git repositories
2632 return unless (-d $_);
Luke Luca5e9492007-10-16 20:45:25 -07002633 # don't traverse too deep (Find is super slow on os x)
2634 if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
2635 $File::Find::prune = 1;
2636 return;
2637 }
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002638
2639 my $subdir = substr($File::Find::name, $pfxlen + 1);
2640 # we check related file in $projectroot
Devin Doucettefb3bb3d2008-12-27 02:39:31 -07002641 my $path = ($filter ? "$filter/" : '') . $subdir;
2642 if (check_export_ok("$projectroot/$path")) {
2643 push @list, { path => $path };
Jakub Narebskic0011ff2006-09-14 22:18:59 +02002644 $File::Find::prune = 1;
2645 }
2646 },
2647 }, "$dir");
2648
Jakub Narebski717b8312006-07-31 21:22:15 +02002649 } elsif (-f $projects_list) {
2650 # read from file(url-encoded):
2651 # 'git%2Fgit.git Linus+Torvalds'
2652 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2653 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002654 my %paths;
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002655 open my $fd, '<', $projects_list or return;
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002656 PROJECT:
Jakub Narebski717b8312006-07-31 21:22:15 +02002657 while (my $line = <$fd>) {
2658 chomp $line;
2659 my ($path, $owner) = split ' ', $line;
2660 $path = unescape($path);
2661 $owner = unescape($owner);
2662 if (!defined $path) {
2663 next;
2664 }
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002665 if ($filter ne '') {
2666 # looking for forks;
2667 my $pfx = substr($path, 0, length($filter));
2668 if ($pfx ne $filter) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002669 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002670 }
2671 my $sfx = substr($path, length($filter));
2672 if ($sfx !~ /^\/.*\.git$/) {
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002673 next PROJECT;
2674 }
2675 } elsif ($check_forks) {
2676 PATH:
2677 foreach my $filter (keys %paths) {
2678 # looking for forks;
2679 my $pfx = substr($path, 0, length($filter));
2680 if ($pfx ne $filter) {
2681 next PATH;
2682 }
2683 my $sfx = substr($path, length($filter));
2684 if ($sfx !~ /^\/.*\.git$/) {
2685 next PATH;
2686 }
2687 # is a fork, don't include it in
2688 # the list
2689 next PROJECT;
Junio C Hamano83ee94c2006-11-07 22:37:17 -08002690 }
2691 }
Junio C Hamano2172ce42006-10-03 02:30:47 -07002692 if (check_export_ok("$projectroot/$path")) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002693 my $pr = {
2694 path => $path,
Martin Koegler00f429a2007-06-03 17:42:44 +02002695 owner => to_utf8($owner),
Jakub Narebski717b8312006-07-31 21:22:15 +02002696 };
Frank Lichtenheldc2b8b132007-04-06 23:58:11 +02002697 push @list, $pr;
2698 (my $forks_path = $path) =~ s/\.git$//;
2699 $paths{$forks_path}++;
Jakub Narebski717b8312006-07-31 21:22:15 +02002700 }
2701 }
2702 close $fd;
2703 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002704 return @list;
2705}
2706
Junio C Hamano47852452007-07-03 22:10:42 -07002707our $gitweb_project_owner = undef;
2708sub git_get_project_list_from_file {
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002709
Junio C Hamano47852452007-07-03 22:10:42 -07002710 return if (defined $gitweb_project_owner);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002711
Junio C Hamano47852452007-07-03 22:10:42 -07002712 $gitweb_project_owner = {};
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002713 # read from file (url-encoded):
2714 # 'git%2Fgit.git Linus+Torvalds'
2715 # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
2716 # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
2717 if (-f $projects_list) {
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02002718 open(my $fd, '<', $projects_list);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002719 while (my $line = <$fd>) {
2720 chomp $line;
2721 my ($pr, $ow) = split ' ', $line;
2722 $pr = unescape($pr);
2723 $ow = unescape($ow);
Junio C Hamano47852452007-07-03 22:10:42 -07002724 $gitweb_project_owner->{$pr} = to_utf8($ow);
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002725 }
2726 close $fd;
2727 }
Junio C Hamano47852452007-07-03 22:10:42 -07002728}
2729
2730sub git_get_project_owner {
2731 my $project = shift;
2732 my $owner;
2733
2734 return undef unless $project;
Bruno Ribasb59012e2008-02-08 14:38:04 -02002735 $git_dir = "$projectroot/$project";
Junio C Hamano47852452007-07-03 22:10:42 -07002736
2737 if (!defined $gitweb_project_owner) {
2738 git_get_project_list_from_file();
2739 }
2740
2741 if (exists $gitweb_project_owner->{$project}) {
2742 $owner = $gitweb_project_owner->{$project};
2743 }
Bruno Ribasb59012e2008-02-08 14:38:04 -02002744 if (!defined $owner){
2745 $owner = git_get_project_config('owner');
2746 }
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002747 if (!defined $owner) {
Bruno Ribasb59012e2008-02-08 14:38:04 -02002748 $owner = get_file_owner("$git_dir");
Jakub Narebski1e0cf032006-08-14 02:10:06 +02002749 }
2750
2751 return $owner;
2752}
2753
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002754sub git_get_last_activity {
2755 my ($path) = @_;
2756 my $fd;
2757
2758 $git_dir = "$projectroot/$path";
2759 open($fd, "-|", git_cmd(), 'for-each-ref',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00002760 '--format=%(committer)',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002761 '--sort=-committerdate',
Robert Fitzsimons0ff5ec72006-12-22 19:38:13 +00002762 '--count=1',
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002763 'refs/heads') or return;
2764 my $most_recent = <$fd>;
2765 close $fd or return;
Jakub Narebski785cdea2007-05-13 12:39:22 +02002766 if (defined $most_recent &&
2767 $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002768 my $timestamp = $1;
2769 my $age = time - $timestamp;
2770 return ($age, age_string($age));
2771 }
Matt McCutchenc9563952007-06-28 18:15:22 -04002772 return (undef, undef);
Jakub Narebskic60c56c2006-10-28 19:43:40 +02002773}
2774
Jakub Narebski847e01f2006-08-14 02:05:47 +02002775sub git_get_references {
Jakub Narebski717b8312006-07-31 21:22:15 +02002776 my $type = shift || "";
2777 my %refs;
Jakub Narebski28b9d9f2006-11-25 11:32:08 +01002778 # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
2779 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
2780 open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
2781 ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
Jakub Narebski9704d752006-09-19 14:31:49 +02002782 or return;
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002783
Jakub Narebski717b8312006-07-31 21:22:15 +02002784 while (my $line = <$fd>) {
2785 chomp $line;
Giuseppe Bilotta4afbaef2008-09-02 21:47:05 +02002786 if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002787 if (defined $refs{$1}) {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002788 push @{$refs{$1}}, $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02002789 } else {
Jakub Narebskid294e1c2006-08-14 02:14:20 +02002790 $refs{$1} = [ $2 ];
Jakub Narebski717b8312006-07-31 21:22:15 +02002791 }
2792 }
2793 }
2794 close $fd or return;
2795 return \%refs;
2796}
2797
Jakub Narebski56a322f2006-08-24 19:41:23 +02002798sub git_get_rev_name_tags {
2799 my $hash = shift || return undef;
2800
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002801 open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
Jakub Narebski56a322f2006-08-24 19:41:23 +02002802 or return;
2803 my $name_rev = <$fd>;
2804 close $fd;
2805
2806 if ($name_rev =~ m|^$hash tags/(.*)$|) {
2807 return $1;
2808 } else {
2809 # catches also '$hash undefined' output
2810 return undef;
2811 }
2812}
2813
Jakub Narebski717b8312006-07-31 21:22:15 +02002814## ----------------------------------------------------------------------
2815## parse to hash functions
2816
Jakub Narebski847e01f2006-08-14 02:05:47 +02002817sub parse_date {
Jakub Narebski717b8312006-07-31 21:22:15 +02002818 my $epoch = shift;
2819 my $tz = shift || "-0000";
2820
2821 my %date;
2822 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
2823 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
2824 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
2825 $date{'hour'} = $hour;
2826 $date{'minute'} = $min;
2827 $date{'mday'} = $mday;
2828 $date{'day'} = $days[$wday];
2829 $date{'month'} = $months[$mon];
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002830 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
2831 $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
Jakub Narebski952c65f2006-08-22 16:52:50 +02002832 $date{'mday-time'} = sprintf "%d %s %02d:%02d",
2833 $mday, $months[$mon], $hour ,$min;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002834 $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
Vincent Zanottia62d6d82007-11-10 19:55:27 +01002835 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
Jakub Narebski717b8312006-07-31 21:22:15 +02002836
2837 $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
2838 my $local = $epoch + ((int $1 + ($2/60)) * 3600);
2839 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
2840 $date{'hour_local'} = $hour;
2841 $date{'minute_local'} = $min;
2842 $date{'tz_local'} = $tz;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01002843 $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
2844 1900+$year, $mon+1, $mday,
2845 $hour, $min, $sec, $tz);
Jakub Narebski717b8312006-07-31 21:22:15 +02002846 return %date;
2847}
2848
Jakub Narebski847e01f2006-08-14 02:05:47 +02002849sub parse_tag {
Jakub Narebski717b8312006-07-31 21:22:15 +02002850 my $tag_id = shift;
2851 my %tag;
2852 my @comment;
2853
Dennis Stosberg25691fb2006-08-28 17:49:58 +02002854 open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
Jakub Narebski717b8312006-07-31 21:22:15 +02002855 $tag{'id'} = $tag_id;
2856 while (my $line = <$fd>) {
2857 chomp $line;
2858 if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
2859 $tag{'object'} = $1;
2860 } elsif ($line =~ m/^type (.+)$/) {
2861 $tag{'type'} = $1;
2862 } elsif ($line =~ m/^tag (.+)$/) {
2863 $tag{'name'} = $1;
2864 } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
2865 $tag{'author'} = $1;
Giuseppe Bilottaba924732009-06-30 00:00:50 +02002866 $tag{'author_epoch'} = $2;
2867 $tag{'author_tz'} = $3;
2868 if ($tag{'author'} =~ m/^([^<]+) <([^>]*)>/) {
2869 $tag{'author_name'} = $1;
2870 $tag{'author_email'} = $2;
2871 } else {
2872 $tag{'author_name'} = $tag{'author'};
2873 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002874 } elsif ($line =~ m/--BEGIN/) {
2875 push @comment, $line;
2876 last;
2877 } elsif ($line eq "") {
2878 last;
2879 }
2880 }
2881 push @comment, <$fd>;
2882 $tag{'comment'} = \@comment;
2883 close $fd or return;
2884 if (!defined $tag{'name'}) {
2885 return
2886 };
2887 return %tag
2888}
2889
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002890sub parse_commit_text {
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002891 my ($commit_text, $withparents) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002892 my @commit_lines = split '\n', $commit_text;
Jakub Narebski717b8312006-07-31 21:22:15 +02002893 my %co;
2894
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002895 pop @commit_lines; # Remove '\0'
2896
Jakub Narebski198a2a82007-05-12 21:16:34 +02002897 if (! @commit_lines) {
2898 return;
2899 }
2900
Jakub Narebski717b8312006-07-31 21:22:15 +02002901 my $header = shift @commit_lines;
Jakub Narebski198a2a82007-05-12 21:16:34 +02002902 if ($header !~ m/^[0-9a-fA-F]{40}/) {
Jakub Narebski717b8312006-07-31 21:22:15 +02002903 return;
2904 }
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002905 ($co{'id'}, my @parents) = split ' ', $header;
Jakub Narebski717b8312006-07-31 21:22:15 +02002906 while (my $line = shift @commit_lines) {
2907 last if $line eq "\n";
2908 if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
2909 $co{'tree'} = $1;
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002910 } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002911 push @parents, $1;
Jakub Narebski717b8312006-07-31 21:22:15 +02002912 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02002913 $co{'author'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02002914 $co{'author_epoch'} = $2;
2915 $co{'author_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002916 if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
2917 $co{'author_name'} = $1;
2918 $co{'author_email'} = $2;
Jakub Narebski717b8312006-07-31 21:22:15 +02002919 } else {
2920 $co{'author_name'} = $co{'author'};
2921 }
2922 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
Zoltán Füzesi5ed5bbc2009-08-02 09:42:24 +02002923 $co{'committer'} = to_utf8($1);
Jakub Narebski717b8312006-07-31 21:22:15 +02002924 $co{'committer_epoch'} = $2;
2925 $co{'committer_tz'} = $3;
Jakub Narebskiba00b8c2006-11-25 15:54:32 +01002926 if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
2927 $co{'committer_name'} = $1;
2928 $co{'committer_email'} = $2;
2929 } else {
2930 $co{'committer_name'} = $co{'committer'};
2931 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002932 }
2933 }
2934 if (!defined $co{'tree'}) {
2935 return;
2936 };
Robert Fitzsimons208b2df2006-12-24 14:31:43 +00002937 $co{'parents'} = \@parents;
2938 $co{'parent'} = $parents[0];
Jakub Narebski717b8312006-07-31 21:22:15 +02002939
2940 foreach my $title (@commit_lines) {
2941 $title =~ s/^ //;
2942 if ($title ne "") {
2943 $co{'title'} = chop_str($title, 80, 5);
2944 # remove leading stuff of merges to make the interesting part visible
2945 if (length($title) > 50) {
2946 $title =~ s/^Automatic //;
2947 $title =~ s/^merge (of|with) /Merge ... /i;
2948 if (length($title) > 50) {
2949 $title =~ s/(http|rsync):\/\///;
2950 }
2951 if (length($title) > 50) {
2952 $title =~ s/(master|www|rsync)\.//;
2953 }
2954 if (length($title) > 50) {
2955 $title =~ s/kernel.org:?//;
2956 }
2957 if (length($title) > 50) {
2958 $title =~ s/\/pub\/scm//;
2959 }
2960 }
2961 $co{'title_short'} = chop_str($title, 50, 5);
2962 last;
2963 }
2964 }
Joey Hess53c39672008-09-05 14:26:29 -04002965 if (! defined $co{'title'} || $co{'title'} eq "") {
Petr Baudis7e0fe5c2006-10-06 18:55:04 +02002966 $co{'title'} = $co{'title_short'} = '(no commit message)';
2967 }
Jakub Narebski717b8312006-07-31 21:22:15 +02002968 # remove added spaces
2969 foreach my $line (@commit_lines) {
2970 $line =~ s/^ //;
2971 }
2972 $co{'comment'} = \@commit_lines;
2973
2974 my $age = time - $co{'committer_epoch'};
2975 $co{'age'} = $age;
2976 $co{'age_string'} = age_string($age);
2977 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
2978 if ($age > 60*60*24*7*2) {
2979 $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2980 $co{'age_string_age'} = $co{'age_string'};
2981 } else {
2982 $co{'age_string_date'} = $co{'age_string'};
2983 $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
2984 }
2985 return %co;
2986}
2987
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002988sub parse_commit {
2989 my ($commit_id) = @_;
2990 my %co;
2991
2992 local $/ = "\0";
2993
2994 open my $fd, "-|", git_cmd(), "rev-list",
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00002995 "--parents",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002996 "--header",
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00002997 "--max-count=1",
2998 $commit_id,
2999 "--",
Lea Wiemann074afaa2008-06-19 22:03:21 +02003000 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimonsccdfdea2006-12-27 14:22:21 +00003001 %co = parse_commit_text(<$fd>, 1);
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003002 close $fd;
3003
3004 return %co;
3005}
3006
3007sub parse_commits {
Jakub Narebski311e5522008-02-26 13:22:06 +01003008 my ($commit_id, $maxcount, $skip, $filename, @args) = @_;
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003009 my @cos;
3010
3011 $maxcount ||= 1;
3012 $skip ||= 0;
3013
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003014 local $/ = "\0";
3015
3016 open my $fd, "-|", git_cmd(), "rev-list",
3017 "--header",
Jakub Narebski311e5522008-02-26 13:22:06 +01003018 @args,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003019 ("--max-count=" . $maxcount),
Robert Fitzsimonsf47efbb2006-12-24 14:31:49 +00003020 ("--skip=" . $skip),
Miklos Vajna868bc062007-07-12 20:39:27 +02003021 @extra_options,
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003022 $commit_id,
3023 "--",
3024 ($filename ? ($filename) : ())
Lea Wiemann074afaa2008-06-19 22:03:21 +02003025 or die_error(500, "Open git-rev-list failed");
Robert Fitzsimons756bbf52006-12-24 14:31:42 +00003026 while (my $line = <$fd>) {
3027 my %co = parse_commit_text($line);
3028 push @cos, \%co;
3029 }
3030 close $fd;
3031
3032 return wantarray ? @cos : \@cos;
3033}
3034
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003035# parse line of git-diff-tree "raw" output
Jakub Narebski740e67f2006-08-21 23:07:00 +02003036sub parse_difftree_raw_line {
3037 my $line = shift;
3038 my %res;
3039
3040 # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
3041 # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
3042 if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
3043 $res{'from_mode'} = $1;
3044 $res{'to_mode'} = $2;
3045 $res{'from_id'} = $3;
3046 $res{'to_id'} = $4;
Jakub Narebski4ed4a342008-04-05 21:13:24 +01003047 $res{'status'} = $5;
Jakub Narebski740e67f2006-08-21 23:07:00 +02003048 $res{'similarity'} = $6;
3049 if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02003050 ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02003051 } else {
Jakub Narebski9d301452007-11-01 12:38:08 +01003052 $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
Jakub Narebski740e67f2006-08-21 23:07:00 +02003053 }
3054 }
Jakub Narebski78bc4032007-05-07 01:10:03 +02003055 # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
3056 # combined diff (for merge commit)
3057 elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
3058 $res{'nparents'} = length($1);
3059 $res{'from_mode'} = [ split(' ', $2) ];
3060 $res{'to_mode'} = pop @{$res{'from_mode'}};
3061 $res{'from_id'} = [ split(' ', $3) ];
3062 $res{'to_id'} = pop @{$res{'from_id'}};
3063 $res{'status'} = [ split('', $4) ];
3064 $res{'to_file'} = unquote($5);
3065 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02003066 # 'c512b523472485aef4fff9e57b229d9d243c967f'
Jakub Narebski0edcb372006-08-31 00:36:04 +02003067 elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
3068 $res{'commit'} = $1;
3069 }
Jakub Narebski740e67f2006-08-21 23:07:00 +02003070
3071 return wantarray ? %res : \%res;
3072}
3073
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003074# wrapper: return parsed line of git-diff-tree "raw" output
3075# (the argument might be raw line, or parsed info)
3076sub parsed_difftree_line {
3077 my $line_or_ref = shift;
3078
3079 if (ref($line_or_ref) eq "HASH") {
3080 # pre-parsed (or generated by hand)
3081 return $line_or_ref;
3082 } else {
3083 return parse_difftree_raw_line($line_or_ref);
3084 }
3085}
3086
Jakub Narebskicb849b42006-08-31 00:32:15 +02003087# parse line of git-ls-tree output
Jakub Narebski74fd8722009-05-07 19:11:29 +02003088sub parse_ls_tree_line {
Jakub Narebskicb849b42006-08-31 00:32:15 +02003089 my $line = shift;
3090 my %opts = @_;
3091 my %res;
3092
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003093 if ($opts{'-l'}) {
3094 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa 16717 panic.c'
3095 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40}) +(-|[0-9]+)\t(.+)$/s;
Jakub Narebskicb849b42006-08-31 00:32:15 +02003096
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003097 $res{'mode'} = $1;
3098 $res{'type'} = $2;
3099 $res{'hash'} = $3;
3100 $res{'size'} = $4;
3101 if ($opts{'-z'}) {
3102 $res{'name'} = $5;
3103 } else {
3104 $res{'name'} = unquote($5);
3105 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02003106 } else {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003107 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
3108 $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
3109
3110 $res{'mode'} = $1;
3111 $res{'type'} = $2;
3112 $res{'hash'} = $3;
3113 if ($opts{'-z'}) {
3114 $res{'name'} = $4;
3115 } else {
3116 $res{'name'} = unquote($4);
3117 }
Jakub Narebskicb849b42006-08-31 00:32:15 +02003118 }
3119
3120 return wantarray ? %res : \%res;
3121}
3122
Jakub Narebski90921742007-06-08 13:27:42 +02003123# generates _two_ hashes, references to which are passed as 2 and 3 argument
3124sub parse_from_to_diffinfo {
3125 my ($diffinfo, $from, $to, @parents) = @_;
3126
3127 if ($diffinfo->{'nparents'}) {
3128 # combined diff
3129 $from->{'file'} = [];
3130 $from->{'href'} = [];
3131 fill_from_file_info($diffinfo, @parents)
3132 unless exists $diffinfo->{'from_file'};
3133 for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
Jakub Narebski9d301452007-11-01 12:38:08 +01003134 $from->{'file'}[$i] =
3135 defined $diffinfo->{'from_file'}[$i] ?
3136 $diffinfo->{'from_file'}[$i] :
3137 $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003138 if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
3139 $from->{'href'}[$i] = href(action=>"blob",
3140 hash_base=>$parents[$i],
3141 hash=>$diffinfo->{'from_id'}[$i],
3142 file_name=>$from->{'file'}[$i]);
3143 } else {
3144 $from->{'href'}[$i] = undef;
3145 }
3146 }
3147 } else {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01003148 # ordinary (not combined) diff
Jakub Narebski9d301452007-11-01 12:38:08 +01003149 $from->{'file'} = $diffinfo->{'from_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003150 if ($diffinfo->{'status'} ne "A") { # not new (added) file
3151 $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
3152 hash=>$diffinfo->{'from_id'},
3153 file_name=>$from->{'file'});
3154 } else {
3155 delete $from->{'href'};
3156 }
3157 }
3158
Jakub Narebski9d301452007-11-01 12:38:08 +01003159 $to->{'file'} = $diffinfo->{'to_file'};
Jakub Narebski90921742007-06-08 13:27:42 +02003160 if (!is_deleted($diffinfo)) { # file exists in result
3161 $to->{'href'} = href(action=>"blob", hash_base=>$hash,
3162 hash=>$diffinfo->{'to_id'},
3163 file_name=>$to->{'file'});
3164 } else {
3165 delete $to->{'href'};
3166 }
3167}
3168
Jakub Narebski717b8312006-07-31 21:22:15 +02003169## ......................................................................
3170## parse to array of hashes functions
3171
Jakub Narebskicd146402006-11-02 20:23:11 +01003172sub git_get_heads_list {
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003173 my ($limit, @classes) = @_;
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01003174 @classes = ('heads') unless @classes;
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003175 my @patterns = map { "refs/$_" } @classes;
Jakub Narebskicd146402006-11-02 20:23:11 +01003176 my @headslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003177
Jakub Narebskicd146402006-11-02 20:23:11 +01003178 open my $fd, '-|', git_cmd(), 'for-each-ref',
3179 ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
3180 '--format=%(objectname) %(refname) %(subject)%00%(committer)',
Giuseppe Bilotta9b3f3de2010-11-11 13:26:10 +01003181 @patterns
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003182 or return;
3183 while (my $line = <$fd>) {
Jakub Narebskicd146402006-11-02 20:23:11 +01003184 my %ref_item;
Jakub Narebski120ddde2006-09-19 14:33:22 +02003185
Jakub Narebskicd146402006-11-02 20:23:11 +01003186 chomp $line;
3187 my ($refinfo, $committerinfo) = split(/\0/, $line);
3188 my ($hash, $name, $title) = split(' ', $refinfo, 3);
3189 my ($committer, $epoch, $tz) =
3190 ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01003191 $ref_item{'fullname'} = $name;
Giuseppe Bilotta60efa242010-11-11 13:26:09 +01003192 $name =~ s!^refs/(?:head|remote)s/!!;
Jakub Narebskicd146402006-11-02 20:23:11 +01003193
3194 $ref_item{'name'} = $name;
3195 $ref_item{'id'} = $hash;
3196 $ref_item{'title'} = $title || '(no commit message)';
3197 $ref_item{'epoch'} = $epoch;
3198 if ($epoch) {
3199 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
3200 } else {
3201 $ref_item{'age'} = "unknown";
Jakub Narebski717b8312006-07-31 21:22:15 +02003202 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003203
3204 push @headslist, \%ref_item;
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003205 }
3206 close $fd;
Junio C Hamano7a13b992006-07-31 19:18:34 -07003207
Jakub Narebskicd146402006-11-02 20:23:11 +01003208 return wantarray ? @headslist : \@headslist;
3209}
Jakub Narebskic83a77e2006-09-15 03:43:28 +02003210
Jakub Narebskicd146402006-11-02 20:23:11 +01003211sub git_get_tags_list {
3212 my $limit = shift;
3213 my @tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003214
Jakub Narebskicd146402006-11-02 20:23:11 +01003215 open my $fd, '-|', git_cmd(), 'for-each-ref',
3216 ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
3217 '--format=%(objectname) %(objecttype) %(refname) '.
3218 '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
3219 'refs/tags'
3220 or return;
3221 while (my $line = <$fd>) {
3222 my %ref_item;
3223
3224 chomp $line;
3225 my ($refinfo, $creatorinfo) = split(/\0/, $line);
3226 my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
3227 my ($creator, $epoch, $tz) =
3228 ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
Jakub Narebskibf901f82007-12-15 15:40:28 +01003229 $ref_item{'fullname'} = $name;
Jakub Narebskicd146402006-11-02 20:23:11 +01003230 $name =~ s!^refs/tags/!!;
3231
3232 $ref_item{'type'} = $type;
3233 $ref_item{'id'} = $id;
3234 $ref_item{'name'} = $name;
3235 if ($type eq "tag") {
3236 $ref_item{'subject'} = $title;
3237 $ref_item{'reftype'} = $reftype;
3238 $ref_item{'refid'} = $refid;
3239 } else {
3240 $ref_item{'reftype'} = $type;
3241 $ref_item{'refid'} = $id;
3242 }
3243
3244 if ($type eq "tag" || $type eq "commit") {
3245 $ref_item{'epoch'} = $epoch;
3246 if ($epoch) {
3247 $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
3248 } else {
3249 $ref_item{'age'} = "unknown";
3250 }
3251 }
3252
3253 push @tagslist, \%ref_item;
Jakub Narebski717b8312006-07-31 21:22:15 +02003254 }
Jakub Narebskicd146402006-11-02 20:23:11 +01003255 close $fd;
3256
3257 return wantarray ? @tagslist : \@tagslist;
Jakub Narebski717b8312006-07-31 21:22:15 +02003258}
3259
3260## ----------------------------------------------------------------------
3261## filesystem-related functions
3262
3263sub get_file_owner {
3264 my $path = shift;
3265
3266 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
3267 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
3268 if (!defined $gcos) {
3269 return undef;
3270 }
3271 my $owner = $gcos;
3272 $owner =~ s/[,;].*$//;
Martin Koegler00f429a2007-06-03 17:42:44 +02003273 return to_utf8($owner);
Jakub Narebski717b8312006-07-31 21:22:15 +02003274}
3275
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003276# assume that file exists
3277sub insert_file {
3278 my $filename = shift;
3279
3280 open my $fd, '<', $filename;
Jakub Narebski45868642008-12-08 14:13:21 +01003281 print map { to_utf8($_) } <$fd>;
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003282 close $fd;
3283}
3284
Jakub Narebski717b8312006-07-31 21:22:15 +02003285## ......................................................................
3286## mimetype related functions
3287
3288sub mimetype_guess_file {
3289 my $filename = shift;
3290 my $mimemap = shift;
3291 -r $mimemap or return undef;
3292
3293 my %mimemap;
Jakub Narebskidff2b6d2009-05-10 02:38:34 +02003294 open(my $mh, '<', $mimemap) or return undef;
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003295 while (<$mh>) {
Jakub Narebski618918e2006-08-14 02:15:22 +02003296 next if m/^#/; # skip comments
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003297 my ($mimetype, $exts) = split(/\t+/);
Junio C Hamano46b059d2006-07-31 19:24:37 -07003298 if (defined $exts) {
3299 my @exts = split(/\s+/, $exts);
3300 foreach my $ext (@exts) {
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003301 $mimemap{$ext} = $mimetype;
Junio C Hamano46b059d2006-07-31 19:24:37 -07003302 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003303 }
3304 }
Jakub Narebskiad87e4f2009-05-11 03:21:06 +02003305 close($mh);
Jakub Narebski717b8312006-07-31 21:22:15 +02003306
Jakub Narebski80593192006-09-19 13:57:03 +02003307 $filename =~ /\.([^.]*)$/;
Jakub Narebski717b8312006-07-31 21:22:15 +02003308 return $mimemap{$1};
3309}
3310
3311sub mimetype_guess {
3312 my $filename = shift;
3313 my $mime;
3314 $filename =~ /\./ or return undef;
3315
3316 if ($mimetypes_file) {
3317 my $file = $mimetypes_file;
Jakub Narebskid5aa50d2006-08-14 02:16:33 +02003318 if ($file !~ m!^/!) { # if it is relative path
3319 # it is relative to project
3320 $file = "$projectroot/$project/$file";
3321 }
Jakub Narebski717b8312006-07-31 21:22:15 +02003322 $mime = mimetype_guess_file($filename, $file);
3323 }
3324 $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
3325 return $mime;
3326}
3327
Jakub Narebski847e01f2006-08-14 02:05:47 +02003328sub blob_mimetype {
Jakub Narebski717b8312006-07-31 21:22:15 +02003329 my $fd = shift;
3330 my $filename = shift;
3331
3332 if ($filename) {
3333 my $mime = mimetype_guess($filename);
3334 $mime and return $mime;
3335 }
3336
3337 # just in case
3338 return $default_blob_plain_mimetype unless $fd;
3339
3340 if (-T $fd) {
Jakub Narebski7f718e82008-06-03 16:47:10 +02003341 return 'text/plain';
Jakub Narebski717b8312006-07-31 21:22:15 +02003342 } elsif (! $filename) {
3343 return 'application/octet-stream';
3344 } elsif ($filename =~ m/\.png$/i) {
3345 return 'image/png';
3346 } elsif ($filename =~ m/\.gif$/i) {
3347 return 'image/gif';
3348 } elsif ($filename =~ m/\.jpe?g$/i) {
3349 return 'image/jpeg';
3350 } else {
3351 return 'application/octet-stream';
3352 }
3353}
3354
Jakub Narebski7f718e82008-06-03 16:47:10 +02003355sub blob_contenttype {
3356 my ($fd, $file_name, $type) = @_;
3357
3358 $type ||= blob_mimetype($fd, $file_name);
3359 if ($type eq 'text/plain' && defined $default_text_plain_charset) {
3360 $type .= "; charset=$default_text_plain_charset";
3361 }
3362
3363 return $type;
3364}
3365
Jakub Narebski592ea412010-04-27 21:34:45 +02003366# guess file syntax for syntax highlighting; return undef if no highlighting
3367# the name of syntax can (in the future) depend on syntax highlighter used
3368sub guess_file_syntax {
3369 my ($highlight, $mimetype, $file_name) = @_;
3370 return undef unless ($highlight && defined $file_name);
Jakub Narebski592ea412010-04-27 21:34:45 +02003371 my $basename = basename($file_name, '.in');
3372 return $highlight_basename{$basename}
3373 if exists $highlight_basename{$basename};
3374
3375 $basename =~ /\.([^.]*)$/;
3376 my $ext = $1 or return undef;
3377 return $highlight_ext{$ext}
3378 if exists $highlight_ext{$ext};
3379
3380 return undef;
3381}
3382
3383# run highlighter and return FD of its output,
3384# or return original FD if no highlighting
3385sub run_highlighter {
3386 my ($fd, $highlight, $syntax) = @_;
3387 return $fd unless ($highlight && defined $syntax);
3388
3389 close $fd
3390 or die_error(404, "Reading blob failed");
3391 open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
Christopher Wilson7ce896b2010-09-21 00:25:19 -07003392 quote_command($highlight_bin).
3393 " --xhtml --fragment --syntax $syntax |"
Jakub Narebski592ea412010-04-27 21:34:45 +02003394 or die_error(500, "Couldn't open file or run syntax highlighter");
3395 return $fd;
3396}
3397
Jakub Narebski717b8312006-07-31 21:22:15 +02003398## ======================================================================
3399## functions printing HTML: header, footer, error page
3400
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02003401sub get_page_title {
3402 my $title = to_utf8($site_name);
3403
3404 return $title unless (defined $project);
3405 $title .= " - " . to_utf8($project);
3406
3407 return $title unless (defined $action);
3408 $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
3409
3410 return $title unless (defined $file_name);
3411 $title .= " - " . esc_path($file_name);
3412 if ($action eq "tree" && $file_name !~ m|/$|) {
3413 $title .= "/";
3414 }
3415
3416 return $title;
3417}
3418
Kay Sievers12a88f22005-08-07 20:02:47 +02003419sub git_header_html {
Kay Sieversa59d4af2005-08-07 20:15:44 +02003420 my $status = shift || "200 OK";
Kay Sievers11044292005-10-19 03:18:45 +02003421 my $expires = shift;
Jakub Narebski7a597452010-04-24 16:00:04 +02003422 my %opts = @_;
Kay Sieversa59d4af2005-08-07 20:15:44 +02003423
Jakub Narebskiefb2d0c2010-04-24 16:01:10 +02003424 my $title = get_page_title();
Alp Tokerf6801d62006-07-11 11:19:34 +01003425 my $content_type;
3426 # require explicit support from the UA if we are to send the page as
3427 # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
3428 # we have to do this because MSIE sometimes globs '*/*', pretending to
3429 # support xhtml+xml but choking when it gets what it asked for.
Jakub Narebski952c65f2006-08-22 16:52:50 +02003430 if (defined $cgi->http('HTTP_ACCEPT') &&
3431 $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
3432 $cgi->Accept('application/xhtml+xml') != 0) {
Alp Tokerf6801d62006-07-11 11:19:34 +01003433 $content_type = 'application/xhtml+xml';
3434 } else {
3435 $content_type = 'text/html';
3436 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02003437 print $cgi->header(-type=>$content_type, -charset => 'utf-8',
Jakub Narebski7a597452010-04-24 16:00:04 +02003438 -status=> $status, -expires => $expires)
Jakub Narebskiad709ea2010-06-13 00:35:59 +02003439 unless ($opts{'-no_http_header'});
Jakub Narebski45c9a752006-12-27 23:59:51 +01003440 my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
Kay Sieversa59d4af2005-08-07 20:15:44 +02003441 print <<EOF;
Kay Sievers6191f8e2005-08-07 20:19:56 +02003442<?xml version="1.0" encoding="utf-8"?>
Kay Sievers161332a2005-08-07 19:49:46 +02003443<!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 +02003444<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
Jakub Narebskid4baf9e2006-08-17 11:21:28 +02003445<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
Jakub Narebskiae20de52006-06-21 09:48:03 +02003446<!-- git core binaries version $git_version -->
Kay Sievers161332a2005-08-07 19:49:46 +02003447<head>
Alp Tokerf6801d62006-07-11 11:19:34 +01003448<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
Jakub Narebski45c9a752006-12-27 23:59:51 +01003449<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
Kay Sieversc994d622005-08-07 20:27:18 +02003450<meta name="robots" content="index, nofollow"/>
Kay Sieversb87d78d2005-08-07 20:21:04 +02003451<title>$title</title>
Kay Sievers161332a2005-08-07 19:49:46 +02003452EOF
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01003453 # the stylesheet, favicon etc urls won't work correctly with path_info
3454 # unless we set the appropriate base URL
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01003455 if ($ENV{'PATH_INFO'}) {
Giuseppe Bilotta81d3fe92009-02-15 10:18:36 +01003456 print "<base href=\"".esc_url($base_url)."\" />\n";
Giuseppe Bilottac3254ae2009-01-31 02:31:50 +01003457 }
Giuseppe Bilotta41a4d162009-01-31 02:31:52 +01003458 # print out each stylesheet that exist, providing backwards capability
3459 # for those people who defined $stylesheet in a config file
Alan Chandlerb2d34762006-10-03 13:49:03 +01003460 if (defined $stylesheet) {
Alan Chandlerb2d34762006-10-03 13:49:03 +01003461 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
3462 } else {
3463 foreach my $stylesheet (@stylesheets) {
3464 next unless $stylesheet;
3465 print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
3466 }
3467 }
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02003468 if (defined $project) {
Jakub Narebski35621982008-04-20 22:09:48 +02003469 my %href_params = get_feed_info();
3470 if (!exists $href_params{'-title'}) {
3471 $href_params{'-title'} = 'log';
3472 }
3473
3474 foreach my $format qw(RSS Atom) {
3475 my $type = lc($format);
3476 my %link_attr = (
3477 '-rel' => 'alternate',
3478 '-title' => "$project - $href_params{'-title'} - $format feed",
3479 '-type' => "application/$type+xml"
3480 );
3481
3482 $href_params{'action'} = $type;
3483 $link_attr{'-href'} = href(%href_params);
3484 print "<link ".
3485 "rel=\"$link_attr{'-rel'}\" ".
3486 "title=\"$link_attr{'-title'}\" ".
3487 "href=\"$link_attr{'-href'}\" ".
3488 "type=\"$link_attr{'-type'}\" ".
3489 "/>\n";
3490
3491 $href_params{'extra_options'} = '--no-merges';
3492 $link_attr{'-href'} = href(%href_params);
3493 $link_attr{'-title'} .= ' (no merges)';
3494 print "<link ".
3495 "rel=\"$link_attr{'-rel'}\" ".
3496 "title=\"$link_attr{'-title'}\" ".
3497 "href=\"$link_attr{'-href'}\" ".
3498 "type=\"$link_attr{'-type'}\" ".
3499 "/>\n";
3500 }
3501
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003502 } else {
3503 printf('<link rel="alternate" title="%s projects list" '.
Jakub Narebski35621982008-04-20 22:09:48 +02003504 'href="%s" type="text/plain; charset=utf-8" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003505 $site_name, href(project=>undef, action=>"project_index"));
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01003506 printf('<link rel="alternate" title="%s projects feeds" '.
Jakub Narebski35621982008-04-20 22:09:48 +02003507 'href="%s" type="text/x-opml" />'."\n",
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003508 $site_name, href(project=>undef, action=>"opml"));
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02003509 }
Jakub Narebski0b5deba2006-09-04 20:32:13 +02003510 if (defined $favicon) {
Jakub Narebski35621982008-04-20 22:09:48 +02003511 print qq(<link rel="shortcut icon" href="$favicon" type="image/png" />\n);
Jakub Narebski0b5deba2006-09-04 20:32:13 +02003512 }
Jakub Narebski10161352006-08-05 13:18:58 +02003513
Matthias Lederhoferdd04c422006-08-06 13:25:41 +02003514 print "</head>\n" .
Alan Chandlerb2d34762006-10-03 13:49:03 +01003515 "<body>\n";
3516
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01003517 if (defined $site_header && -f $site_header) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003518 insert_file($site_header);
Alan Chandlerb2d34762006-10-03 13:49:03 +01003519 }
3520
3521 print "<div class=\"page_header\">\n" .
Jakub Narebski9a7a62f2006-10-06 12:31:05 +02003522 $cgi->a({-href => esc_url($logo_url),
3523 -title => $logo_label},
3524 qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
Jakub Narebskif93bff82006-09-26 01:58:41 +02003525 print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
Kay Sieversb87d78d2005-08-07 20:21:04 +02003526 if (defined $project) {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003527 print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
Kay Sieversb87d78d2005-08-07 20:21:04 +02003528 if (defined $action) {
3529 print " / $action";
3530 }
Kay Sievers19806692005-08-07 20:26:27 +02003531 print "\n";
Robert Fitzsimons6be93512006-12-23 03:35:16 +00003532 }
Petr Baudisd77b5672007-05-17 04:24:19 +02003533 print "</div>\n";
3534
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003535 my $have_search = gitweb_check_feature('search');
Jakub Narebskif70dda22008-06-02 11:54:41 +02003536 if (defined $project && $have_search) {
Kay Sievers19806692005-08-07 20:26:27 +02003537 if (!defined $searchtext) {
3538 $searchtext = "";
3539 }
Kay Sieversc39e47d2005-09-17 03:00:21 +02003540 my $search_hash;
Timo Hirvonen4c5c2022006-06-20 16:41:05 +03003541 if (defined $hash_base) {
3542 $search_hash = $hash_base;
3543 } elsif (defined $hash) {
Kay Sieversc39e47d2005-09-17 03:00:21 +02003544 $search_hash = $hash;
3545 } else {
Jakub Narebski8adc4bd2006-06-22 08:52:57 +02003546 $search_hash = "HEAD";
Kay Sieversc39e47d2005-09-17 03:00:21 +02003547 }
Matt McCutchen40375a82007-06-28 14:57:07 -04003548 my $action = $my_uri;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08003549 my $use_pathinfo = gitweb_check_feature('pathinfo');
Matt McCutchen40375a82007-06-28 14:57:07 -04003550 if ($use_pathinfo) {
martin f. krafft85d17a12008-04-20 23:23:38 +02003551 $action .= "/".esc_url($project);
Matt McCutchen40375a82007-06-28 14:57:07 -04003552 }
Matt McCutchen40375a82007-06-28 14:57:07 -04003553 print $cgi->startform(-method => "get", -action => $action) .
Kay Sieversc994d622005-08-07 20:27:18 +02003554 "<div class=\"search\">\n" .
Jakub Narebskif70dda22008-06-02 11:54:41 +02003555 (!$use_pathinfo &&
3556 $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
3557 $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
3558 $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
Petr Baudis88ad7292006-10-24 05:15:46 +02003559 $cgi->popup_menu(-name => 'st', -default => 'commit',
Petr Baudise7738552007-05-17 04:31:12 +02003560 -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
Petr Baudis88ad7292006-10-24 05:15:46 +02003561 $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
3562 " search:\n",
Kay Sieversc994d622005-08-07 20:27:18 +02003563 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
Petr Baudis0e559912008-02-26 13:22:08 +01003564 "<span title=\"Extended regular expression\">" .
3565 $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
3566 -checked => $search_use_regexp) .
3567 "</span>" .
Kay Sieversc994d622005-08-07 20:27:18 +02003568 "</div>" .
3569 $cgi->end_form() . "\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02003570 }
Kay Sievers161332a2005-08-07 19:49:46 +02003571}
3572
Kay Sievers12a88f22005-08-07 20:02:47 +02003573sub git_footer_html {
Jakub Narebski35621982008-04-20 22:09:48 +02003574 my $feed_class = 'rss_logo';
3575
Kay Sievers6191f8e2005-08-07 20:19:56 +02003576 print "<div class=\"page_footer\">\n";
Kay Sieversb87d78d2005-08-07 20:21:04 +02003577 if (defined $project) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02003578 my $descr = git_get_project_description($project);
Kay Sieversb87d78d2005-08-07 20:21:04 +02003579 if (defined $descr) {
Kay Sievers40c13812005-11-19 17:41:29 +01003580 print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
Kay Sievers6191f8e2005-08-07 20:19:56 +02003581 }
Jakub Narebski35621982008-04-20 22:09:48 +02003582
3583 my %href_params = get_feed_info();
3584 if (!%href_params) {
3585 $feed_class .= ' generic';
3586 }
3587 $href_params{'-title'} ||= 'log';
3588
3589 foreach my $format qw(RSS Atom) {
3590 $href_params{'action'} = lc($format);
3591 print $cgi->a({-href => href(%href_params),
3592 -title => "$href_params{'-title'} $format feed",
3593 -class => $feed_class}, $format)."\n";
3594 }
3595
Kay Sieversc994d622005-08-07 20:27:18 +02003596 } else {
Jakub Narebskia1565c42006-09-15 19:30:34 +02003597 print $cgi->a({-href => href(project=>undef, action=>"opml"),
Jakub Narebski35621982008-04-20 22:09:48 +02003598 -class => $feed_class}, "OPML") . " ";
Jakub Narebski9d0734a2006-09-15 11:11:33 +02003599 print $cgi->a({-href => href(project=>undef, action=>"project_index"),
Jakub Narebski35621982008-04-20 22:09:48 +02003600 -class => $feed_class}, "TXT") . "\n";
Kay Sieversff7669a2005-08-07 20:13:02 +02003601 }
Jakub Narebski35621982008-04-20 22:09:48 +02003602 print "</div>\n"; # class="page_footer"
Alan Chandlerb2d34762006-10-03 13:49:03 +01003603
Jakub Narebskiaa7dd052009-09-01 13:39:16 +02003604 if (defined $t0 && gitweb_check_feature('timed')) {
3605 print "<div id=\"generating_info\">\n";
3606 print 'This page took '.
3607 '<span id="generating_time" class="time_span">'.
3608 Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
3609 ' seconds </span>'.
3610 ' and '.
3611 '<span id="generating_cmd">'.
3612 $number_of_git_cmds.
3613 '</span> git commands '.
3614 " to generate.\n";
3615 print "</div>\n"; # class="page_footer"
3616 }
3617
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01003618 if (defined $site_footer && -f $site_footer) {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01003619 insert_file($site_footer);
Alan Chandlerb2d34762006-10-03 13:49:03 +01003620 }
3621
Jakub Narebskic4ccf612009-09-01 13:39:19 +02003622 print qq!<script type="text/javascript" src="$javascript"></script>\n!;
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01003623 if (defined $action &&
3624 $action eq 'blame_incremental') {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02003625 print qq!<script type="text/javascript">\n!.
3626 qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
3627 qq! "!. href() .qq!");\n!.
3628 qq!</script>\n!;
Jakub Narebskie627e502009-11-26 21:12:15 +01003629 } elsif (gitweb_check_feature('javascript-actions')) {
Jakub Narebskic4ccf612009-09-01 13:39:19 +02003630 print qq!<script type="text/javascript">\n!.
3631 qq!window.onload = fixLinks;\n!.
3632 qq!</script>\n!;
3633 }
3634
Alan Chandlerb2d34762006-10-03 13:49:03 +01003635 print "</body>\n" .
Kay Sievers9cd3d982005-08-07 20:17:42 +02003636 "</html>";
Kay Sievers161332a2005-08-07 19:49:46 +02003637}
3638
Jakub Narebski453541f2010-02-07 21:51:18 +01003639# die_error(<http_status_code>, <error_message>[, <detailed_html_description>])
Lea Wiemann074afaa2008-06-19 22:03:21 +02003640# Example: die_error(404, 'Hash not found')
3641# By convention, use the following status codes (as defined in RFC 2616):
3642# 400: Invalid or missing CGI parameters, or
3643# requested object exists but has wrong type.
3644# 403: Requested feature (like "pickaxe" or "snapshot") not enabled on
3645# this server or project.
3646# 404: Requested object/revision/project doesn't exist.
3647# 500: The server isn't configured properly, or
3648# an internal error occurred (e.g. failed assertions caused by bugs), or
3649# an unknown error occurred (e.g. the git binary died unexpectedly).
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01003650# 503: The server is currently unavailable (because it is overloaded,
3651# or down for maintenance). Generally, this is a temporary state.
Kay Sievers061cc7c2005-08-07 20:15:57 +02003652sub die_error {
Lea Wiemann074afaa2008-06-19 22:03:21 +02003653 my $status = shift || 500;
Jakub Narebski1df48762010-02-07 21:52:25 +01003654 my $error = esc_html(shift) || "Internal Server Error";
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01003655 my $extra = shift;
Jakub Narebski7a597452010-04-24 16:00:04 +02003656 my %opts = @_;
Kay Sievers664f4cc2005-08-07 20:17:19 +02003657
John 'Warthog9' Hawleyb62a1a92010-01-30 23:30:39 +01003658 my %http_responses = (
3659 400 => '400 Bad Request',
3660 403 => '403 Forbidden',
3661 404 => '404 Not Found',
3662 500 => '500 Internal Server Error',
3663 503 => '503 Service Unavailable',
3664 );
Jakub Narebski7a597452010-04-24 16:00:04 +02003665 git_header_html($http_responses{$status}, undef, %opts);
Jakub Narebski59b9f612006-08-22 23:42:53 +02003666 print <<EOF;
3667<div class="page_body">
3668<br /><br />
3669$status - $error
3670<br />
Jakub Narebski59b9f612006-08-22 23:42:53 +02003671EOF
John 'Warthog9' Hawleyaa140132010-01-30 23:30:44 +01003672 if (defined $extra) {
3673 print "<hr />\n" .
3674 "$extra\n";
3675 }
3676 print "</div>\n";
3677
Kay Sieversa59d4af2005-08-07 20:15:44 +02003678 git_footer_html();
Jakub Narebski7a597452010-04-24 16:00:04 +02003679 goto DONE_GITWEB
3680 unless ($opts{'-error_handler'});
Kay Sieversa59d4af2005-08-07 20:15:44 +02003681}
3682
Jakub Narebski717b8312006-07-31 21:22:15 +02003683## ----------------------------------------------------------------------
3684## functions printing or outputting HTML: navigation
3685
Jakub Narebski847e01f2006-08-14 02:05:47 +02003686sub git_print_page_nav {
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003687 my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
3688 $extra = '' if !defined $extra; # pager or formats
3689
3690 my @navs = qw(summary shortlog log commit commitdiff tree);
3691 if ($suppress) {
3692 @navs = grep { $_ ne $suppress } @navs;
3693 }
3694
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003695 my %arg = map { $_ => {action=>$_} } @navs;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003696 if (defined $head) {
3697 for (qw(commit commitdiff)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02003698 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003699 }
3700 if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
3701 for (qw(shortlog log)) {
Jakub Narebski3be8e722007-04-01 22:22:21 +02003702 $arg{$_}{'hash'} = $head;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003703 }
3704 }
3705 }
Petr Baudisd627f682008-10-02 16:36:52 +02003706
Jakub Narebski3be8e722007-04-01 22:22:21 +02003707 $arg{'tree'}{'hash'} = $treehead if defined $treehead;
3708 $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003709
Junio C Hamanoa7c5a282008-11-29 13:02:08 -08003710 my @actions = gitweb_get_feature('actions');
Jakub Narebski2b11e052008-10-12 00:02:32 +02003711 my %repl = (
3712 '%' => '%',
3713 'n' => $project, # project name
3714 'f' => $git_dir, # project path within filesystem
3715 'h' => $treehead || '', # current hash ('h' parameter)
3716 'b' => $treebase || '', # hash base ('hb' parameter)
3717 );
Petr Baudisd627f682008-10-02 16:36:52 +02003718 while (@actions) {
Jakub Narebski2b11e052008-10-12 00:02:32 +02003719 my ($label, $link, $pos) = splice(@actions,0,3);
3720 # insert
Petr Baudisd627f682008-10-02 16:36:52 +02003721 @navs = map { $_ eq $pos ? ($_, $label) : $_ } @navs;
3722 # munch munch
Jakub Narebski2b11e052008-10-12 00:02:32 +02003723 $link =~ s/%([%nfhb])/$repl{$1}/g;
Petr Baudisd627f682008-10-02 16:36:52 +02003724 $arg{$label}{'_href'} = $link;
3725 }
3726
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003727 print "<div class=\"page_nav\">\n" .
3728 (join " | ",
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003729 map { $_ eq $current ?
Petr Baudisd627f682008-10-02 16:36:52 +02003730 $_ : $cgi->a({-href => ($arg{$_}{_href} ? $arg{$_}{_href} : href(%{$arg{$_}}))}, "$_")
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003731 } @navs);
Jakub Narebski898a8932006-07-30 16:14:43 +02003732 print "<br/>\n$extra<br/>\n" .
Jakub Narebskib18f9bf2006-07-30 14:59:57 +02003733 "</div>\n";
3734}
3735
Jakub Narebski847e01f2006-08-14 02:05:47 +02003736sub format_paging_nav {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01003737 my ($action, $page, $has_next_link) = @_;
Jakub Narebski43ffc062006-07-30 17:49:00 +02003738 my $paging_nav;
3739
3740
Jakub Narebski43ffc062006-07-30 17:49:00 +02003741 if ($page > 0) {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01003742 $paging_nav .=
3743 $cgi->a({-href => href(-replay=>1, page=>undef)}, "first") .
3744 " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01003745 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebski26298b52006-08-10 12:38:47 +02003746 -accesskey => "p", -title => "Alt-p"}, "prev");
Jakub Narebski43ffc062006-07-30 17:49:00 +02003747 } else {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01003748 $paging_nav .= "first &sdot; prev";
Jakub Narebski43ffc062006-07-30 17:49:00 +02003749 }
3750
Lea Wiemann1f684dc2008-05-28 01:25:42 +02003751 if ($has_next_link) {
Jakub Narebski43ffc062006-07-30 17:49:00 +02003752 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01003753 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebski26298b52006-08-10 12:38:47 +02003754 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski43ffc062006-07-30 17:49:00 +02003755 } else {
3756 $paging_nav .= " &sdot; next";
3757 }
3758
3759 return $paging_nav;
3760}
3761
Jakub Narebski717b8312006-07-31 21:22:15 +02003762## ......................................................................
3763## functions printing or outputting HTML: div
Kay Sievers42f7eb92005-08-07 20:21:46 +02003764
Jakub Narebski847e01f2006-08-14 02:05:47 +02003765sub git_print_header_div {
Jakub Narebski717b8312006-07-31 21:22:15 +02003766 my ($action, $title, $hash, $hash_base) = @_;
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003767 my %args = ();
Jakub Narebski717b8312006-07-31 21:22:15 +02003768
Jakub Narebski3be8e722007-04-01 22:22:21 +02003769 $args{'action'} = $action;
3770 $args{'hash'} = $hash if $hash;
3771 $args{'hash_base'} = $hash_base if $hash_base;
Jakub Narebski717b8312006-07-31 21:22:15 +02003772
3773 print "<div class=\"header\">\n" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02003774 $cgi->a({-href => href(%args), -class => "title"},
3775 $title ? $title : $action) .
3776 "\n</div>\n";
Kay Sievers42f7eb92005-08-07 20:21:46 +02003777}
3778
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003779sub print_local_time {
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003780 print format_local_time(@_);
3781}
3782
3783sub format_local_time {
3784 my $localtime = '';
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003785 my %date = @_;
3786 if ($date{'hour_local'} < 6) {
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003787 $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003788 $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
Jakub Narebskia44465c2006-08-28 23:17:31 +02003789 } else {
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003790 $localtime .= sprintf(" (%02d:%02d %s)",
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003791 $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
Jakub Narebskia44465c2006-08-28 23:17:31 +02003792 }
John 'Warthog9' Hawley0cf207f2010-01-30 23:30:42 +01003793
3794 return $localtime;
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003795}
3796
3797# Outputs the author name and date in long form
3798sub git_print_authorship {
3799 my $co = shift;
3800 my %opts = @_;
3801 my $tag = $opts{-tag} || 'div';
Stephen Boyde133d652009-10-15 21:14:59 -07003802 my $author = $co->{'author_name'};
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003803
3804 my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
3805 print "<$tag class=\"author_date\">" .
Stephen Boyde133d652009-10-15 21:14:59 -07003806 format_search_author($author, "author", esc_html($author)) .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003807 " [$ad{'rfc2822'}";
3808 print_local_time(%ad) if ($opts{-localtime});
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02003809 print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
3810 . "</$tag>\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003811}
3812
3813# Outputs table rows containing the full author or committer information,
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02003814# in the format expected for 'commit' view (& similar).
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003815# Parameters are a commit hash reference, followed by the list of people
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02003816# to output information for. If the list is empty it defaults to both
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003817# author and committer.
3818sub git_print_authorship_rows {
3819 my $co = shift;
3820 # too bad we can't use @people = @_ || ('author', 'committer')
3821 my @people = @_;
3822 @people = ('author', 'committer') unless @people;
3823 foreach my $who (@people) {
3824 my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
Stephen Boyde133d652009-10-15 21:14:59 -07003825 print "<tr><td>$who</td><td>" .
3826 format_search_author($co->{"${who}_name"}, $who,
3827 esc_html($co->{"${who}_name"})) . " " .
3828 format_search_author($co->{"${who}_email"}, $who,
3829 esc_html("<" . $co->{"${who}_email"} . ">")) .
3830 "</td><td rowspan=\"2\">" .
Giuseppe Bilottae9fdd742009-06-30 00:00:51 +02003831 git_get_avatar($co->{"${who}_email"}, -size => 'double') .
3832 "</td></tr>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02003833 "<tr>" .
3834 "<td></td><td> $wd{'rfc2822'}";
3835 print_local_time(%wd);
3836 print "</td>" .
3837 "</tr>\n";
3838 }
Jakub Narebski6fd92a22006-08-28 14:48:12 +02003839}
3840
Jakub Narebski717b8312006-07-31 21:22:15 +02003841sub git_print_page_path {
3842 my $name = shift;
3843 my $type = shift;
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003844 my $hb = shift;
Junio C Hamanodf2c37a2006-01-09 13:13:39 +01003845
Jakub Narebski4df118e2006-10-21 17:53:55 +02003846
3847 print "<div class=\"page_path\">";
3848 print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
Martin Koegler00f429a2007-06-03 17:42:44 +02003849 -title => 'tree root'}, to_utf8("[$project]"));
Jakub Narebski4df118e2006-10-21 17:53:55 +02003850 print " / ";
3851 if (defined $name) {
Jakub Narebski762c7202006-09-04 18:17:58 +02003852 my @dirname = split '/', $name;
3853 my $basename = pop @dirname;
3854 my $fullname = '';
3855
Jakub Narebski762c7202006-09-04 18:17:58 +02003856 foreach my $dir (@dirname) {
Petr Baudis16fdb482006-09-21 02:05:50 +02003857 $fullname .= ($fullname ? '/' : '') . $dir;
Jakub Narebski762c7202006-09-04 18:17:58 +02003858 print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
3859 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003860 -title => $fullname}, esc_path($dir));
Petr Baudis26d0a972006-09-23 01:00:12 +02003861 print " / ";
Jakub Narebski762c7202006-09-04 18:17:58 +02003862 }
3863 if (defined $type && $type eq 'blob') {
Jakub Narebski952c65f2006-08-22 16:52:50 +02003864 print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
Jakub Narebski762c7202006-09-04 18:17:58 +02003865 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003866 -title => $name}, esc_path($basename));
Jakub Narebski762c7202006-09-04 18:17:58 +02003867 } elsif (defined $type && $type eq 'tree') {
3868 print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
3869 hash_base=>$hb),
Jakub Narebskiedc04e92007-03-07 02:21:25 +01003870 -title => $name}, esc_path($basename));
Jakub Narebski4df118e2006-10-21 17:53:55 +02003871 print " / ";
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003872 } else {
Jakub Narebski403d0902006-11-08 11:48:56 +01003873 print esc_path($basename);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07003874 }
Kay Sievers8ed23e12005-08-07 20:05:44 +02003875 }
Jakub Narebski4df118e2006-10-21 17:53:55 +02003876 print "<br/></div>\n";
Kay Sievers4c02e3c2005-08-07 19:52:52 +02003877}
3878
Jakub Narebski74fd8722009-05-07 19:11:29 +02003879sub git_print_log {
Jakub Narebskid16d0932006-08-17 11:21:23 +02003880 my $log = shift;
Jakub Narebskib7f92532006-08-28 14:48:10 +02003881 my %opts = @_;
Jakub Narebskid16d0932006-08-17 11:21:23 +02003882
Jakub Narebskib7f92532006-08-28 14:48:10 +02003883 if ($opts{'-remove_title'}) {
3884 # remove title, i.e. first line of log
3885 shift @$log;
3886 }
Jakub Narebskid16d0932006-08-17 11:21:23 +02003887 # remove leading empty lines
3888 while (defined $log->[0] && $log->[0] eq "") {
3889 shift @$log;
3890 }
3891
3892 # print log
3893 my $signoff = 0;
3894 my $empty = 0;
3895 foreach my $line (@$log) {
Jakub Narebskib7f92532006-08-28 14:48:10 +02003896 if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
3897 $signoff = 1;
Jakub Narebskifba20b42006-08-28 14:48:13 +02003898 $empty = 0;
Jakub Narebskib7f92532006-08-28 14:48:10 +02003899 if (! $opts{'-remove_signoff'}) {
3900 print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
3901 next;
3902 } else {
3903 # remove signoff lines
3904 next;
3905 }
3906 } else {
3907 $signoff = 0;
3908 }
3909
Jakub Narebskid16d0932006-08-17 11:21:23 +02003910 # print only one empty line
3911 # do not print empty line after signoff
3912 if ($line eq "") {
3913 next if ($empty || $signoff);
3914 $empty = 1;
3915 } else {
3916 $empty = 0;
3917 }
Jakub Narebskib7f92532006-08-28 14:48:10 +02003918
3919 print format_log_line_html($line) . "<br/>\n";
3920 }
3921
3922 if ($opts{'-final_empty_line'}) {
3923 # end with single empty line
3924 print "<br/>\n" unless $empty;
Jakub Narebskid16d0932006-08-17 11:21:23 +02003925 }
3926}
3927
Jakub Narebskie33fba42006-12-10 13:25:46 +01003928# return link target (what link points to)
3929sub git_get_link_target {
3930 my $hash = shift;
3931 my $link_target;
3932
3933 # read link
3934 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
3935 or return;
3936 {
Jakub Narebski34122b52009-05-11 03:29:40 +02003937 local $/ = undef;
Jakub Narebskie33fba42006-12-10 13:25:46 +01003938 $link_target = <$fd>;
3939 }
3940 close $fd
3941 or return;
3942
3943 return $link_target;
3944}
3945
Jakub Narebski3bf9d572006-12-10 13:25:48 +01003946# given link target, and the directory (basedir) the link is in,
3947# return target of link relative to top directory (top tree);
3948# return undef if it is not possible (including absolute links).
3949sub normalize_link_target {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02003950 my ($link_target, $basedir) = @_;
Jakub Narebski3bf9d572006-12-10 13:25:48 +01003951
3952 # absolute symlinks (beginning with '/') cannot be normalized
3953 return if (substr($link_target, 0, 1) eq '/');
3954
3955 # normalize link target to path from top (root) tree (dir)
3956 my $path;
3957 if ($basedir) {
3958 $path = $basedir . '/' . $link_target;
3959 } else {
3960 # we are in top (root) tree (dir)
3961 $path = $link_target;
3962 }
3963
3964 # remove //, /./, and /../
3965 my @path_parts;
3966 foreach my $part (split('/', $path)) {
3967 # discard '.' and ''
3968 next if (!$part || $part eq '.');
3969 # handle '..'
3970 if ($part eq '..') {
3971 if (@path_parts) {
3972 pop @path_parts;
3973 } else {
3974 # link leads outside repository (outside top dir)
3975 return;
3976 }
3977 } else {
3978 push @path_parts, $part;
3979 }
3980 }
3981 $path = join('/', @path_parts);
3982
3983 return $path;
3984}
Jakub Narebskie33fba42006-12-10 13:25:46 +01003985
Jakub Narebskifa702002006-08-31 00:35:07 +02003986# print tree entry (row of git_tree), but without encompassing <tr> element
3987sub git_print_tree_entry {
3988 my ($t, $basedir, $hash_base, $have_blame) = @_;
3989
3990 my %base_key = ();
Jakub Narebskie33fba42006-12-10 13:25:46 +01003991 $base_key{'hash_base'} = $hash_base if defined $hash_base;
Jakub Narebskifa702002006-08-31 00:35:07 +02003992
Luben Tuikov4de741b2006-09-25 22:38:16 -07003993 # The format of a table row is: mode list link. Where mode is
3994 # the mode of the entry, list is the name of the entry, an href,
3995 # and link is the action links of the entry.
3996
Jakub Narebskifa702002006-08-31 00:35:07 +02003997 print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02003998 if (exists $t->{'size'}) {
3999 print "<td class=\"size\">$t->{'size'}</td>\n";
4000 }
Jakub Narebskifa702002006-08-31 00:35:07 +02004001 if ($t->{'type'} eq "blob") {
4002 print "<td class=\"list\">" .
Luben Tuikov4de741b2006-09-25 22:38:16 -07004003 $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004004 file_name=>"$basedir$t->{'name'}", %base_key),
Jakub Narebskie33fba42006-12-10 13:25:46 +01004005 -class => "list"}, esc_path($t->{'name'}));
4006 if (S_ISLNK(oct $t->{'mode'})) {
4007 my $link_target = git_get_link_target($t->{'hash'});
4008 if ($link_target) {
Jakub Narebski15c54fe2009-05-11 19:45:11 +02004009 my $norm_target = normalize_link_target($link_target, $basedir);
Jakub Narebski3bf9d572006-12-10 13:25:48 +01004010 if (defined $norm_target) {
4011 print " -> " .
4012 $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
4013 file_name=>$norm_target),
4014 -title => $norm_target}, esc_path($link_target));
4015 } else {
4016 print " -> " . esc_path($link_target);
4017 }
Jakub Narebskie33fba42006-12-10 13:25:46 +01004018 }
4019 }
4020 print "</td>\n";
Luben Tuikov4de741b2006-09-25 22:38:16 -07004021 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02004022 print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004023 file_name=>"$basedir$t->{'name'}", %base_key)},
4024 "blob");
Jakub Narebskifa702002006-08-31 00:35:07 +02004025 if ($have_blame) {
Petr Baudis4777b012006-10-24 05:36:10 +02004026 print " | " .
4027 $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004028 file_name=>"$basedir$t->{'name'}", %base_key)},
4029 "blame");
Jakub Narebskifa702002006-08-31 00:35:07 +02004030 }
4031 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02004032 print " | " .
4033 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02004034 hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
4035 "history");
4036 }
4037 print " | " .
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07004038 $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004039 file_name=>"$basedir$t->{'name'}")},
4040 "raw");
Luben Tuikov4de741b2006-09-25 22:38:16 -07004041 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02004042
4043 } elsif ($t->{'type'} eq "tree") {
Luben Tuikov0fa105e2006-09-26 12:45:37 -07004044 print "<td class=\"list\">";
4045 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004046 file_name=>"$basedir$t->{'name'}",
4047 %base_key)},
Jakub Narebski403d0902006-11-08 11:48:56 +01004048 esc_path($t->{'name'}));
Luben Tuikov0fa105e2006-09-26 12:45:37 -07004049 print "</td>\n";
4050 print "<td class=\"link\">";
Petr Baudis4777b012006-10-24 05:36:10 +02004051 print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02004052 file_name=>"$basedir$t->{'name'}",
4053 %base_key)},
Jakub Narebskie33fba42006-12-10 13:25:46 +01004054 "tree");
Jakub Narebskifa702002006-08-31 00:35:07 +02004055 if (defined $hash_base) {
Petr Baudis4777b012006-10-24 05:36:10 +02004056 print " | " .
4057 $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
Jakub Narebskifa702002006-08-31 00:35:07 +02004058 file_name=>"$basedir$t->{'name'}")},
4059 "history");
4060 }
4061 print "</td>\n";
Jakub Narebski01ac1e32007-07-28 16:27:31 +02004062 } else {
4063 # unknown object: we can only present history for it
4064 # (this includes 'commit' object, i.e. submodule support)
4065 print "<td class=\"list\">" .
4066 esc_path($t->{'name'}) .
4067 "</td>\n";
4068 print "<td class=\"link\">";
4069 if (defined $hash_base) {
4070 print $cgi->a({-href => href(action=>"history",
4071 hash_base=>$hash_base,
4072 file_name=>"$basedir$t->{'name'}")},
4073 "history");
4074 }
4075 print "</td>\n";
Jakub Narebskifa702002006-08-31 00:35:07 +02004076 }
4077}
4078
Jakub Narebski717b8312006-07-31 21:22:15 +02004079## ......................................................................
4080## functions printing large fragments of HTML
Kay Sieversede5e102005-08-07 20:23:12 +02004081
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004082# get pre-image filenames for merge (combined) diff
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004083sub fill_from_file_info {
4084 my ($diff, @parents) = @_;
4085
4086 $diff->{'from_file'} = [ ];
4087 $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
4088 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
4089 if ($diff->{'status'}[$i] eq 'R' ||
4090 $diff->{'status'}[$i] eq 'C') {
4091 $diff->{'from_file'}[$i] =
4092 git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
4093 }
4094 }
4095
4096 return $diff;
4097}
4098
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004099# is current raw difftree line of file deletion
Jakub Narebski90921742007-06-08 13:27:42 +02004100sub is_deleted {
4101 my $diffinfo = shift;
4102
Jakub Narebski4ed4a342008-04-05 21:13:24 +01004103 return $diffinfo->{'to_id'} eq ('0' x 40);
Jakub Narebski90921742007-06-08 13:27:42 +02004104}
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004105
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004106# does patch correspond to [previous] difftree raw line
4107# $diffinfo - hashref of parsed raw diff format
4108# $patchinfo - hashref of parsed patch diff format
4109# (the same keys as in $diffinfo)
4110sub is_patch_split {
4111 my ($diffinfo, $patchinfo) = @_;
4112
4113 return defined $diffinfo && defined $patchinfo
Jakub Narebski9d301452007-11-01 12:38:08 +01004114 && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004115}
4116
4117
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004118sub git_difftree_body {
Jakub Narebskied224de2007-05-07 01:10:04 +02004119 my ($difftree, $hash, @parents) = @_;
4120 my ($parent) = $parents[0];
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004121 my $have_blame = gitweb_check_feature('blame');
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004122 print "<div class=\"list_head\">\n";
4123 if ($#{$difftree} > 10) {
4124 print(($#{$difftree} + 1) . " files changed:\n");
4125 }
4126 print "</div>\n";
4127
Jakub Narebskied224de2007-05-07 01:10:04 +02004128 print "<table class=\"" .
4129 (@parents > 1 ? "combined " : "") .
4130 "diff_tree\">\n";
Jakub Narebski47598d72007-06-08 13:24:56 +02004131
4132 # header only for combined diff in 'commitdiff' view
Jakub Narebski3ef408a2007-09-08 21:54:28 +02004133 my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
Jakub Narebski47598d72007-06-08 13:24:56 +02004134 if ($has_header) {
4135 # table header
4136 print "<thead><tr>\n" .
4137 "<th></th><th></th>\n"; # filename, patchN link
4138 for (my $i = 0; $i < @parents; $i++) {
4139 my $par = $parents[$i];
4140 print "<th>" .
4141 $cgi->a({-href => href(action=>"commitdiff",
4142 hash=>$hash, hash_parent=>$par),
4143 -title => 'commitdiff to parent number ' .
4144 ($i+1) . ': ' . substr($par,0,7)},
4145 $i+1) .
4146 "&nbsp;</th>\n";
4147 }
4148 print "</tr></thead>\n<tbody>\n";
4149 }
4150
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004151 my $alternate = 1;
Jakub Narebskib4657e72006-08-28 14:48:14 +02004152 my $patchno = 0;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004153 foreach my $line (@{$difftree}) {
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004154 my $diff = parsed_difftree_line($line);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004155
4156 if ($alternate) {
4157 print "<tr class=\"dark\">\n";
4158 } else {
4159 print "<tr class=\"light\">\n";
4160 }
4161 $alternate ^= 1;
4162
Jakub Narebski493e01d2007-05-07 01:10:06 +02004163 if (exists $diff->{'nparents'}) { # combined diff
Jakub Narebskied224de2007-05-07 01:10:04 +02004164
Jakub Narebski493e01d2007-05-07 01:10:06 +02004165 fill_from_file_info($diff, @parents)
4166 unless exists $diff->{'from_file'};
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004167
Jakub Narebski90921742007-06-08 13:27:42 +02004168 if (!is_deleted($diff)) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004169 # file exists in the result (child) commit
4170 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02004171 $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4172 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004173 hash_base=>$hash),
Jakub Narebski493e01d2007-05-07 01:10:06 +02004174 -class => "list"}, esc_path($diff->{'to_file'})) .
Jakub Narebskied224de2007-05-07 01:10:04 +02004175 "</td>\n";
4176 } else {
4177 print "<td>" .
Jakub Narebski493e01d2007-05-07 01:10:06 +02004178 esc_path($diff->{'to_file'}) .
Jakub Narebskied224de2007-05-07 01:10:04 +02004179 "</td>\n";
4180 }
4181
4182 if ($action eq 'commitdiff') {
4183 # link to patch
4184 $patchno++;
4185 print "<td class=\"link\">" .
4186 $cgi->a({-href => "#patch$patchno"}, "patch") .
4187 " | " .
4188 "</td>\n";
4189 }
4190
4191 my $has_history = 0;
4192 my $not_deleted = 0;
Jakub Narebski493e01d2007-05-07 01:10:06 +02004193 for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004194 my $hash_parent = $parents[$i];
Jakub Narebski493e01d2007-05-07 01:10:06 +02004195 my $from_hash = $diff->{'from_id'}[$i];
4196 my $from_path = $diff->{'from_file'}[$i];
4197 my $status = $diff->{'status'}[$i];
Jakub Narebskied224de2007-05-07 01:10:04 +02004198
4199 $has_history ||= ($status ne 'A');
4200 $not_deleted ||= ($status ne 'D');
4201
Jakub Narebskied224de2007-05-07 01:10:04 +02004202 if ($status eq 'A') {
4203 print "<td class=\"link\" align=\"right\"> | </td>\n";
4204 } elsif ($status eq 'D') {
4205 print "<td class=\"link\">" .
4206 $cgi->a({-href => href(action=>"blob",
4207 hash_base=>$hash,
4208 hash=>$from_hash,
4209 file_name=>$from_path)},
4210 "blob" . ($i+1)) .
4211 " | </td>\n";
4212 } else {
Jakub Narebski493e01d2007-05-07 01:10:06 +02004213 if ($diff->{'to_id'} eq $from_hash) {
Jakub Narebskied224de2007-05-07 01:10:04 +02004214 print "<td class=\"link nochange\">";
4215 } else {
4216 print "<td class=\"link\">";
4217 }
4218 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004219 hash=>$diff->{'to_id'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004220 hash_parent=>$from_hash,
4221 hash_base=>$hash,
4222 hash_parent_base=>$hash_parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004223 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004224 file_parent=>$from_path)},
4225 "diff" . ($i+1)) .
4226 " | </td>\n";
4227 }
4228 }
4229
4230 print "<td class=\"link\">";
4231 if ($not_deleted) {
4232 print $cgi->a({-href => href(action=>"blob",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004233 hash=>$diff->{'to_id'},
4234 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004235 hash_base=>$hash)},
4236 "blob");
4237 print " | " if ($has_history);
4238 }
4239 if ($has_history) {
4240 print $cgi->a({-href => href(action=>"history",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004241 file_name=>$diff->{'to_file'},
Jakub Narebskied224de2007-05-07 01:10:04 +02004242 hash_base=>$hash)},
4243 "history");
4244 }
4245 print "</td>\n";
4246
4247 print "</tr>\n";
4248 next; # instead of 'else' clause, to avoid extra indent
4249 }
4250 # else ordinary diff
4251
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004252 my ($to_mode_oct, $to_mode_str, $to_file_type);
4253 my ($from_mode_oct, $from_mode_str, $from_file_type);
Jakub Narebski493e01d2007-05-07 01:10:06 +02004254 if ($diff->{'to_mode'} ne ('0' x 6)) {
4255 $to_mode_oct = oct $diff->{'to_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004256 if (S_ISREG($to_mode_oct)) { # only for regular file
4257 $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004258 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004259 $to_file_type = file_type($diff->{'to_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004260 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004261 if ($diff->{'from_mode'} ne ('0' x 6)) {
4262 $from_mode_oct = oct $diff->{'from_mode'};
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004263 if (S_ISREG($to_mode_oct)) { # only for regular file
4264 $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
4265 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004266 $from_file_type = file_type($diff->{'from_mode'});
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004267 }
4268
Jakub Narebski493e01d2007-05-07 01:10:06 +02004269 if ($diff->{'status'} eq "A") { # created
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004270 my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
4271 $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
4272 $mode_chng .= "]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004273 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004274 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4275 hash_base=>$hash, file_name=>$diff->{'file'}),
4276 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004277 print "</td>\n";
4278 print "<td>$mode_chng</td>\n";
4279 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004280 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004281 # link to patch
4282 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07004283 print $cgi->a({-href => "#patch$patchno"}, "patch");
Jakub Narebski897d1d22006-11-19 22:51:39 +01004284 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004285 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004286 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4287 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski3faa5412007-01-08 02:10:42 +01004288 "blob");
Jakub Narebskib4657e72006-08-28 14:48:14 +02004289 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004290
Jakub Narebski493e01d2007-05-07 01:10:06 +02004291 } elsif ($diff->{'status'} eq "D") { # deleted
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004292 my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
Luben Tuikov499faed2006-09-27 17:23:25 -07004293 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004294 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4295 hash_base=>$parent, file_name=>$diff->{'file'}),
4296 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004297 print "</td>\n";
4298 print "<td>$mode_chng</td>\n";
4299 print "<td class=\"link\">";
Jakub Narebski72dbafa2006-09-04 18:19:58 +02004300 if ($action eq 'commitdiff') {
Jakub Narebskib4657e72006-08-28 14:48:14 +02004301 # link to patch
4302 $patchno++;
Luben Tuikov499faed2006-09-27 17:23:25 -07004303 print $cgi->a({-href => "#patch$patchno"}, "patch");
4304 print " | ";
Jakub Narebskib4657e72006-08-28 14:48:14 +02004305 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004306 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
4307 hash_base=>$parent, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004308 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004309 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004310 print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004311 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004312 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004313 }
Jakub Narebskib4657e72006-08-28 14:48:14 +02004314 print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004315 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004316 "history");
Luben Tuikov499faed2006-09-27 17:23:25 -07004317 print "</td>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004318
Jakub Narebski493e01d2007-05-07 01:10:06 +02004319 } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004320 my $mode_chnge = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004321 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004322 $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
Jakub Narebski6e72cf42007-01-03 20:47:24 +01004323 if ($from_file_type ne $to_file_type) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004324 $mode_chnge .= " from $from_file_type to $to_file_type";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004325 }
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004326 if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
4327 if ($from_mode_str && $to_mode_str) {
4328 $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
4329 } elsif ($to_mode_str) {
4330 $mode_chnge .= " mode: $to_mode_str";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004331 }
4332 }
4333 $mode_chnge .= "]</span>\n";
4334 }
4335 print "<td>";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004336 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4337 hash_base=>$hash, file_name=>$diff->{'file'}),
4338 -class => "list"}, esc_path($diff->{'file'}));
Luben Tuikov499faed2006-09-27 17:23:25 -07004339 print "</td>\n";
4340 print "<td>$mode_chnge</td>\n";
4341 print "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01004342 if ($action eq 'commitdiff') {
4343 # link to patch
4344 $patchno++;
4345 print $cgi->a({-href => "#patch$patchno"}, "patch") .
4346 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004347 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01004348 # "commit" view and modified file (not onlu mode changed)
4349 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004350 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01004351 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004352 file_name=>$diff->{'file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01004353 "diff") .
4354 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004355 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004356 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4357 hash_base=>$hash, file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004358 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004359 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004360 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004361 file_name=>$diff->{'file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004362 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004363 }
Luben Tuikoveb51ec92006-09-27 17:24:49 -07004364 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004365 file_name=>$diff->{'file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004366 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004367 print "</td>\n";
4368
Jakub Narebski493e01d2007-05-07 01:10:06 +02004369 } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004370 my %status_name = ('R' => 'moved', 'C' => 'copied');
Jakub Narebski493e01d2007-05-07 01:10:06 +02004371 my $nstatus = $status_name{$diff->{'status'}};
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004372 my $mode_chng = "";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004373 if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004374 # mode also for directories, so we cannot use $to_mode_str
4375 $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004376 }
4377 print "<td>" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004378 $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004379 hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
4380 -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
Jakub Narebskie8e41a92006-08-21 23:08:52 +02004381 "<td><span class=\"file_status $nstatus\">[$nstatus from " .
4382 $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004383 hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
4384 -class => "list"}, esc_path($diff->{'from_file'})) .
4385 " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
Luben Tuikov499faed2006-09-27 17:23:25 -07004386 "<td class=\"link\">";
Jakub Narebski241cc592006-10-31 17:36:27 +01004387 if ($action eq 'commitdiff') {
4388 # link to patch
4389 $patchno++;
4390 print $cgi->a({-href => "#patch$patchno"}, "patch") .
4391 " | ";
Jakub Narebski493e01d2007-05-07 01:10:06 +02004392 } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
Jakub Narebski241cc592006-10-31 17:36:27 +01004393 # "commit" view and modified file (not only pure rename or copy)
4394 print $cgi->a({-href => href(action=>"blobdiff",
Jakub Narebski493e01d2007-05-07 01:10:06 +02004395 hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
Jakub Narebski241cc592006-10-31 17:36:27 +01004396 hash_base=>$hash, hash_parent_base=>$parent,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004397 file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
Jakub Narebski241cc592006-10-31 17:36:27 +01004398 "diff") .
4399 " | ";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004400 }
Jakub Narebski493e01d2007-05-07 01:10:06 +02004401 print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
4402 hash_base=>$parent, file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004403 "blob") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004404 if ($have_blame) {
Jakub Narebski897d1d22006-11-19 22:51:39 +01004405 print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004406 file_name=>$diff->{'to_file'})},
Jakub Narebski897d1d22006-11-19 22:51:39 +01004407 "blame") . " | ";
Junio C Hamano2b2a8c72006-11-08 12:22:04 -08004408 }
Jakub Narebski897d1d22006-11-19 22:51:39 +01004409 print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
Jakub Narebski493e01d2007-05-07 01:10:06 +02004410 file_name=>$diff->{'to_file'})},
Jakub Narebskie7fb0222006-10-21 17:52:19 +02004411 "history");
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004412 print "</td>\n";
4413
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004414 } # we should not encounter Unmerged (U) or Unknown (X) status
4415 print "</tr>\n";
4416 }
Jakub Narebski47598d72007-06-08 13:24:56 +02004417 print "</tbody>" if $has_header;
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02004418 print "</table>\n";
4419}
4420
Jakub Narebskieee08902006-08-24 00:15:14 +02004421sub git_patchset_body {
Jakub Narebskie72c0ea2007-05-07 01:10:05 +02004422 my ($fd, $difftree, $hash, @hash_parents) = @_;
4423 my ($hash_parent) = $hash_parents[0];
Jakub Narebskieee08902006-08-24 00:15:14 +02004424
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004425 my $is_combined = (@hash_parents > 1);
Jakub Narebskieee08902006-08-24 00:15:14 +02004426 my $patch_idx = 0;
Martin Koegler4280cde2007-04-22 22:49:25 -07004427 my $patch_number = 0;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004428 my $patch_line;
Jakub Narebskife875852006-08-25 20:59:39 +02004429 my $diffinfo;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004430 my $to_name;
Jakub Narebski744d0ac2006-11-08 17:59:41 +01004431 my (%from, %to);
Jakub Narebskieee08902006-08-24 00:15:14 +02004432
4433 print "<div class=\"patchset\">\n";
4434
Jakub Narebski6d55f052006-11-18 23:35:39 +01004435 # skip to first patch
4436 while ($patch_line = <$fd>) {
Jakub Narebski157e43b2006-08-24 19:34:36 +02004437 chomp $patch_line;
Jakub Narebskieee08902006-08-24 00:15:14 +02004438
Jakub Narebski6d55f052006-11-18 23:35:39 +01004439 last if ($patch_line =~ m/^diff /);
4440 }
4441
4442 PATCH:
4443 while ($patch_line) {
Jakub Narebski6d55f052006-11-18 23:35:39 +01004444
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004445 # parse "git diff" header line
4446 if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
4447 # $1 is from_name, which we do not use
4448 $to_name = unquote($2);
4449 $to_name =~ s!^b/!!;
4450 } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
4451 # $1 is 'cc' or 'combined', which we do not use
4452 $to_name = unquote($2);
4453 } else {
4454 $to_name = undef;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004455 }
Jakub Narebski6d55f052006-11-18 23:35:39 +01004456
4457 # check if current patch belong to current raw line
4458 # and parse raw git-diff line if needed
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004459 if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
Jakub Narebski22065372007-05-12 12:42:32 +02004460 # this is continuation of a split patch
Jakub Narebski6d55f052006-11-18 23:35:39 +01004461 print "<div class=\"patch cont\">\n";
4462 } else {
4463 # advance raw git-diff output if needed
4464 $patch_idx++ if defined $diffinfo;
Jakub Narebskieee08902006-08-24 00:15:14 +02004465
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004466 # read and prepare patch information
4467 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
4468
Jakub Narebskicd030c32007-06-08 13:33:28 +02004469 # compact combined diff output can have some patches skipped
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004470 # find which patch (using pathname of result) we are at now;
4471 if ($is_combined) {
4472 while ($to_name ne $diffinfo->{'to_file'}) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02004473 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
4474 format_diff_cc_simplified($diffinfo, @hash_parents) .
4475 "</div>\n"; # class="patch"
4476
4477 $patch_idx++;
4478 $patch_number++;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004479
4480 last if $patch_idx > $#$difftree;
4481 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02004482 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004483 }
Jakub Narebski711fa742007-09-08 21:49:11 +02004484
Jakub Narebski90921742007-06-08 13:27:42 +02004485 # modifies %from, %to hashes
4486 parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
Jakub Narebski5f855052007-05-17 22:54:28 +02004487
Jakub Narebski6d55f052006-11-18 23:35:39 +01004488 # this is first patch for raw difftree line with $patch_idx index
4489 # we index @$difftree array from 0, but number patches from 1
4490 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
Jakub Narebski744d0ac2006-11-08 17:59:41 +01004491 }
Jakub Narebskieee08902006-08-24 00:15:14 +02004492
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004493 # git diff header
4494 #assert($patch_line =~ m/^diff /) if DEBUG;
4495 #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
4496 $patch_number++;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004497 # print "git diff" header
Jakub Narebski90921742007-06-08 13:27:42 +02004498 print format_git_diff_header_line($patch_line, $diffinfo,
4499 \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02004500
Jakub Narebski6d55f052006-11-18 23:35:39 +01004501 # print extended diff header
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004502 print "<div class=\"diff extended_header\">\n";
Jakub Narebski6d55f052006-11-18 23:35:39 +01004503 EXTENDED_HEADER:
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004504 while ($patch_line = <$fd>) {
4505 chomp $patch_line;
4506
4507 last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
4508
Jakub Narebski90921742007-06-08 13:27:42 +02004509 print format_extended_diff_header_line($patch_line, $diffinfo,
4510 \%from, \%to);
Jakub Narebski6d55f052006-11-18 23:35:39 +01004511 }
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004512 print "</div>\n"; # class="diff extended_header"
Jakub Narebskie4e4f822006-08-25 21:04:13 +02004513
Jakub Narebski6d55f052006-11-18 23:35:39 +01004514 # from-file/to-file diff header
Jakub Narebski0bdb28c2007-01-10 00:07:43 +01004515 if (! $patch_line) {
4516 print "</div>\n"; # class="patch"
4517 last PATCH;
4518 }
Jakub Narebski66399ef2007-01-07 02:52:25 +01004519 next PATCH if ($patch_line =~ m/^diff /);
Jakub Narebski6d55f052006-11-18 23:35:39 +01004520 #assert($patch_line =~ m/^---/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004521
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004522 my $last_patch_line = $patch_line;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004523 $patch_line = <$fd>;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004524 chomp $patch_line;
Jakub Narebski90921742007-06-08 13:27:42 +02004525 #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
Jakub Narebski6d55f052006-11-18 23:35:39 +01004526
Jakub Narebski90921742007-06-08 13:27:42 +02004527 print format_diff_from_to_header($last_patch_line, $patch_line,
Jakub Narebski91af4ce2007-06-08 13:32:44 +02004528 $diffinfo, \%from, \%to,
4529 @hash_parents);
Jakub Narebski6d55f052006-11-18 23:35:39 +01004530
4531 # the patch itself
4532 LINE:
4533 while ($patch_line = <$fd>) {
4534 chomp $patch_line;
4535
4536 next PATCH if ($patch_line =~ m/^diff /);
4537
Jakub Narebski59e3b142006-11-18 23:35:40 +01004538 print format_diff_line($patch_line, \%from, \%to);
Jakub Narebskieee08902006-08-24 00:15:14 +02004539 }
Jakub Narebskieee08902006-08-24 00:15:14 +02004540
Jakub Narebski6d55f052006-11-18 23:35:39 +01004541 } continue {
4542 print "</div>\n"; # class="patch"
Jakub Narebskieee08902006-08-24 00:15:14 +02004543 }
Jakub Narebskid26c4262007-05-17 00:05:55 +02004544
Ralf Wildenhues22e5e582010-08-22 13:12:12 +02004545 # for compact combined (--cc) format, with chunk and patch simplification
4546 # the patchset might be empty, but there might be unprocessed raw lines
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004547 for (++$patch_idx if $patch_number > 0;
Jakub Narebskicd030c32007-06-08 13:33:28 +02004548 $patch_idx < @$difftree;
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004549 ++$patch_idx) {
Jakub Narebskicd030c32007-06-08 13:33:28 +02004550 # read and prepare patch information
Jakub Narebski0cec6db2007-10-30 01:35:05 +01004551 $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
Jakub Narebskicd030c32007-06-08 13:33:28 +02004552
4553 # generate anchor for "patch" links in difftree / whatchanged part
4554 print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
4555 format_diff_cc_simplified($diffinfo, @hash_parents) .
4556 "</div>\n"; # class="patch"
4557
4558 $patch_number++;
4559 }
4560
Jakub Narebskid26c4262007-05-17 00:05:55 +02004561 if ($patch_number == 0) {
4562 if (@hash_parents > 1) {
4563 print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
4564 } else {
4565 print "<div class=\"diff nodifferences\">No differences found</div>\n";
4566 }
4567 }
Jakub Narebskieee08902006-08-24 00:15:14 +02004568
4569 print "</div>\n"; # class="patchset"
4570}
4571
4572# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4573
Jakub Narebski69913412008-06-10 19:21:01 +02004574# fills project list info (age, description, owner, forks) for each
4575# project in the list, removing invalid projects from returned list
4576# NOTE: modifies $projlist, but does not remove entries from it
4577sub fill_project_list_info {
4578 my ($projlist, $check_forks) = @_;
Petr Baudise30496d2006-10-24 05:33:17 +02004579 my @projects;
Jakub Narebski69913412008-06-10 19:21:01 +02004580
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004581 my $show_ctags = gitweb_check_feature('ctags');
Jakub Narebski69913412008-06-10 19:21:01 +02004582 PROJECT:
Petr Baudise30496d2006-10-24 05:33:17 +02004583 foreach my $pr (@$projlist) {
Jakub Narebski69913412008-06-10 19:21:01 +02004584 my (@activity) = git_get_last_activity($pr->{'path'});
4585 unless (@activity) {
4586 next PROJECT;
Petr Baudise30496d2006-10-24 05:33:17 +02004587 }
Jakub Narebski69913412008-06-10 19:21:01 +02004588 ($pr->{'age'}, $pr->{'age_string'}) = @activity;
Petr Baudise30496d2006-10-24 05:33:17 +02004589 if (!defined $pr->{'descr'}) {
4590 my $descr = git_get_project_description($pr->{'path'}) || "";
Jakub Narebski69913412008-06-10 19:21:01 +02004591 $descr = to_utf8($descr);
4592 $pr->{'descr_long'} = $descr;
Michael Hendricks55feb122007-07-04 18:36:48 -06004593 $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
Petr Baudise30496d2006-10-24 05:33:17 +02004594 }
4595 if (!defined $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02004596 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
Petr Baudise30496d2006-10-24 05:33:17 +02004597 }
4598 if ($check_forks) {
4599 my $pname = $pr->{'path'};
Junio C Hamano83ee94c2006-11-07 22:37:17 -08004600 if (($pname =~ s/\.git$//) &&
4601 ($pname !~ /\/$/) &&
4602 (-d "$projectroot/$pname")) {
4603 $pr->{'forks'} = "-d $projectroot/$pname";
Jakub Narebski3278fbc2009-05-11 19:37:28 +02004604 } else {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08004605 $pr->{'forks'} = 0;
4606 }
Petr Baudise30496d2006-10-24 05:33:17 +02004607 }
Petr Baudisaed93de2008-10-02 17:13:02 +02004608 $show_ctags and $pr->{'ctags'} = git_get_project_ctags($pr->{'path'});
Petr Baudise30496d2006-10-24 05:33:17 +02004609 push @projects, $pr;
4610 }
4611
Jakub Narebski69913412008-06-10 19:21:01 +02004612 return @projects;
4613}
4614
Petr Baudis6b28da62008-09-25 18:48:37 +02004615# print 'sort by' <th> element, generating 'sort by $name' replay link
4616# if that order is not selected
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004617sub print_sort_th {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004618 print format_sort_th(@_);
4619}
4620
4621sub format_sort_th {
Petr Baudis6b28da62008-09-25 18:48:37 +02004622 my ($name, $order, $header) = @_;
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004623 my $sort_th = "";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004624 $header ||= ucfirst($name);
4625
4626 if ($order eq $name) {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004627 $sort_th .= "<th>$header</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004628 } else {
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004629 $sort_th .= "<th>" .
4630 $cgi->a({-href => href(-replay=>1, order=>$name),
4631 -class => "header"}, $header) .
4632 "</th>\n";
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004633 }
John 'Warthog9' Hawley1ee4b4e2010-01-30 23:30:43 +01004634
4635 return $sort_th;
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004636}
4637
Jakub Narebski69913412008-06-10 19:21:01 +02004638sub git_project_list_body {
Petr Baudis42326112008-10-02 17:17:01 +02004639 # actually uses global variable $project
Jakub Narebski69913412008-06-10 19:21:01 +02004640 my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
4641
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004642 my $check_forks = gitweb_check_feature('forks');
Jakub Narebski69913412008-06-10 19:21:01 +02004643 my @projects = fill_project_list_info($projlist, $check_forks);
4644
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02004645 $order ||= $default_projects_order;
Petr Baudise30496d2006-10-24 05:33:17 +02004646 $from = 0 unless defined $from;
4647 $to = $#projects if (!defined $to || $#projects < $to);
4648
Petr Baudis6b28da62008-09-25 18:48:37 +02004649 my %order_info = (
4650 project => { key => 'path', type => 'str' },
4651 descr => { key => 'descr_long', type => 'str' },
4652 owner => { key => 'owner', type => 'str' },
4653 age => { key => 'age', type => 'num' }
4654 );
4655 my $oi = $order_info{$order};
4656 if ($oi->{'type'} eq 'str') {
4657 @projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @projects;
4658 } else {
4659 @projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @projects;
4660 }
4661
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08004662 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02004663 if ($show_ctags) {
4664 my %ctags;
4665 foreach my $p (@projects) {
4666 foreach my $ct (keys %{$p->{'ctags'}}) {
4667 $ctags{$ct} += $p->{'ctags'}->{$ct};
4668 }
4669 }
4670 my $cloud = git_populate_project_tagcloud(\%ctags);
4671 print git_show_project_tagcloud($cloud, 64);
4672 }
4673
Petr Baudise30496d2006-10-24 05:33:17 +02004674 print "<table class=\"project_list\">\n";
4675 unless ($no_header) {
4676 print "<tr>\n";
4677 if ($check_forks) {
4678 print "<th></th>\n";
4679 }
Petr Baudis6b28da62008-09-25 18:48:37 +02004680 print_sort_th('project', $order, 'Project');
4681 print_sort_th('descr', $order, 'Description');
4682 print_sort_th('owner', $order, 'Owner');
4683 print_sort_th('age', $order, 'Last Change');
Jakub Narebski7da0f3a2008-06-10 19:21:44 +02004684 print "<th></th>\n" . # for links
Petr Baudise30496d2006-10-24 05:33:17 +02004685 "</tr>\n";
4686 }
4687 my $alternate = 1;
Petr Baudisaed93de2008-10-02 17:13:02 +02004688 my $tagfilter = $cgi->param('by_tag');
Petr Baudise30496d2006-10-24 05:33:17 +02004689 for (my $i = $from; $i <= $to; $i++) {
4690 my $pr = $projects[$i];
Petr Baudis42326112008-10-02 17:17:01 +02004691
Petr Baudisaed93de2008-10-02 17:13:02 +02004692 next if $tagfilter and $show_ctags and not grep { lc $_ eq lc $tagfilter } keys %{$pr->{'ctags'}};
Petr Baudis0d1d1542008-10-03 09:29:45 +02004693 next if $searchtext and not $pr->{'path'} =~ /$searchtext/
4694 and not $pr->{'descr_long'} =~ /$searchtext/;
4695 # Weed out forks or non-matching entries of search
Petr Baudis42326112008-10-02 17:17:01 +02004696 if ($check_forks) {
4697 my $forkbase = $project; $forkbase ||= ''; $forkbase =~ s#\.git$#/#;
4698 $forkbase="^$forkbase" if $forkbase;
Petr Baudis0d1d1542008-10-03 09:29:45 +02004699 next if not $searchtext and not $tagfilter and $show_ctags
4700 and $pr->{'path'} =~ m#$forkbase.*/.*#; # regexp-safe
Petr Baudis42326112008-10-02 17:17:01 +02004701 }
4702
Petr Baudise30496d2006-10-24 05:33:17 +02004703 if ($alternate) {
4704 print "<tr class=\"dark\">\n";
4705 } else {
4706 print "<tr class=\"light\">\n";
4707 }
4708 $alternate ^= 1;
4709 if ($check_forks) {
4710 print "<td>";
4711 if ($pr->{'forks'}) {
Junio C Hamano83ee94c2006-11-07 22:37:17 -08004712 print "<!-- $pr->{'forks'} -->\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004713 print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
4714 }
4715 print "</td>\n";
4716 }
4717 print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
4718 -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
Jakub Narebskie88ce8a2006-11-26 02:18:26 +01004719 "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
4720 -class => "list", -title => $pr->{'descr_long'}},
4721 esc_html($pr->{'descr'})) . "</td>\n" .
David Symondsd3cd2492007-10-23 11:31:23 +10004722 "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
Petr Baudise30496d2006-10-24 05:33:17 +02004723 print "<td class=\"". age_class($pr->{'age'}) . "\">" .
Jakub Narebski785cdea2007-05-13 12:39:22 +02004724 (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
Petr Baudise30496d2006-10-24 05:33:17 +02004725 "<td class=\"link\">" .
4726 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
Alexandre Julliardfaa1bbf2006-11-15 21:37:50 +01004727 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
Petr Baudise30496d2006-10-24 05:33:17 +02004728 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
4729 $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
4730 ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
4731 "</td>\n" .
4732 "</tr>\n";
4733 }
4734 if (defined $extra) {
4735 print "<tr>\n";
4736 if ($check_forks) {
4737 print "<td></td>\n";
4738 }
4739 print "<td colspan=\"5\">$extra</td>\n" .
4740 "</tr>\n";
4741 }
4742 print "</table>\n";
4743}
4744
Jakub Narebski42671ca2009-11-13 02:02:12 +01004745sub git_log_body {
4746 # uses global variable $project
4747 my ($commitlist, $from, $to, $refs, $extra) = @_;
4748
4749 $from = 0 unless defined $from;
4750 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
4751
4752 for (my $i = 0; $i <= $to; $i++) {
4753 my %co = %{$commitlist->[$i]};
4754 next if !%co;
4755 my $commit = $co{'id'};
4756 my $ref = format_ref_marker($refs, $commit);
4757 my %ad = parse_date($co{'author_epoch'});
4758 git_print_header_div('commit',
4759 "<span class=\"age\">$co{'age_string'}</span>" .
4760 esc_html($co{'title'}) . $ref,
4761 $commit);
4762 print "<div class=\"title_text\">\n" .
4763 "<div class=\"log_link\">\n" .
4764 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
4765 " | " .
4766 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
4767 " | " .
4768 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
4769 "<br/>\n" .
4770 "</div>\n";
4771 git_print_authorship(\%co, -tag => 'span');
4772 print "<br/>\n</div>\n";
4773
4774 print "<div class=\"log_body\">\n";
4775 git_print_log($co{'comment'}, -final_empty_line=> 1);
4776 print "</div>\n";
4777 }
4778 if ($extra) {
4779 print "<div class=\"page_nav\">\n";
4780 print "$extra\n";
4781 print "</div>\n";
4782 }
4783}
4784
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004785sub git_shortlog_body {
4786 # uses global variable $project
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004787 my ($commitlist, $from, $to, $refs, $extra) = @_;
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05304788
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004789 $from = 0 unless defined $from;
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004790 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004791
Jakub Narebski591ebf62007-11-19 14:16:11 +01004792 print "<table class=\"shortlog\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004793 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004794 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons190d7fd2006-12-24 14:31:44 +00004795 my %co = %{$commitlist->[$i]};
4796 my $commit = $co{'id'};
Jakub Narebski847e01f2006-08-14 02:05:47 +02004797 my $ref = format_ref_marker($refs, $commit);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004798 if ($alternate) {
4799 print "<tr class=\"dark\">\n";
4800 } else {
4801 print "<tr class=\"light\">\n";
4802 }
4803 $alternate ^= 1;
4804 # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
4805 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004806 format_author_html('td', \%co, 10) . "<td>";
Jakub Narebski952c65f2006-08-22 16:52:50 +02004807 print format_subject_html($co{'title'}, $co{'title_short'},
4808 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004809 print "</td>\n" .
4810 "<td class=\"link\">" .
Petr Baudis4777b012006-10-24 05:36:10 +02004811 $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
Petr Baudis35749ae2006-09-22 03:19:44 +02004812 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
Petr Baudis55ff35c2006-10-06 15:57:52 +02004813 $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02004814 my $snapshot_links = format_snapshot_links($commit);
4815 if (defined $snapshot_links) {
4816 print " | " . $snapshot_links;
Petr Baudis55ff35c2006-10-06 15:57:52 +02004817 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05304818 print "</td>\n" .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004819 "</tr>\n";
4820 }
4821 if (defined $extra) {
4822 print "<tr>\n" .
4823 "<td colspan=\"4\">$extra</td>\n" .
4824 "</tr>\n";
4825 }
4826 print "</table>\n";
4827}
4828
Jakub Narebski581860e2006-08-14 02:09:08 +02004829sub git_history_body {
4830 # Warning: assumes constant type (blob or tree) during history
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004831 my ($commitlist, $from, $to, $refs, $extra,
4832 $file_name, $file_hash, $ftype) = @_;
Jakub Narebski8be68352006-09-11 00:36:04 +02004833
4834 $from = 0 unless defined $from;
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004835 $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
Jakub Narebski581860e2006-08-14 02:09:08 +02004836
Jakub Narebski591ebf62007-11-19 14:16:11 +01004837 print "<table class=\"history\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004838 my $alternate = 1;
Jakub Narebski8be68352006-09-11 00:36:04 +02004839 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004840 my %co = %{$commitlist->[$i]};
Jakub Narebski581860e2006-08-14 02:09:08 +02004841 if (!%co) {
4842 next;
4843 }
Robert Fitzsimonsa8b983b2006-12-24 14:31:48 +00004844 my $commit = $co{'id'};
Jakub Narebski581860e2006-08-14 02:09:08 +02004845
4846 my $ref = format_ref_marker($refs, $commit);
4847
4848 if ($alternate) {
4849 print "<tr class=\"dark\">\n";
4850 } else {
4851 print "<tr class=\"light\">\n";
4852 }
4853 $alternate ^= 1;
4854 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02004855 # shortlog: format_author_html('td', \%co, 10)
4856 format_author_html('td', \%co, 15, 3) . "<td>";
Jakub Narebski581860e2006-08-14 02:09:08 +02004857 # originally git_history used chop_str($co{'title'}, 50)
Jakub Narebski952c65f2006-08-22 16:52:50 +02004858 print format_subject_html($co{'title'}, $co{'title_short'},
4859 href(action=>"commit", hash=>$commit), $ref);
Jakub Narebski581860e2006-08-14 02:09:08 +02004860 print "</td>\n" .
4861 "<td class=\"link\">" .
Luben Tuikov6d81c5a2006-09-28 17:21:07 -07004862 $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
4863 $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
Jakub Narebski581860e2006-08-14 02:09:08 +02004864
4865 if ($ftype eq 'blob') {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01004866 my $blob_current = $file_hash;
Jakub Narebski581860e2006-08-14 02:09:08 +02004867 my $blob_parent = git_get_hash_by_path($commit, $file_name);
4868 if (defined $blob_current && defined $blob_parent &&
4869 $blob_current ne $blob_parent) {
4870 print " | " .
Jakub Narebski420e92f2006-08-24 23:53:54 +02004871 $cgi->a({-href => href(action=>"blobdiff",
4872 hash=>$blob_current, hash_parent=>$blob_parent,
4873 hash_base=>$hash_base, hash_parent_base=>$commit,
4874 file_name=>$file_name)},
Jakub Narebski581860e2006-08-14 02:09:08 +02004875 "diff to current");
4876 }
4877 }
4878 print "</td>\n" .
4879 "</tr>\n";
4880 }
4881 if (defined $extra) {
4882 print "<tr>\n" .
4883 "<td colspan=\"4\">$extra</td>\n" .
4884 "</tr>\n";
4885 }
4886 print "</table>\n";
4887}
4888
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004889sub git_tags_body {
4890 # uses global variable $project
4891 my ($taglist, $from, $to, $extra) = @_;
4892 $from = 0 unless defined $from;
4893 $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
4894
Jakub Narebski591ebf62007-11-19 14:16:11 +01004895 print "<table class=\"tags\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004896 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004897 for (my $i = $from; $i <= $to; $i++) {
4898 my $entry = $taglist->[$i];
4899 my %tag = %$entry;
Jakub Narebskicd146402006-11-02 20:23:11 +01004900 my $comment = $tag{'subject'};
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004901 my $comment_short;
4902 if (defined $comment) {
4903 $comment_short = chop_str($comment, 30, 5);
4904 }
4905 if ($alternate) {
4906 print "<tr class=\"dark\">\n";
4907 } else {
4908 print "<tr class=\"light\">\n";
4909 }
4910 $alternate ^= 1;
Jakub Narebski27dd1a82007-01-03 22:54:29 +01004911 if (defined $tag{'age'}) {
4912 print "<td><i>$tag{'age'}</i></td>\n";
4913 } else {
4914 print "<td></td>\n";
4915 }
4916 print "<td>" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004917 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
Jakub Narebski63e42202006-08-22 12:38:59 +02004918 -class => "list name"}, esc_html($tag{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004919 "</td>\n" .
4920 "<td>";
4921 if (defined $comment) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02004922 print format_subject_html($comment, $comment_short,
4923 href(action=>"tag", hash=>$tag{'id'}));
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004924 }
4925 print "</td>\n" .
4926 "<td class=\"selflink\">";
4927 if ($tag{'type'} eq "tag") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004928 print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004929 } else {
4930 print "&nbsp;";
4931 }
4932 print "</td>\n" .
4933 "<td class=\"link\">" . " | " .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004934 $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004935 if ($tag{'reftype'} eq "commit") {
Jakub Narebskibf901f82007-12-15 15:40:28 +01004936 print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'fullname'})}, "shortlog") .
4937 " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'fullname'})}, "log");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004938 } elsif ($tag{'reftype'} eq "blob") {
Martin Waitz1c2a4f52006-08-16 00:24:30 +02004939 print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004940 }
4941 print "</td>\n" .
4942 "</tr>";
4943 }
4944 if (defined $extra) {
4945 print "<tr>\n" .
4946 "<td colspan=\"5\">$extra</td>\n" .
4947 "</tr>\n";
4948 }
4949 print "</table>\n";
4950}
4951
4952sub git_heads_body {
4953 # uses global variable $project
Jakub Narebski120ddde2006-09-19 14:33:22 +02004954 my ($headlist, $head, $from, $to, $extra) = @_;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004955 $from = 0 unless defined $from;
Jakub Narebski120ddde2006-09-19 14:33:22 +02004956 $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004957
Jakub Narebski591ebf62007-11-19 14:16:11 +01004958 print "<table class=\"heads\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07004959 my $alternate = 1;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004960 for (my $i = $from; $i <= $to; $i++) {
Jakub Narebski120ddde2006-09-19 14:33:22 +02004961 my $entry = $headlist->[$i];
Jakub Narebskicd146402006-11-02 20:23:11 +01004962 my %ref = %$entry;
4963 my $curr = $ref{'id'} eq $head;
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004964 if ($alternate) {
4965 print "<tr class=\"dark\">\n";
4966 } else {
4967 print "<tr class=\"light\">\n";
4968 }
4969 $alternate ^= 1;
Jakub Narebskicd146402006-11-02 20:23:11 +01004970 print "<td><i>$ref{'age'}</i></td>\n" .
4971 ($curr ? "<td class=\"current_head\">" : "<td>") .
Jakub Narebskibf901f82007-12-15 15:40:28 +01004972 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'}),
Jakub Narebskicd146402006-11-02 20:23:11 +01004973 -class => "list name"},esc_html($ref{'name'})) .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004974 "</td>\n" .
4975 "<td class=\"link\">" .
Jakub Narebskibf901f82007-12-15 15:40:28 +01004976 $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'fullname'})}, "shortlog") . " | " .
4977 $cgi->a({-href => href(action=>"log", hash=>$ref{'fullname'})}, "log") . " | " .
Giuseppe Bilotta9e70e152010-11-11 13:26:08 +01004978 $cgi->a({-href => href(action=>"tree", hash=>$ref{'fullname'}, hash_base=>$ref{'fullname'})}, "tree") .
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02004979 "</td>\n" .
4980 "</tr>";
4981 }
4982 if (defined $extra) {
4983 print "<tr>\n" .
4984 "<td colspan=\"3\">$extra</td>\n" .
4985 "</tr>\n";
4986 }
4987 print "</table>\n";
4988}
4989
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004990sub git_search_grep_body {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004991 my ($commitlist, $from, $to, $extra) = @_;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004992 $from = 0 unless defined $from;
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004993 $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004994
Jakub Narebski591ebf62007-11-19 14:16:11 +01004995 print "<table class=\"commit_search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004996 my $alternate = 1;
4997 for (my $i = $from; $i <= $to; $i++) {
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00004998 my %co = %{$commitlist->[$i]};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00004999 if (!%co) {
5000 next;
5001 }
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00005002 my $commit = $co{'id'};
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005003 if ($alternate) {
5004 print "<tr class=\"dark\">\n";
5005 } else {
5006 print "<tr class=\"light\">\n";
5007 }
5008 $alternate ^= 1;
5009 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02005010 format_author_html('td', \%co, 15, 5) .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005011 "<td>" .
Junio C Hamanobe8b9062008-02-22 17:33:47 +01005012 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
5013 -class => "list subject"},
5014 chop_and_escape_str($co{'title'}, 50) . "<br/>");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005015 my $comment = $co{'comment'};
5016 foreach my $line (@$comment) {
Jakub Narebski6dfbb302008-03-02 16:57:14 +01005017 if ($line =~ m/^(.*?)($search_regexp)(.*)$/i) {
Junio C Hamanobe8b9062008-02-22 17:33:47 +01005018 my ($lead, $match, $trail) = ($1, $2, $3);
Jakub Narebskib8d97d02008-02-25 21:07:57 +01005019 $match = chop_str($match, 70, 5, 'center');
5020 my $contextlen = int((80 - length($match))/2);
5021 $contextlen = 30 if ($contextlen > 30);
5022 $lead = chop_str($lead, $contextlen, 10, 'left');
5023 $trail = chop_str($trail, $contextlen, 10, 'right');
Junio C Hamanobe8b9062008-02-22 17:33:47 +01005024
5025 $lead = esc_html($lead);
5026 $match = esc_html($match);
5027 $trail = esc_html($trail);
5028
5029 print "$lead<span class=\"match\">$match</span>$trail<br />";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005030 }
5031 }
5032 print "</td>\n" .
5033 "<td class=\"link\">" .
5034 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
5035 " | " .
Denis Chengf1fe8f52007-11-26 20:42:06 +08005036 $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
5037 " | " .
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00005038 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
5039 print "</td>\n" .
5040 "</tr>\n";
5041 }
5042 if (defined $extra) {
5043 print "<tr>\n" .
5044 "<td colspan=\"3\">$extra</td>\n" .
5045 "</tr>\n";
5046 }
5047 print "</table>\n";
5048}
5049
Jakub Narebski717b8312006-07-31 21:22:15 +02005050## ======================================================================
5051## ======================================================================
5052## actions
5053
Jakub Narebski717b8312006-07-31 21:22:15 +02005054sub git_project_list {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02005055 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02005056 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005057 die_error(400, "Unknown order parameter");
Jakub Narebski6326b602006-08-01 02:59:12 +02005058 }
5059
Jakub Narebski847e01f2006-08-14 02:05:47 +02005060 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02005061 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005062 die_error(404, "No projects found");
Jakub Narebski717b8312006-07-31 21:22:15 +02005063 }
Jakub Narebski6326b602006-08-01 02:59:12 +02005064
Jakub Narebski717b8312006-07-31 21:22:15 +02005065 git_header_html();
John 'Warthog9' Hawley24d4afc2010-01-30 23:30:41 +01005066 if (defined $home_text && -f $home_text) {
Jakub Narebski717b8312006-07-31 21:22:15 +02005067 print "<div class=\"index_include\">\n";
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01005068 insert_file($home_text);
Jakub Narebski717b8312006-07-31 21:22:15 +02005069 print "</div>\n";
5070 }
Petr Baudis0d1d1542008-10-03 09:29:45 +02005071 print $cgi->startform(-method => "get") .
5072 "<p class=\"projsearch\">Search:\n" .
5073 $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
5074 "</p>" .
5075 $cgi->end_form() . "\n";
Petr Baudise30496d2006-10-24 05:33:17 +02005076 git_project_list_body(\@list, $order);
5077 git_footer_html();
5078}
5079
5080sub git_forks {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02005081 my $order = $input_params{'order'};
Frank Lichtenheldb06dcf82007-04-06 23:58:24 +02005082 if (defined $order && $order !~ m/none|project|descr|owner|age/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005083 die_error(400, "Unknown order parameter");
Jakub Narebski717b8312006-07-31 21:22:15 +02005084 }
Petr Baudise30496d2006-10-24 05:33:17 +02005085
5086 my @list = git_get_projects_list($project);
5087 if (!@list) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005088 die_error(404, "No forks found");
Jakub Narebski717b8312006-07-31 21:22:15 +02005089 }
Petr Baudise30496d2006-10-24 05:33:17 +02005090
5091 git_header_html();
5092 git_print_page_nav('','');
5093 git_print_header_div('summary', "$project forks");
5094 git_project_list_body(\@list, $order);
Jakub Narebski717b8312006-07-31 21:22:15 +02005095 git_footer_html();
5096}
5097
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02005098sub git_project_index {
Petr Baudise30496d2006-10-24 05:33:17 +02005099 my @projects = git_get_projects_list($project);
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02005100
5101 print $cgi->header(
5102 -type => 'text/plain',
5103 -charset => 'utf-8',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02005104 -content_disposition => 'inline; filename="index.aux"');
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02005105
5106 foreach my $pr (@projects) {
5107 if (!exists $pr->{'owner'}) {
Miklos Vajna76e4f5d2007-07-04 00:11:23 +02005108 $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
Jakub Narebskifc2b2be2006-09-15 04:56:03 +02005109 }
5110
5111 my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
5112 # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
5113 $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
5114 $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
5115 $path =~ s/ /\+/g;
5116 $owner =~ s/ /\+/g;
5117
5118 print "$path $owner\n";
5119 }
5120}
5121
Kay Sieversede5e102005-08-07 20:23:12 +02005122sub git_summary {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005123 my $descr = git_get_project_description($project) || "none";
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00005124 my %co = parse_commit("HEAD");
Jakub Narebski785cdea2007-05-13 12:39:22 +02005125 my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
Robert Fitzsimonsa979d122006-12-22 19:38:15 +00005126 my $head = $co{'id'};
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01005127 my $remote_heads = gitweb_check_feature('remote_heads');
Kay Sieversede5e102005-08-07 20:23:12 +02005128
Jakub Narebski1e0cf032006-08-14 02:10:06 +02005129 my $owner = git_get_project_owner($project);
Kay Sieversede5e102005-08-07 20:23:12 +02005130
Jakub Narebskicd146402006-11-02 20:23:11 +01005131 my $refs = git_get_references();
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00005132 # These get_*_list functions return one more to allow us to see if
5133 # there are more ...
5134 my @taglist = git_get_tags_list(16);
5135 my @headlist = git_get_heads_list(16);
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01005136 my @remotelist = $remote_heads ? git_get_heads_list(16, 'remotes') : ();
Petr Baudise30496d2006-10-24 05:33:17 +02005137 my @forklist;
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005138 my $check_forks = gitweb_check_feature('forks');
Junio C Hamano5dd5ed02006-11-07 22:00:45 -08005139
5140 if ($check_forks) {
Petr Baudise30496d2006-10-24 05:33:17 +02005141 @forklist = git_get_projects_list($project);
5142 }
Jakub Narebski120ddde2006-09-19 14:33:22 +02005143
Kay Sieversede5e102005-08-07 20:23:12 +02005144 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005145 git_print_page_nav('summary','', $head);
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005146
Kay Sievers19806692005-08-07 20:26:27 +02005147 print "<div class=\"title\">&nbsp;</div>\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01005148 print "<table class=\"projects_list\">\n" .
Petr Baudisa4761422008-10-02 16:25:05 +02005149 "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
5150 "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02005151 if (defined $cd{'rfc2822'}) {
Petr Baudisa4761422008-10-02 16:25:05 +02005152 print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
Jakub Narebski785cdea2007-05-13 12:39:22 +02005153 }
5154
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02005155 # use per project git URL list in $projectroot/$project/cloneurl
5156 # or make project git URL from git base URL and project name
Jakub Narebski19a87212006-08-15 23:03:17 +02005157 my $url_tag = "URL";
Jakub Narebskie79ca7c2006-08-16 14:50:34 +02005158 my @url_list = git_get_project_url_list($project);
5159 @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
5160 foreach my $git_url (@url_list) {
5161 next unless $git_url;
Petr Baudisa4761422008-10-02 16:25:05 +02005162 print "<tr class=\"metadata_url\"><td>$url_tag</td><td>$git_url</td></tr>\n";
Jakub Narebski19a87212006-08-15 23:03:17 +02005163 $url_tag = "";
5164 }
Petr Baudisaed93de2008-10-02 17:13:02 +02005165
5166 # Tag cloud
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005167 my $show_ctags = gitweb_check_feature('ctags');
Petr Baudisaed93de2008-10-02 17:13:02 +02005168 if ($show_ctags) {
5169 my $ctags = git_get_project_ctags($project);
5170 my $cloud = git_populate_project_tagcloud($ctags);
5171 print "<tr id=\"metadata_ctags\"><td>Content tags:<br />";
5172 print "</td>\n<td>" unless %$ctags;
5173 print "<form action=\"$show_ctags\" method=\"post\"><input type=\"hidden\" name=\"p\" value=\"$project\" />Add: <input type=\"text\" name=\"t\" size=\"8\" /></form>";
5174 print "</td>\n<td>" if %$ctags;
5175 print git_show_project_tagcloud($cloud, 48);
5176 print "</td></tr>";
5177 }
5178
Jakub Narebski19a87212006-08-15 23:03:17 +02005179 print "</table>\n";
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005180
Matt McCutchen7e1100e2009-02-07 19:00:09 -05005181 # If XSS prevention is on, we don't include README.html.
5182 # TODO: Allow a readme in some safe format.
5183 if (!$prevent_xss && -s "$projectroot/$project/README.html") {
Jakub Narebski2dcb5e12008-12-01 19:01:42 +01005184 print "<div class=\"title\">readme</div>\n" .
5185 "<div class=\"readme\">\n";
5186 insert_file("$projectroot/$project/README.html");
5187 print "\n</div>\n"; # class="readme"
Petr Baudis447ef092006-10-24 05:23:46 +02005188 }
5189
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00005190 # we need to request one more than 16 (0..15) to check if
5191 # those 16 are all
Jakub Narebski785cdea2007-05-13 12:39:22 +02005192 my @commitlist = $head ? parse_commits($head, 17) : ();
5193 if (@commitlist) {
5194 git_print_header_div('shortlog');
5195 git_shortlog_body(\@commitlist, 0, 15, $refs,
5196 $#commitlist <= 15 ? undef :
5197 $cgi->a({-href => href(action=>"shortlog")}, "..."));
5198 }
Kay Sieversede5e102005-08-07 20:23:12 +02005199
Jakub Narebski120ddde2006-09-19 14:33:22 +02005200 if (@taglist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005201 git_print_header_div('tags');
Jakub Narebski120ddde2006-09-19 14:33:22 +02005202 git_tags_body(\@taglist, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00005203 $#taglist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005204 $cgi->a({-href => href(action=>"tags")}, "..."));
Kay Sieversede5e102005-08-07 20:23:12 +02005205 }
Kay Sievers0db37972005-08-07 20:24:35 +02005206
Jakub Narebski120ddde2006-09-19 14:33:22 +02005207 if (@headlist) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005208 git_print_header_div('heads');
Jakub Narebski120ddde2006-09-19 14:33:22 +02005209 git_heads_body(\@headlist, $head, 0, 15,
Robert Fitzsimons313ce8c2006-12-19 12:08:54 +00005210 $#headlist <= 15 ? undef :
Martin Waitz1c2a4f52006-08-16 00:24:30 +02005211 $cgi->a({-href => href(action=>"heads")}, "..."));
Kay Sievers0db37972005-08-07 20:24:35 +02005212 }
Jakub Narebski9f5dcb82006-07-31 11:22:13 +02005213
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01005214 if (@remotelist) {
5215 git_print_header_div('remotes');
5216 git_heads_body(\@remotelist, $head, 0, 15,
5217 $#remotelist <= 15 ? undef :
5218 $cgi->a({-href => href(action=>"remotes")}, "..."));
5219 }
5220
Petr Baudise30496d2006-10-24 05:33:17 +02005221 if (@forklist) {
5222 git_print_header_div('forks');
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02005223 git_project_list_body(\@forklist, 'age', 0, 15,
Robert Fitzsimonsaaca9672006-12-22 19:38:12 +00005224 $#forklist <= 15 ? undef :
Petr Baudise30496d2006-10-24 05:33:17 +02005225 $cgi->a({-href => href(action=>"forks")}, "..."),
Mike Ralphsonf04f27e2008-09-25 18:48:48 +02005226 'no_header');
Petr Baudise30496d2006-10-24 05:33:17 +02005227 }
5228
Kay Sieversede5e102005-08-07 20:23:12 +02005229 git_footer_html();
5230}
5231
Kay Sieversd8a20ba2005-08-07 20:28:53 +02005232sub git_tag {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005233 my %tag = parse_tag($hash);
Jakub Narebski198a2a82007-05-12 21:16:34 +02005234
5235 if (! %tag) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005236 die_error(404, "Unknown tag object");
Jakub Narebski198a2a82007-05-12 21:16:34 +02005237 }
5238
Anders Kaseorgd8a94802010-08-27 13:38:16 -04005239 my $head = git_get_head_hash($project);
5240 git_header_html();
5241 git_print_page_nav('','', $head,undef,$head);
Jakub Narebski847e01f2006-08-14 02:05:47 +02005242 git_print_header_div('commit', esc_html($tag{'name'}), $hash);
Kay Sieversd8a20ba2005-08-07 20:28:53 +02005243 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01005244 "<table class=\"object_header\">\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02005245 "<tr>\n" .
5246 "<td>object</td>\n" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005247 "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
5248 $tag{'object'}) . "</td>\n" .
5249 "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
5250 $tag{'type'}) . "</td>\n" .
Kay Sieverse4669df2005-08-08 00:02:39 +02005251 "</tr>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02005252 if (defined($tag{'author'})) {
Giuseppe Bilottaba924732009-06-30 00:00:50 +02005253 git_print_authorship_rows(\%tag, 'author');
Kay Sieversd8a20ba2005-08-07 20:28:53 +02005254 }
5255 print "</table>\n\n" .
5256 "</div>\n";
5257 print "<div class=\"page_body\">";
5258 my $comment = $tag{'comment'};
5259 foreach my $line (@$comment) {
Junio C Hamano70022432006-11-24 14:04:01 -08005260 chomp $line;
Jakub Narebski793c4002006-11-24 22:25:50 +01005261 print esc_html($line, -nbsp=>1) . "<br/>\n";
Kay Sieversd8a20ba2005-08-07 20:28:53 +02005262 }
5263 print "</div>\n";
5264 git_footer_html();
5265}
5266
Jakub Narebski4af819d2009-09-01 13:39:17 +02005267sub git_blame_common {
5268 my $format = shift || 'porcelain';
Jakub Narebskic4ccf612009-09-01 13:39:19 +02005269 if ($format eq 'porcelain' && $cgi->param('js')) {
5270 $format = 'incremental';
5271 $action = 'blame_incremental'; # for page title etc
5272 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005273
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005274 # permissions
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005275 gitweb_check_feature('blame')
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005276 or die_error(403, "Blame view not allowed");
Lea Wiemann074afaa2008-06-19 22:03:21 +02005277
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005278 # error checking
Lea Wiemann074afaa2008-06-19 22:03:21 +02005279 die_error(400, "No file name given") unless $file_name;
Jakub Narebski847e01f2006-08-14 02:05:47 +02005280 $hash_base ||= git_get_head_hash($project);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005281 die_error(404, "Couldn't find base commit") unless $hash_base;
Jakub Narebski847e01f2006-08-14 02:05:47 +02005282 my %co = parse_commit($hash_base)
Lea Wiemann074afaa2008-06-19 22:03:21 +02005283 or die_error(404, "Commit not found");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005284 my $ftype = "blob";
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005285 if (!defined $hash) {
5286 $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02005287 or die_error(404, "Error looking up file");
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005288 } else {
5289 $ftype = git_get_type($hash);
5290 if ($ftype !~ "blob") {
5291 die_error(400, "Object is not a blob");
5292 }
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005293 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005294
Jakub Narebski4af819d2009-09-01 13:39:17 +02005295 my $fd;
5296 if ($format eq 'incremental') {
5297 # get file contents (as base)
5298 open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
5299 or die_error(500, "Open git-cat-file failed");
5300 } elsif ($format eq 'data') {
5301 # run git-blame --incremental
5302 open $fd, "-|", git_cmd(), "blame", "--incremental",
5303 $hash_base, "--", $file_name
5304 or die_error(500, "Open git-blame --incremental failed");
5305 } else {
5306 # run git-blame --porcelain
5307 open $fd, "-|", git_cmd(), "blame", '-p',
5308 $hash_base, '--', $file_name
5309 or die_error(500, "Open git-blame --porcelain failed");
5310 }
5311
5312 # incremental blame data returns early
5313 if ($format eq 'data') {
5314 print $cgi->header(
5315 -type=>"text/plain", -charset => "utf-8",
5316 -status=> "200 OK");
5317 local $| = 1; # output autoflush
5318 print while <$fd>;
5319 close $fd
5320 or print "ERROR $!\n";
5321
5322 print 'END';
5323 if (defined $t0 && gitweb_check_feature('timed')) {
5324 print ' '.
5325 Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
5326 ' '.$number_of_git_cmds;
5327 }
5328 print "\n";
5329
5330 return;
5331 }
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005332
5333 # page header
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005334 git_header_html();
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005335 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01005336 $cgi->a({-href => href(action=>"blob", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02005337 "blob") .
Jakub Narebski87e573f2009-12-01 17:54:26 +01005338 " | ";
5339 if ($format eq 'incremental') {
5340 $formats_nav .=
5341 $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
5342 "blame") . " (non-incremental)";
5343 } else {
5344 $formats_nav .=
5345 $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
5346 "blame") . " (incremental)";
5347 }
5348 $formats_nav .=
Jakub Narebski952c65f2006-08-22 16:52:50 +02005349 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01005350 $cgi->a({-href => href(action=>"history", -replay=>1)},
5351 "history") .
Petr Baudiscae18622006-09-22 03:19:41 +02005352 " | " .
Jakub Narebski4af819d2009-09-01 13:39:17 +02005353 $cgi->a({-href => href(action=>$action, file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02005354 "HEAD");
Jakub Narebski847e01f2006-08-14 02:05:47 +02005355 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
5356 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Luben Tuikov59fb1c92006-08-17 10:39:29 -07005357 git_print_page_path($file_name, $ftype, $hash_base);
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005358
5359 # page body
Jakub Narebski4af819d2009-09-01 13:39:17 +02005360 if ($format eq 'incremental') {
5361 print "<noscript>\n<div class=\"error\"><center><b>\n".
5362 "This page requires JavaScript to run.\n Use ".
Jakub Narebskic4ccf612009-09-01 13:39:19 +02005363 $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
Jakub Narebski4af819d2009-09-01 13:39:17 +02005364 'this page').
5365 " instead.\n".
5366 "</b></center></div>\n</noscript>\n";
5367
5368 print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
5369 }
5370
5371 print qq!<div class="page_body">\n!;
5372 print qq!<div id="progress_info">... / ...</div>\n!
5373 if ($format eq 'incremental');
5374 print qq!<table id="blame_table" class="blame" width="100%">\n!.
5375 #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
5376 qq!<thead>\n!.
5377 qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
5378 qq!</thead>\n!.
5379 qq!<tbody>\n!;
5380
Jakub Narebskiaef37682009-07-25 00:44:06 +02005381 my @rev_color = qw(light dark);
Luben Tuikovcc1bf972006-07-23 13:37:53 -07005382 my $num_colors = scalar(@rev_color);
5383 my $current_color = 0;
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005384
Jakub Narebski4af819d2009-09-01 13:39:17 +02005385 if ($format eq 'incremental') {
5386 my $color_class = $rev_color[$current_color];
5387
5388 #contents of a file
5389 my $linenr = 0;
5390 LINE:
5391 while (my $line = <$fd>) {
5392 chomp $line;
5393 $linenr++;
5394
5395 print qq!<tr id="l$linenr" class="$color_class">!.
5396 qq!<td class="sha1"><a href=""> </a></td>!.
5397 qq!<td class="linenr">!.
5398 qq!<a class="linenr" href="">$linenr</a></td>!;
5399 print qq!<td class="pre">! . esc_html($line) . "</td>\n";
5400 print qq!</tr>\n!;
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07005401 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005402
5403 } else { # porcelain, i.e. ordinary blame
5404 my %metainfo = (); # saves information about commits
5405
5406 # blame data
5407 LINE:
5408 while (my $line = <$fd>) {
5409 chomp $line;
5410 # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
5411 # no <lines in group> for subsequent lines in group of lines
5412 my ($full_rev, $orig_lineno, $lineno, $group_size) =
5413 ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
5414 if (!exists $metainfo{$full_rev}) {
5415 $metainfo{$full_rev} = { 'nprevious' => 0 };
Junio C Hamanoeeef88c2006-10-05 13:55:58 -07005416 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005417 my $meta = $metainfo{$full_rev};
5418 my $data;
5419 while ($data = <$fd>) {
5420 chomp $data;
5421 last if ($data =~ s/^\t//); # contents of line
5422 if ($data =~ /^(\S+)(?: (.*))?$/) {
5423 $meta->{$1} = $2 unless exists $meta->{$1};
5424 }
5425 if ($data =~ /^previous /) {
5426 $meta->{'nprevious'}++;
Jakub Narebskia36817b2009-07-25 00:44:05 +02005427 }
5428 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005429 my $short_rev = substr($full_rev, 0, 8);
5430 my $author = $meta->{'author'};
5431 my %date =
5432 parse_date($meta->{'author-time'}, $meta->{'author-tz'});
5433 my $date = $date{'iso-tz'};
5434 if ($group_size) {
5435 $current_color = ($current_color + 1) % $num_colors;
5436 }
5437 my $tr_class = $rev_color[$current_color];
5438 $tr_class .= ' boundary' if (exists $meta->{'boundary'});
5439 $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
5440 $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
5441 print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
5442 if ($group_size) {
5443 print "<td class=\"sha1\"";
5444 print " title=\"". esc_html($author) . ", $date\"";
5445 print " rowspan=\"$group_size\"" if ($group_size > 1);
5446 print ">";
5447 print $cgi->a({-href => href(action=>"commit",
5448 hash=>$full_rev,
5449 file_name=>$file_name)},
5450 esc_html($short_rev));
5451 if ($group_size >= 2) {
5452 my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
5453 if (@author_initials) {
5454 print "<br />" .
5455 esc_html(join('', @author_initials));
5456 # or join('.', ...)
5457 }
5458 }
5459 print "</td>\n";
5460 }
5461 # 'previous' <sha1 of parent commit> <filename at commit>
5462 if (exists $meta->{'previous'} &&
5463 $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
5464 $meta->{'parent'} = $1;
5465 $meta->{'file_parent'} = unquote($2);
5466 }
5467 my $linenr_commit =
5468 exists($meta->{'parent'}) ?
5469 $meta->{'parent'} : $full_rev;
5470 my $linenr_filename =
5471 exists($meta->{'file_parent'}) ?
5472 $meta->{'file_parent'} : unquote($meta->{'filename'});
5473 my $blamed = href(action => 'blame',
5474 file_name => $linenr_filename,
5475 hash_base => $linenr_commit);
5476 print "<td class=\"linenr\">";
5477 print $cgi->a({ -href => "$blamed#l$orig_lineno",
5478 -class => "linenr" },
5479 esc_html($lineno));
5480 print "</td>";
5481 print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
5482 print "</tr>\n";
5483 } # end while
5484
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005485 }
Jakub Narebski4af819d2009-09-01 13:39:17 +02005486
5487 # footer
5488 print "</tbody>\n".
5489 "</table>\n"; # class="blame"
5490 print "</div>\n"; # class="blame_body"
Jakub Narebski952c65f2006-08-22 16:52:50 +02005491 close $fd
5492 or print "Reading blob failed\n";
Jakub Narebskid2ce10d2008-12-09 23:48:51 +01005493
Luben Tuikov1f2857e2006-07-23 13:34:55 -07005494 git_footer_html();
5495}
5496
Jakub Narebski4af819d2009-09-01 13:39:17 +02005497sub git_blame {
5498 git_blame_common();
5499}
5500
5501sub git_blame_incremental {
5502 git_blame_common('incremental');
5503}
5504
5505sub git_blame_data {
5506 git_blame_common('data');
5507}
5508
Kay Sieversede5e102005-08-07 20:23:12 +02005509sub git_tags {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005510 my $head = git_get_head_hash($project);
Kay Sieversede5e102005-08-07 20:23:12 +02005511 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005512 git_print_page_nav('','', $head,undef,$head);
5513 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02005514
Jakub Narebskicd146402006-11-02 20:23:11 +01005515 my @tagslist = git_get_tags_list();
5516 if (@tagslist) {
5517 git_tags_body(\@tagslist);
Kay Sieversede5e102005-08-07 20:23:12 +02005518 }
Kay Sieversede5e102005-08-07 20:23:12 +02005519 git_footer_html();
5520}
5521
Kay Sieversd8f1c5c2005-10-04 01:12:47 +02005522sub git_heads {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005523 my $head = git_get_head_hash($project);
Kay Sievers0db37972005-08-07 20:24:35 +02005524 git_header_html();
Jakub Narebski847e01f2006-08-14 02:05:47 +02005525 git_print_page_nav('','', $head,undef,$head);
5526 git_print_header_div('summary', $project);
Jakub Narebski27fb8c42006-07-30 20:32:01 +02005527
Jakub Narebskicd146402006-11-02 20:23:11 +01005528 my @headslist = git_get_heads_list();
5529 if (@headslist) {
5530 git_heads_body(\@headslist, $head);
Kay Sievers0db37972005-08-07 20:24:35 +02005531 }
Kay Sievers0db37972005-08-07 20:24:35 +02005532 git_footer_html();
5533}
5534
Giuseppe Bilotta00fa6fe2010-11-11 13:26:11 +01005535sub git_remotes {
5536 gitweb_check_feature('remote_heads')
5537 or die_error(403, "Remote heads view is disabled");
5538
5539 my $head = git_get_head_hash($project);
5540 git_header_html();
5541 git_print_page_nav('','', $head,undef,$head);
5542 git_print_header_div('summary', $project);
5543
5544 my @remotelist = git_get_heads_list(undef, 'remotes');
5545 if (@remotelist) {
5546 git_heads_body(\@remotelist, $head);
5547 }
5548 git_footer_html();
5549}
5550
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005551sub git_blob_plain {
Jakub Narebski7f718e82008-06-03 16:47:10 +02005552 my $type = shift;
Jakub Narebskif2e73302006-08-26 19:14:25 +02005553 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02005554
Luben Tuikovcff07712006-07-23 13:28:55 -07005555 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005556 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005557 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005558 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02005559 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005560 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005561 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005562 }
Martin Waitz800764c2006-09-16 23:09:02 +02005563 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5564 # blobs defined by non-textual hash id's can be cached
5565 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005566 }
Martin Waitz800764c2006-09-16 23:09:02 +02005567
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005568 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02005569 or die_error(500, "Open git-cat-file blob '$hash' failed");
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005570
Jakub Narebski7f718e82008-06-03 16:47:10 +02005571 # content-type (can include charset)
5572 $type = blob_contenttype($fd, $file_name, $type);
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005573
Jakub Narebski7f718e82008-06-03 16:47:10 +02005574 # "save as" filename, even when no $file_name is given
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005575 my $save_as = "$hash";
5576 if (defined $file_name) {
5577 $save_as = $file_name;
5578 } elsif ($type =~ m/^text\//) {
5579 $save_as .= '.txt';
5580 }
5581
Matt McCutchen7e1100e2009-02-07 19:00:09 -05005582 # With XSS prevention on, blobs of all types except a few known safe
5583 # ones are served with "Content-Disposition: attachment" to make sure
5584 # they don't run in our security domain. For certain image types,
5585 # blob view writes an <img> tag referring to blob_plain view, and we
5586 # want to be sure not to break that by serving the image as an
5587 # attachment (though Firefox 3 doesn't seem to care).
5588 my $sandbox = $prevent_xss &&
5589 $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
5590
Jakub Narebskif2e73302006-08-26 19:14:25 +02005591 print $cgi->header(
Jakub Narebski7f718e82008-06-03 16:47:10 +02005592 -type => $type,
5593 -expires => $expires,
Matt McCutchen7e1100e2009-02-07 19:00:09 -05005594 -content_disposition =>
5595 ($sandbox ? 'attachment' : 'inline')
5596 . '; filename="' . $save_as . '"');
Jakub Narebski34122b52009-05-11 03:29:40 +02005597 local $/ = undef;
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005598 binmode STDOUT, ':raw';
5599 print <$fd>;
5600 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005601 close $fd;
5602}
5603
Kay Sievers09bd7892005-08-07 20:21:23 +02005604sub git_blob {
Jakub Narebskif2e73302006-08-26 19:14:25 +02005605 my $expires;
Jakub Narebskif2e73302006-08-26 19:14:25 +02005606
Luben Tuikovcff07712006-07-23 13:28:55 -07005607 if (!defined $hash) {
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005608 if (defined $file_name) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02005609 my $base = $hash_base || git_get_head_hash($project);
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005610 $hash = git_get_hash_by_path($base, $file_name, "blob")
Lea Wiemann074afaa2008-06-19 22:03:21 +02005611 or die_error(404, "Cannot find file");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005612 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005613 die_error(400, "No file name defined");
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005614 }
Martin Waitz800764c2006-09-16 23:09:02 +02005615 } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
5616 # blobs defined by non-textual hash id's can be cached
5617 $expires = "+1d";
Jakub Narebski5be01bc2006-07-29 22:43:40 +02005618 }
Martin Waitz800764c2006-09-16 23:09:02 +02005619
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08005620 my $have_blame = gitweb_check_feature('blame');
Dennis Stosberg25691fb2006-08-28 17:49:58 +02005621 open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
Lea Wiemann074afaa2008-06-19 22:03:21 +02005622 or die_error(500, "Couldn't cat $file_name, $hash");
Jakub Narebski847e01f2006-08-14 02:05:47 +02005623 my $mimetype = blob_mimetype($fd, $file_name);
Johannes Schindelinb331fe52010-04-27 21:34:44 +02005624 # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005625 if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
Luben Tuikov930cf7d2006-07-09 20:18:57 -07005626 close $fd;
5627 return git_blob_plain($mimetype);
5628 }
Jakub Narebski5a4cf332006-12-04 23:47:22 +01005629 # we can have blame only for text/* mimetype
5630 $have_blame &&= ($mimetype =~ m!^text/!);
5631
Jakub Narebski592ea412010-04-27 21:34:45 +02005632 my $highlight = gitweb_check_feature('highlight');
5633 my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
5634 $fd = run_highlighter($fd, $highlight, $syntax)
5635 if $syntax;
Johannes Schindelinb331fe52010-04-27 21:34:44 +02005636
Jakub Narebskif2e73302006-08-26 19:14:25 +02005637 git_header_html(undef, $expires);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005638 my $formats_nav = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02005639 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Kay Sievers93129442005-10-17 03:27:54 +02005640 if (defined $file_name) {
Florian Forster5996ca02006-06-12 10:31:57 +02005641 if ($have_blame) {
Jakub Narebski952c65f2006-08-22 16:52:50 +02005642 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01005643 $cgi->a({-href => href(action=>"blame", -replay=>1)},
Jakub Narebski952c65f2006-08-22 16:52:50 +02005644 "blame") .
5645 " | ";
Florian Forster5996ca02006-06-12 10:31:57 +02005646 }
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005647 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01005648 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02005649 "history") .
5650 " | " .
Jakub Narebskia3823e52007-11-01 13:06:29 +01005651 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02005652 "raw") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02005653 " | " .
5654 $cgi->a({-href => href(action=>"blob",
5655 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02005656 "HEAD");
Kay Sievers93129442005-10-17 03:27:54 +02005657 } else {
Jakub Narebski952c65f2006-08-22 16:52:50 +02005658 $formats_nav .=
Jakub Narebskia3823e52007-11-01 13:06:29 +01005659 $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
5660 "raw");
Kay Sievers93129442005-10-17 03:27:54 +02005661 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005662 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
5663 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02005664 } else {
5665 print "<div class=\"page_nav\">\n" .
5666 "<br/><br/></div>\n" .
5667 "<div class=\"title\">$hash</div>\n";
5668 }
Luben Tuikov59fb1c92006-08-17 10:39:29 -07005669 git_print_page_path($file_name, "blob", $hash_base);
Kay Sieversc07ad4b2005-08-07 20:22:44 +02005670 print "<div class=\"page_body\">\n";
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005671 if ($mimetype =~ m!^image/!) {
Jakub Narebski5a4cf332006-12-04 23:47:22 +01005672 print qq!<img type="$mimetype"!;
5673 if ($file_name) {
5674 print qq! alt="$file_name" title="$file_name"!;
5675 }
5676 print qq! src="! .
5677 href(action=>"blob_plain", hash=>$hash,
5678 hash_base=>$hash_base, file_name=>$file_name) .
5679 qq!" />\n!;
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005680 } else {
5681 my $nr;
5682 while (my $line = <$fd>) {
5683 chomp $line;
5684 $nr++;
5685 $line = untabify($line);
Jakub Narebski592ea412010-04-27 21:34:45 +02005686 printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
5687 $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
Jakub Narebskidfa7c7d2007-12-15 15:41:49 +01005688 }
Kay Sievers161332a2005-08-07 19:49:46 +02005689 }
Jakub Narebski952c65f2006-08-22 16:52:50 +02005690 close $fd
5691 or print "Reading blob failed.\n";
Kay Sieversfbb592a2005-08-07 20:12:11 +02005692 print "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02005693 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005694}
5695
5696sub git_tree {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07005697 if (!defined $hash_base) {
5698 $hash_base = "HEAD";
5699 }
Kay Sieversb87d78d2005-08-07 20:21:04 +02005700 if (!defined $hash) {
Kay Sievers09bd7892005-08-07 20:21:23 +02005701 if (defined $file_name) {
Luben Tuikov6f7ea5f2006-09-29 09:57:43 -07005702 $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
5703 } else {
5704 $hash = $hash_base;
Kay Sievers10dba282005-08-07 20:25:27 +02005705 }
Kay Sieverse925f382005-08-07 20:23:35 +02005706 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02005707 die_error(404, "No such tree") unless defined($hash);
Jakub Narebski34122b52009-05-11 03:29:40 +02005708
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005709 my $show_sizes = gitweb_check_feature('show-sizes');
5710 my $have_blame = gitweb_check_feature('blame');
5711
Jakub Narebski34122b52009-05-11 03:29:40 +02005712 my @entries = ();
5713 {
5714 local $/ = "\0";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005715 open my $fd, "-|", git_cmd(), "ls-tree", '-z',
5716 ($show_sizes ? '-l' : ()), @extra_options, $hash
Jakub Narebski34122b52009-05-11 03:29:40 +02005717 or die_error(500, "Open git-ls-tree failed");
5718 @entries = map { chomp; $_ } <$fd>;
5719 close $fd
5720 or die_error(404, "Reading tree failed");
5721 }
Kay Sieversd63577d2005-08-07 20:18:13 +02005722
Jakub Narebski847e01f2006-08-14 02:05:47 +02005723 my $refs = git_get_references();
5724 my $ref = format_ref_marker($refs, $hash_base);
Kay Sievers12a88f22005-08-07 20:02:47 +02005725 git_header_html();
Jakub Narebski300454f2006-10-21 17:53:09 +02005726 my $basedir = '';
Jakub Narebski847e01f2006-08-14 02:05:47 +02005727 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
Petr Baudiscae18622006-09-22 03:19:41 +02005728 my @views_nav = ();
5729 if (defined $file_name) {
5730 push @views_nav,
Jakub Narebskia3823e52007-11-01 13:06:29 +01005731 $cgi->a({-href => href(action=>"history", -replay=>1)},
Petr Baudiscae18622006-09-22 03:19:41 +02005732 "history"),
5733 $cgi->a({-href => href(action=>"tree",
5734 hash_base=>"HEAD", file_name=>$file_name)},
Petr Baudisf35274d2006-09-22 03:19:53 +02005735 "HEAD"),
Petr Baudiscae18622006-09-22 03:19:41 +02005736 }
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005737 my $snapshot_links = format_snapshot_links($hash);
5738 if (defined $snapshot_links) {
Petr Baudiscae18622006-09-22 03:19:41 +02005739 # FIXME: Should be available when we have no hash base as well.
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005740 push @views_nav, $snapshot_links;
Petr Baudiscae18622006-09-22 03:19:41 +02005741 }
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005742 git_print_page_nav('tree','', $hash_base, undef, undef,
5743 join(' | ', @views_nav));
Jakub Narebski847e01f2006-08-14 02:05:47 +02005744 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
Kay Sieversd63577d2005-08-07 20:18:13 +02005745 } else {
Jakub Narebskifa702002006-08-31 00:35:07 +02005746 undef $hash_base;
Kay Sieversd63577d2005-08-07 20:18:13 +02005747 print "<div class=\"page_nav\">\n";
5748 print "<br/><br/></div>\n";
5749 print "<div class=\"title\">$hash</div>\n";
5750 }
Kay Sievers09bd7892005-08-07 20:21:23 +02005751 if (defined $file_name) {
Jakub Narebski300454f2006-10-21 17:53:09 +02005752 $basedir = $file_name;
5753 if ($basedir ne '' && substr($basedir, -1) ne '/') {
5754 $basedir .= '/';
5755 }
Jakub Narebski2d7a3532008-10-02 16:50:04 +02005756 git_print_page_path($file_name, 'tree', $hash_base);
Kay Sievers09bd7892005-08-07 20:21:23 +02005757 }
Kay Sieversfbb592a2005-08-07 20:12:11 +02005758 print "<div class=\"page_body\">\n";
Jakub Narebski591ebf62007-11-19 14:16:11 +01005759 print "<table class=\"tree\">\n";
Luben Tuikov6dd36ac2006-09-28 16:47:50 -07005760 my $alternate = 1;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02005761 # '..' (top directory) link if possible
5762 if (defined $hash_base &&
5763 defined $file_name && $file_name =~ m![^/]+$!) {
5764 if ($alternate) {
5765 print "<tr class=\"dark\">\n";
5766 } else {
5767 print "<tr class=\"light\">\n";
5768 }
5769 $alternate ^= 1;
5770
5771 my $up = $file_name;
5772 $up =~ s!/?[^/]+$!!;
5773 undef $up unless $up;
5774 # based on git_print_tree_entry
5775 print '<td class="mode">' . mode_str('040000') . "</td>\n";
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005776 print '<td class="size">&nbsp;</td>'."\n" if $show_sizes;
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02005777 print '<td class="list">';
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005778 print $cgi->a({-href => href(action=>"tree",
5779 hash_base=>$hash_base,
Jakub Narebskib6b7fc72006-10-21 17:54:44 +02005780 file_name=>$up)},
5781 "..");
5782 print "</td>\n";
5783 print "<td class=\"link\"></td>\n";
5784
5785 print "</tr>\n";
5786 }
Kay Sievers161332a2005-08-07 19:49:46 +02005787 foreach my $line (@entries) {
Jakub Narebskie4b48ea2009-09-07 14:40:00 +02005788 my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes);
Jakub Narebskicb849b42006-08-31 00:32:15 +02005789
Kay Sieversbddec012005-08-07 20:25:42 +02005790 if ($alternate) {
Kay Sieversc994d622005-08-07 20:27:18 +02005791 print "<tr class=\"dark\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02005792 } else {
Kay Sieversc994d622005-08-07 20:27:18 +02005793 print "<tr class=\"light\">\n";
Kay Sieversbddec012005-08-07 20:25:42 +02005794 }
5795 $alternate ^= 1;
Jakub Narebskicb849b42006-08-31 00:32:15 +02005796
Jakub Narebski300454f2006-10-21 17:53:09 +02005797 git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
Jakub Narebskifa702002006-08-31 00:35:07 +02005798
Kay Sievers42f7eb92005-08-07 20:21:46 +02005799 print "</tr>\n";
Kay Sievers161332a2005-08-07 19:49:46 +02005800 }
Kay Sievers42f7eb92005-08-07 20:21:46 +02005801 print "</table>\n" .
5802 "</div>";
Kay Sievers12a88f22005-08-07 20:02:47 +02005803 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005804}
5805
Mark Radab6292752009-11-07 16:13:29 +01005806sub snapshot_name {
5807 my ($project, $hash) = @_;
5808
5809 # path/to/project.git -> project
5810 # path/to/project/.git -> project
5811 my $name = to_utf8($project);
5812 $name =~ s,([^/])/*\.git$,$1,;
5813 $name = basename($name);
5814 # sanitize name
5815 $name =~ s/[[:cntrl:]]/?/g;
5816
5817 my $ver = $hash;
5818 if ($hash =~ /^[0-9a-fA-F]+$/) {
5819 # shorten SHA-1 hash
5820 my $full_hash = git_get_full_hash($project, $hash);
5821 if ($full_hash =~ /^$hash/ && length($hash) > 7) {
5822 $ver = git_get_short_hash($project, $hash);
5823 }
5824 } elsif ($hash =~ m!^refs/tags/(.*)$!) {
5825 # tags don't need shortened SHA-1 hash
5826 $ver = $1;
5827 } else {
5828 # branches and other need shortened SHA-1 hash
5829 if ($hash =~ m!^refs/(?:heads|remotes)/(.*)$!) {
5830 $ver = $1;
5831 }
5832 $ver .= '-' . git_get_short_hash($project, $hash);
5833 }
5834 # in case of hierarchical branch names
5835 $ver =~ s!/!.!g;
5836
5837 # name = project-version_string
5838 $name = "$name-$ver";
5839
5840 return wantarray ? ($name, $name) : $name;
5841}
5842
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305843sub git_snapshot {
Giuseppe Bilotta1b2d2972008-10-10 20:42:26 +02005844 my $format = $input_params{'snapshot_format'};
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01005845 if (!@snapshot_fmts) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005846 die_error(403, "Snapshots not allowed");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005847 }
5848 # default to first supported snapshot format
Giuseppe Bilotta5e166842008-11-02 10:21:37 +01005849 $format ||= $snapshot_fmts[0];
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005850 if ($format !~ m/^[a-z0-9]+$/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005851 die_error(400, "Invalid snapshot format parameter");
Jakub Narebski3473e7d2007-07-25 01:19:58 +02005852 } elsif (!exists($known_snapshot_formats{$format})) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02005853 die_error(400, "Unknown snapshot format");
Mark A Rada1bfd3632009-08-06 10:25:39 -04005854 } elsif ($known_snapshot_formats{$format}{'disabled'}) {
5855 die_error(403, "Snapshot format not allowed");
Mark Rada34b31a82009-08-25 00:59:48 -04005856 } elsif (!grep($_ eq $format, @snapshot_fmts)) {
5857 die_error(403, "Unsupported snapshot format");
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05305858 }
5859
Mark Radafdb0c362009-09-26 13:46:08 -04005860 my $type = git_get_type("$hash^{}");
5861 if (!$type) {
5862 die_error(404, 'Object does not exist');
5863 } elsif ($type eq 'blob') {
5864 die_error(400, 'Object is not a tree-ish');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305865 }
5866
Mark Radab6292752009-11-07 16:13:29 +01005867 my ($name, $prefix) = snapshot_name($project, $hash);
5868 my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
5869 my $cmd = quote_command(
Lea Wiemann516381d2008-06-17 23:46:35 +02005870 git_cmd(), 'archive',
5871 "--format=$known_snapshot_formats{$format}{'format'}",
Mark Radab6292752009-11-07 16:13:29 +01005872 "--prefix=$prefix/", $hash);
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005873 if (exists $known_snapshot_formats{$format}{'compressor'}) {
Lea Wiemann516381d2008-06-17 23:46:35 +02005874 $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}});
Mark Levedahl072570e2007-05-20 11:46:46 -04005875 }
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305876
Mark Radab6292752009-11-07 16:13:29 +01005877 $filename =~ s/(["\\])/\\$1/g;
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02005878 print $cgi->header(
Matt McCutchena3c8ab32007-07-22 01:30:27 +02005879 -type => $known_snapshot_formats{$format}{'type'},
Mark Radab6292752009-11-07 16:13:29 +01005880 -content_disposition => 'inline; filename="' . $filename . '"',
Jakub Narebskiab41dfb2006-09-26 01:59:43 +02005881 -status => '200 OK');
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305882
Mark Levedahl072570e2007-05-20 11:46:46 -04005883 open my $fd, "-|", $cmd
Lea Wiemann074afaa2008-06-19 22:03:21 +02005884 or die_error(500, "Execute git-archive failed");
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305885 binmode STDOUT, ':raw';
5886 print <$fd>;
5887 binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
5888 close $fd;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05305889}
5890
Jakub Narebski15f0b112009-11-13 02:02:13 +01005891sub git_log_generic {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005892 my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_;
Jakub Narebski15f0b112009-11-13 02:02:13 +01005893
Jakub Narebski847e01f2006-08-14 02:05:47 +02005894 my $head = git_get_head_hash($project);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005895 if (!defined $base) {
5896 $base = $head;
Kay Sievers0db37972005-08-07 20:24:35 +02005897 }
Kay Sieversea4a6df2005-08-07 20:26:49 +02005898 if (!defined $page) {
5899 $page = 0;
Kay Sieversb87d78d2005-08-07 20:21:04 +02005900 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02005901 my $refs = git_get_references();
Kay Sieversea4a6df2005-08-07 20:26:49 +02005902
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005903 my $commit_hash = $base;
5904 if (defined $parent) {
5905 $commit_hash = "$parent..$base";
Jakub Narebski15f0b112009-11-13 02:02:13 +01005906 }
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005907 my @commitlist =
5908 parse_commits($commit_hash, 101, (100 * $page),
5909 defined $file_name ? ($file_name, "--full-history") : ());
Kay Sieversea4a6df2005-08-07 20:26:49 +02005910
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005911 my $ftype;
5912 if (!defined $file_hash && defined $file_name) {
5913 # some commits could have deleted file in question,
5914 # and not have it in tree, but one of them has to have it
5915 for (my $i = 0; $i < @commitlist; $i++) {
5916 $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name);
5917 last if defined $file_hash;
5918 }
5919 }
5920 if (defined $file_hash) {
5921 $ftype = git_get_type($file_hash);
5922 }
5923 if (defined $file_name && !defined $ftype) {
5924 die_error(500, "Unknown type of object");
5925 }
5926 my %co;
5927 if (defined $file_name) {
5928 %co = parse_commit($base)
5929 or die_error(404, "Unknown commit object");
5930 }
5931
5932
5933 my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100);
Jakub Narebski15f0b112009-11-13 02:02:13 +01005934 my $next_link = '';
Jakub Narebski42671ca2009-11-13 02:02:12 +01005935 if ($#commitlist >= 100) {
5936 $next_link =
5937 $cgi->a({-href => href(-replay=>1, page=>$page+1),
5938 -accesskey => "n", -title => "Alt-n"}, "next");
5939 }
Jakub Narebski15f0b112009-11-13 02:02:13 +01005940 my $patch_max = gitweb_get_feature('patches');
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005941 if ($patch_max && !defined $file_name) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01005942 if ($patch_max < 0 || @commitlist <= $patch_max) {
5943 $paging_nav .= " &sdot; " .
5944 $cgi->a({-href => href(action=>"patches", -replay=>1)},
5945 "patches");
5946 }
5947 }
5948
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005949 git_header_html();
Jakub Narebski15f0b112009-11-13 02:02:13 +01005950 git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav);
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005951 if (defined $file_name) {
5952 git_print_header_div('commit', esc_html($co{'title'}), $base);
5953 } else {
5954 git_print_header_div('summary', $project)
5955 }
5956 git_print_page_path($file_name, $ftype, $hash_base)
5957 if (defined $file_name);
Jakub Narebski0d83ddc2006-07-30 15:01:07 +02005958
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005959 $body_subr->(\@commitlist, 0, 99, $refs, $next_link,
5960 $file_name, $file_hash, $ftype);
Jakub Narebski42671ca2009-11-13 02:02:12 +01005961
Kay Sievers034df392005-08-07 20:20:07 +02005962 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02005963}
5964
Jakub Narebski15f0b112009-11-13 02:02:13 +01005965sub git_log {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01005966 git_log_generic('log', \&git_log_body,
5967 $hash, $hash_parent);
Jakub Narebski15f0b112009-11-13 02:02:13 +01005968}
5969
Kay Sievers09bd7892005-08-07 20:21:23 +02005970sub git_commit {
Jakub Narebski9954f772006-11-18 23:35:41 +01005971 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02005972 my %co = parse_commit($hash)
5973 or die_error(404, "Unknown commit object");
Kay Sievers161332a2005-08-07 19:49:46 +02005974
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005975 my $parent = $co{'parent'};
5976 my $parents = $co{'parents'}; # listref
5977
5978 # we need to prepare $formats_nav before any parameter munging
5979 my $formats_nav;
5980 if (!defined $parent) {
5981 # --root commitdiff
5982 $formats_nav .= '(initial)';
5983 } elsif (@$parents == 1) {
5984 # single parent commit
5985 $formats_nav .=
5986 '(parent: ' .
5987 $cgi->a({-href => href(action=>"commit",
5988 hash=>$parent)},
5989 esc_html(substr($parent, 0, 7))) .
5990 ')';
5991 } else {
5992 # merge commit
5993 $formats_nav .=
5994 '(merge: ' .
5995 join(' ', map {
Jakub Narebskif9308a12007-03-23 21:04:31 +01005996 $cgi->a({-href => href(action=>"commit",
Jakub Narebskic9d193d2006-12-15 21:57:16 +01005997 hash=>$_)},
5998 esc_html(substr($_, 0, 7)));
5999 } @$parents ) .
6000 ')';
6001 }
Jakub Narebski1655c982009-10-09 14:26:44 +02006002 if (gitweb_check_feature('patches') && @$parents <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01006003 $formats_nav .= " | " .
6004 $cgi->a({-href => href(action=>"patch", -replay=>1)},
6005 "patch");
6006 }
Jakub Narebskic9d193d2006-12-15 21:57:16 +01006007
Kay Sieversd8a20ba2005-08-07 20:28:53 +02006008 if (!defined $parent) {
Jakub Narebskib9182982006-07-30 18:28:34 -07006009 $parent = "--root";
Kay Sievers6191f8e2005-08-07 20:19:56 +02006010 }
Jakub Narebski549ab4a2006-12-15 17:53:45 +01006011 my @difftree;
Jakub Narebski208ecb22007-05-07 01:10:08 +02006012 open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
6013 @diff_opts,
6014 (@$parents <= 1 ? $parent : '-c'),
6015 $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02006016 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski208ecb22007-05-07 01:10:08 +02006017 @difftree = map { chomp; $_ } <$fd>;
Lea Wiemann074afaa2008-06-19 22:03:21 +02006018 close $fd or die_error(404, "Reading git-diff-tree failed");
Kay Sievers11044292005-10-19 03:18:45 +02006019
6020 # non-textual hash id's can be cached
6021 my $expires;
6022 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
6023 $expires = "+1d";
6024 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02006025 my $refs = git_get_references();
6026 my $ref = format_ref_marker($refs, $co{'id'});
Aneesh Kumar K.Vddb8d902006-08-20 11:53:04 +05306027
Jakub Narebski594e2122006-07-31 02:21:52 +02006028 git_header_html(undef, $expires);
Petr Baudisa1441542006-10-06 18:59:33 +02006029 git_print_page_nav('commit', '',
Jakub Narebski952c65f2006-08-22 16:52:50 +02006030 $hash, $co{'tree'}, $hash,
Jakub Narebskic9d193d2006-12-15 21:57:16 +01006031 $formats_nav);
Luben Tuikov4f7b34c2006-07-23 13:36:32 -07006032
Kay Sieversb87d78d2005-08-07 20:21:04 +02006033 if (defined $co{'parent'}) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006034 git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02006035 } else {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006036 git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
Kay Sieversb87d78d2005-08-07 20:21:04 +02006037 }
Kay Sievers6191f8e2005-08-07 20:19:56 +02006038 print "<div class=\"title_text\">\n" .
Jakub Narebski591ebf62007-11-19 14:16:11 +01006039 "<table class=\"object_header\">\n";
Giuseppe Bilotta1c49a4e2009-06-30 00:00:48 +02006040 git_print_authorship_rows(\%co);
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00006041 print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
Kay Sieversbddec012005-08-07 20:25:42 +02006042 print "<tr>" .
6043 "<td>tree</td>" .
Jakub Narebski1f1ab5f2006-06-20 14:58:12 +00006044 "<td class=\"sha1\">" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006045 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
6046 class => "list"}, $co{'tree'}) .
Kay Sievers19806692005-08-07 20:26:27 +02006047 "</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006048 "<td class=\"link\">" .
6049 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
6050 "tree");
Matt McCutchena3c8ab32007-07-22 01:30:27 +02006051 my $snapshot_links = format_snapshot_links($hash);
6052 if (defined $snapshot_links) {
6053 print " | " . $snapshot_links;
Aneesh Kumar K.Vcb9c6e52006-08-17 20:59:46 +05306054 }
6055 print "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02006056 "</tr>\n";
Jakub Narebski549ab4a2006-12-15 17:53:45 +01006057
Kay Sievers3e029292005-08-07 20:05:15 +02006058 foreach my $par (@$parents) {
Kay Sieversbddec012005-08-07 20:25:42 +02006059 print "<tr>" .
6060 "<td>parent</td>" .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006061 "<td class=\"sha1\">" .
6062 $cgi->a({-href => href(action=>"commit", hash=>$par),
6063 class => "list"}, $par) .
6064 "</td>" .
Kay Sieversbddec012005-08-07 20:25:42 +02006065 "<td class=\"link\">" .
Martin Waitz1c2a4f52006-08-16 00:24:30 +02006066 $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006067 " | " .
Jakub Narebskif2e60942006-09-03 23:43:03 +02006068 $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
Kay Sieversbddec012005-08-07 20:25:42 +02006069 "</td>" .
6070 "</tr>\n";
Kay Sievers3e029292005-08-07 20:05:15 +02006071 }
Jakub Narebski7a9b4c52006-06-21 09:48:02 +02006072 print "</table>".
Kay Sieversb87d78d2005-08-07 20:21:04 +02006073 "</div>\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02006074
Kay Sieversfbb592a2005-08-07 20:12:11 +02006075 print "<div class=\"page_body\">\n";
Jakub Narebskid16d0932006-08-17 11:21:23 +02006076 git_print_log($co{'comment'});
Kay Sievers927dcec2005-08-07 20:18:44 +02006077 print "</div>\n";
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02006078
Jakub Narebski208ecb22007-05-07 01:10:08 +02006079 git_difftree_body(\@difftree, $hash, @$parents);
Jakub Narebski4a4a1a52006-08-14 02:18:33 +02006080
Kay Sievers12a88f22005-08-07 20:02:47 +02006081 git_footer_html();
Kay Sievers09bd7892005-08-07 20:21:23 +02006082}
6083
Jakub Narebskica946012006-12-10 13:25:47 +01006084sub git_object {
6085 # object is defined by:
6086 # - hash or hash_base alone
6087 # - hash_base and file_name
6088 my $type;
6089
6090 # - hash or hash_base alone
6091 if ($hash || ($hash_base && !defined $file_name)) {
6092 my $object_id = $hash || $hash_base;
6093
Lea Wiemann516381d2008-06-17 23:46:35 +02006094 open my $fd, "-|", quote_command(
6095 git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null'
Lea Wiemann074afaa2008-06-19 22:03:21 +02006096 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01006097 $type = <$fd>;
6098 chomp $type;
6099 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02006100 or die_error(404, "Object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01006101
6102 # - hash_base and file_name
6103 } elsif ($hash_base && defined $file_name) {
6104 $file_name =~ s,/+$,,;
6105
6106 system(git_cmd(), "cat-file", '-e', $hash_base) == 0
Lea Wiemann074afaa2008-06-19 22:03:21 +02006107 or die_error(404, "Base object does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01006108
6109 # here errors should not hapen
6110 open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02006111 or die_error(500, "Open git-ls-tree failed");
Jakub Narebskica946012006-12-10 13:25:47 +01006112 my $line = <$fd>;
6113 close $fd;
6114
6115 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
6116 unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006117 die_error(404, "File or directory for given base does not exist");
Jakub Narebskica946012006-12-10 13:25:47 +01006118 }
6119 $type = $2;
6120 $hash = $3;
6121 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006122 die_error(400, "Not enough information to find object");
Jakub Narebskica946012006-12-10 13:25:47 +01006123 }
6124
6125 print $cgi->redirect(-uri => href(action=>$type, -full=>1,
6126 hash=>$hash, hash_base=>$hash_base,
6127 file_name=>$file_name),
6128 -status => '302 Found');
6129}
6130
Kay Sievers09bd7892005-08-07 20:21:23 +02006131sub git_blobdiff {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006132 my $format = shift || 'html';
6133
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006134 my $fd;
6135 my @difftree;
6136 my %diffinfo;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006137 my $expires;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006138
6139 # preparing $fd and %diffinfo for git_patchset_body
6140 # new style URI
6141 if (defined $hash_base && defined $hash_parent_base) {
6142 if (defined $file_name) {
6143 # read raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01006144 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
6145 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02006146 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02006147 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006148 @difftree = map { chomp; $_ } <$fd>;
6149 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02006150 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006151 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02006152 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006153
Jakub Narebski0aea3372006-08-27 23:45:26 +02006154 } elsif (defined $hash &&
6155 $hash =~ /[0-9a-fA-F]{40}/) {
6156 # try to find filename from $hash
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006157
6158 # read filtered raw output
Jakub Narebski45bd0c82006-10-30 22:29:06 +01006159 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
6160 $hash_parent_base, $hash_base, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02006161 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006162 @difftree =
6163 # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
6164 # $hash == to_id
6165 grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
6166 map { chomp; $_ } <$fd>;
6167 close $fd
Lea Wiemann074afaa2008-06-19 22:03:21 +02006168 or die_error(404, "Reading git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006169 @difftree
Lea Wiemann074afaa2008-06-19 22:03:21 +02006170 or die_error(404, "Blob diff not found");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006171
6172 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006173 die_error(400, "Missing one of the blob diff parameters");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006174 }
6175
6176 if (@difftree > 1) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006177 die_error(400, "Ambiguous blob diff specification");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006178 }
6179
6180 %diffinfo = parse_difftree_raw_line($difftree[0]);
Jakub Narebski9d301452007-11-01 12:38:08 +01006181 $file_parent ||= $diffinfo{'from_file'} || $file_name;
6182 $file_name ||= $diffinfo{'to_file'};
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006183
6184 $hash_parent ||= $diffinfo{'from_id'};
6185 $hash ||= $diffinfo{'to_id'};
6186
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006187 # non-textual hash id's can be cached
6188 if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
6189 $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
6190 $expires = '+1d';
6191 }
6192
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006193 # open patch output
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006194 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski957d6ea2007-04-05 13:45:41 +02006195 '-p', ($format eq 'html' ? "--full-index" : ()),
6196 $hash_parent_base, $hash_base,
Jakub Narebski5ae917a2007-03-30 23:41:26 +02006197 "--", (defined $file_parent ? $file_parent : ()), $file_name
Lea Wiemann074afaa2008-06-19 22:03:21 +02006198 or die_error(500, "Open git-diff-tree failed");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006199 }
6200
Junio C Hamanob54dc9f2008-12-16 19:42:02 -08006201 # old/legacy style URI -- not generated anymore since 1.4.3.
6202 if (!%diffinfo) {
6203 die_error('404 Not Found', "Missing one of the blob diff parameters")
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006204 }
6205
6206 # header
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006207 if ($format eq 'html') {
6208 my $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01006209 $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
Petr Baudis35329cc2006-09-22 03:19:50 +02006210 "raw");
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006211 git_header_html(undef, $expires);
6212 if (defined $hash_base && (my %co = parse_commit($hash_base))) {
6213 git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
6214 git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
6215 } else {
6216 print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
6217 print "<div class=\"title\">$hash vs $hash_parent</div>\n";
6218 }
6219 if (defined $file_name) {
6220 git_print_page_path($file_name, "blob", $hash_base);
6221 } else {
6222 print "<div class=\"page_path\"></div>\n";
6223 }
6224
6225 } elsif ($format eq 'plain') {
6226 print $cgi->header(
6227 -type => 'text/plain',
6228 -charset => 'utf-8',
6229 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07006230 -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006231
6232 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
6233
Kay Sievers09bd7892005-08-07 20:21:23 +02006234 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006235 die_error(400, "Unknown blobdiff format");
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006236 }
6237
6238 # patch
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006239 if ($format eq 'html') {
6240 print "<div class=\"page_body\">\n";
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006241
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006242 git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
6243 close $fd;
Jakub Narebski7c5e2eb2006-08-25 21:13:34 +02006244
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006245 print "</div>\n"; # class="page_body"
6246 git_footer_html();
6247
6248 } else {
6249 while (my $line = <$fd>) {
Jakub Narebski403d0902006-11-08 11:48:56 +01006250 $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
6251 $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006252
6253 print $line;
6254
6255 last if $line =~ m!^\+\+\+!;
6256 }
6257 local $/ = undef;
6258 print <$fd>;
6259 close $fd;
6260 }
Kay Sievers09bd7892005-08-07 20:21:23 +02006261}
6262
Kay Sievers19806692005-08-07 20:26:27 +02006263sub git_blobdiff_plain {
Jakub Narebski9b71b1f2006-08-25 21:14:49 +02006264 git_blobdiff('plain');
Kay Sievers19806692005-08-07 20:26:27 +02006265}
6266
Kay Sievers09bd7892005-08-07 20:21:23 +02006267sub git_commitdiff {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01006268 my %params = @_;
6269 my $format = $params{-format} || 'html';
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006270
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01006271 my ($patch_max) = gitweb_get_feature('patches');
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006272 if ($format eq 'patch') {
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006273 die_error(403, "Patch view not allowed") unless $patch_max;
6274 }
6275
Jakub Narebski9954f772006-11-18 23:35:41 +01006276 $hash ||= $hash_base || "HEAD";
Lea Wiemann074afaa2008-06-19 22:03:21 +02006277 my %co = parse_commit($hash)
6278 or die_error(404, "Unknown commit object");
Jakub Narebski151602d2006-10-23 00:37:56 +02006279
Jakub Narebskicd030c32007-06-08 13:33:28 +02006280 # choose format for commitdiff for merge
6281 if (! defined $hash_parent && @{$co{'parents'}} > 1) {
6282 $hash_parent = '--cc';
6283 }
6284 # we need to prepare $formats_nav before almost any parameter munging
Jakub Narebski151602d2006-10-23 00:37:56 +02006285 my $formats_nav;
6286 if ($format eq 'html') {
6287 $formats_nav =
Jakub Narebskia3823e52007-11-01 13:06:29 +01006288 $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
Jakub Narebski151602d2006-10-23 00:37:56 +02006289 "raw");
Jakub Narebski1655c982009-10-09 14:26:44 +02006290 if ($patch_max && @{$co{'parents'}} <= 1) {
Giuseppe Bilotta75bf2cb2008-12-18 08:13:19 +01006291 $formats_nav .= " | " .
6292 $cgi->a({-href => href(action=>"patch", -replay=>1)},
6293 "patch");
6294 }
Jakub Narebski151602d2006-10-23 00:37:56 +02006295
Jakub Narebskicd030c32007-06-08 13:33:28 +02006296 if (defined $hash_parent &&
6297 $hash_parent ne '-c' && $hash_parent ne '--cc') {
Jakub Narebski151602d2006-10-23 00:37:56 +02006298 # commitdiff with two commits given
6299 my $hash_parent_short = $hash_parent;
6300 if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
6301 $hash_parent_short = substr($hash_parent, 0, 7);
6302 }
6303 $formats_nav .=
Jakub Narebskiada3e1f2007-06-08 13:26:31 +02006304 ' (from';
6305 for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
6306 if ($co{'parents'}[$i] eq $hash_parent) {
6307 $formats_nav .= ' parent ' . ($i+1);
6308 last;
6309 }
6310 }
6311 $formats_nav .= ': ' .
Jakub Narebski151602d2006-10-23 00:37:56 +02006312 $cgi->a({-href => href(action=>"commitdiff",
6313 hash=>$hash_parent)},
6314 esc_html($hash_parent_short)) .
6315 ')';
6316 } elsif (!$co{'parent'}) {
6317 # --root commitdiff
6318 $formats_nav .= ' (initial)';
6319 } elsif (scalar @{$co{'parents'}} == 1) {
6320 # single parent commit
6321 $formats_nav .=
6322 ' (parent: ' .
6323 $cgi->a({-href => href(action=>"commitdiff",
6324 hash=>$co{'parent'})},
6325 esc_html(substr($co{'parent'}, 0, 7))) .
6326 ')';
6327 } else {
6328 # merge commit
Jakub Narebskicd030c32007-06-08 13:33:28 +02006329 if ($hash_parent eq '--cc') {
6330 $formats_nav .= ' | ' .
6331 $cgi->a({-href => href(action=>"commitdiff",
6332 hash=>$hash, hash_parent=>'-c')},
6333 'combined');
6334 } else { # $hash_parent eq '-c'
6335 $formats_nav .= ' | ' .
6336 $cgi->a({-href => href(action=>"commitdiff",
6337 hash=>$hash, hash_parent=>'--cc')},
6338 'compact');
6339 }
Jakub Narebski151602d2006-10-23 00:37:56 +02006340 $formats_nav .=
6341 ' (merge: ' .
6342 join(' ', map {
6343 $cgi->a({-href => href(action=>"commitdiff",
6344 hash=>$_)},
6345 esc_html(substr($_, 0, 7)));
6346 } @{$co{'parents'}} ) .
6347 ')';
6348 }
6349 }
6350
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006351 my $hash_parent_param = $hash_parent;
Jakub Narebskicd030c32007-06-08 13:33:28 +02006352 if (!defined $hash_parent_param) {
6353 # --cc for multiple parents, --root for parentless
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006354 $hash_parent_param =
Jakub Narebskicd030c32007-06-08 13:33:28 +02006355 @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
Kay Sieversbddec012005-08-07 20:25:42 +02006356 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006357
6358 # read commitdiff
6359 my $fd;
6360 my @difftree;
Jakub Narebskieee08902006-08-24 00:15:14 +02006361 if ($format eq 'html') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006362 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebski45bd0c82006-10-30 22:29:06 +01006363 "--no-commit-id", "--patch-with-raw", "--full-index",
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006364 $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02006365 or die_error(500, "Open git-diff-tree failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02006366
Jakub Narebski04408c32006-11-18 23:35:38 +01006367 while (my $line = <$fd>) {
6368 chomp $line;
Jakub Narebskieee08902006-08-24 00:15:14 +02006369 # empty line ends raw part of diff-tree output
6370 last unless $line;
Jakub Narebski493e01d2007-05-07 01:10:06 +02006371 push @difftree, scalar parse_difftree_raw_line($line);
Jakub Narebskieee08902006-08-24 00:15:14 +02006372 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006373
Jakub Narebskieee08902006-08-24 00:15:14 +02006374 } elsif ($format eq 'plain') {
Dennis Stosberg25691fb2006-08-28 17:49:58 +02006375 open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskifb1dde42007-05-07 01:10:07 +02006376 '-p', $hash_parent_param, $hash, "--"
Lea Wiemann074afaa2008-06-19 22:03:21 +02006377 or die_error(500, "Open git-diff-tree failed");
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006378 } elsif ($format eq 'patch') {
6379 # For commit ranges, we limit the output to the number of
6380 # patches specified in the 'patches' feature.
6381 # For single commits, we limit the output to a single patch,
6382 # diverging from the git-format-patch default.
6383 my @commit_spec = ();
6384 if ($hash_parent) {
6385 if ($patch_max > 0) {
6386 push @commit_spec, "-$patch_max";
6387 }
6388 push @commit_spec, '-n', "$hash_parent..$hash";
6389 } else {
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01006390 if ($params{-single}) {
6391 push @commit_spec, '-1';
6392 } else {
6393 if ($patch_max > 0) {
6394 push @commit_spec, "-$patch_max";
6395 }
6396 push @commit_spec, "-n";
6397 }
6398 push @commit_spec, '--root', $hash;
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006399 }
Pavan Kumar Sunkara04794fd2010-05-10 18:41:35 +02006400 open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
6401 '--encoding=utf8', '--stdout', @commit_spec
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006402 or die_error(500, "Open git-format-patch failed");
Jakub Narebskieee08902006-08-24 00:15:14 +02006403 } else {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006404 die_error(400, "Unknown commitdiff format");
Jakub Narebskieee08902006-08-24 00:15:14 +02006405 }
Kay Sievers4c02e3c2005-08-07 19:52:52 +02006406
Kay Sievers11044292005-10-19 03:18:45 +02006407 # non-textual hash id's can be cached
6408 my $expires;
6409 if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
6410 $expires = "+1d";
6411 }
Kay Sievers09bd7892005-08-07 20:21:23 +02006412
Jakub Narebskieee08902006-08-24 00:15:14 +02006413 # write commit message
6414 if ($format eq 'html') {
6415 my $refs = git_get_references();
6416 my $ref = format_ref_marker($refs, $co{'id'});
Kay Sievers19806692005-08-07 20:26:27 +02006417
Jakub Narebskieee08902006-08-24 00:15:14 +02006418 git_header_html(undef, $expires);
6419 git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
6420 git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
Giuseppe Bilottaf88bafa2009-06-30 00:00:49 +02006421 print "<div class=\"title_text\">\n" .
6422 "<table class=\"object_header\">\n";
6423 git_print_authorship_rows(\%co);
6424 print "</table>".
6425 "</div>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02006426 print "<div class=\"page_body\">\n";
Jakub Narebski82560982006-10-24 13:55:33 +02006427 if (@{$co{'comment'}} > 1) {
6428 print "<div class=\"log\">\n";
6429 git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
6430 print "</div>\n"; # class="log"
6431 }
Kay Sievers1b1cd422005-08-07 20:28:01 +02006432
Jakub Narebskieee08902006-08-24 00:15:14 +02006433 } elsif ($format eq 'plain') {
6434 my $refs = git_get_references("tags");
Jakub Narebskiedf735a2006-08-24 19:45:30 +02006435 my $tagname = git_get_rev_name_tags($hash);
Jakub Narebskieee08902006-08-24 00:15:14 +02006436 my $filename = basename($project) . "-$hash.patch";
6437
6438 print $cgi->header(
6439 -type => 'text/plain',
6440 -charset => 'utf-8',
6441 -expires => $expires,
Luben Tuikova2a3bf72006-09-28 16:51:43 -07006442 -content_disposition => 'inline; filename="' . "$filename" . '"');
Jakub Narebskieee08902006-08-24 00:15:14 +02006443 my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
Yasushi SHOJI77202242008-01-29 21:16:02 +09006444 print "From: " . to_utf8($co{'author'}) . "\n";
6445 print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n";
6446 print "Subject: " . to_utf8($co{'title'}) . "\n";
6447
Jakub Narebskiedf735a2006-08-24 19:45:30 +02006448 print "X-Git-Tag: $tagname\n" if $tagname;
Jakub Narebskieee08902006-08-24 00:15:14 +02006449 print "X-Git-Url: " . $cgi->self_url() . "\n\n";
Jakub Narebskiedf735a2006-08-24 19:45:30 +02006450
Jakub Narebskieee08902006-08-24 00:15:14 +02006451 foreach my $line (@{$co{'comment'}}) {
Yasushi SHOJI77202242008-01-29 21:16:02 +09006452 print to_utf8($line) . "\n";
Kay Sievers19806692005-08-07 20:26:27 +02006453 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006454 print "---\n\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006455 } elsif ($format eq 'patch') {
6456 my $filename = basename($project) . "-$hash.patch";
6457
6458 print $cgi->header(
6459 -type => 'text/plain',
6460 -charset => 'utf-8',
6461 -expires => $expires,
6462 -content_disposition => 'inline; filename="' . "$filename" . '"');
Kay Sievers19806692005-08-07 20:26:27 +02006463 }
Jakub Narebskieee08902006-08-24 00:15:14 +02006464
6465 # write patch
6466 if ($format eq 'html') {
Jakub Narebskicd030c32007-06-08 13:33:28 +02006467 my $use_parents = !defined $hash_parent ||
6468 $hash_parent eq '-c' || $hash_parent eq '--cc';
6469 git_difftree_body(\@difftree, $hash,
6470 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebskib4657e72006-08-28 14:48:14 +02006471 print "<br/>\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02006472
Jakub Narebskicd030c32007-06-08 13:33:28 +02006473 git_patchset_body($fd, \@difftree, $hash,
6474 $use_parents ? @{$co{'parents'}} : $hash_parent);
Jakub Narebski157e43b2006-08-24 19:34:36 +02006475 close $fd;
Jakub Narebskieee08902006-08-24 00:15:14 +02006476 print "</div>\n"; # class="page_body"
6477 git_footer_html();
6478
6479 } elsif ($format eq 'plain') {
6480 local $/ = undef;
6481 print <$fd>;
6482 close $fd
6483 or print "Reading git-diff-tree failed\n";
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006484 } elsif ($format eq 'patch') {
6485 local $/ = undef;
6486 print <$fd>;
6487 close $fd
6488 or print "Reading git-format-patch failed\n";
Jakub Narebskieee08902006-08-24 00:15:14 +02006489 }
6490}
6491
6492sub git_commitdiff_plain {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01006493 git_commitdiff(-format => 'plain');
Kay Sievers19806692005-08-07 20:26:27 +02006494}
6495
Giuseppe Bilotta9872cd62008-12-18 08:13:16 +01006496# format-patch-style patches
6497sub git_patch {
Jakub Narebski1655c982009-10-09 14:26:44 +02006498 git_commitdiff(-format => 'patch', -single => 1);
Giuseppe Bilottaa3411f82008-12-18 08:13:18 +01006499}
6500
6501sub git_patches {
Giuseppe Bilotta20209852008-12-18 08:13:17 +01006502 git_commitdiff(-format => 'patch');
Kay Sievers09bd7892005-08-07 20:21:23 +02006503}
6504
6505sub git_history {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01006506 git_log_generic('history', \&git_history_body,
6507 $hash_base, $hash_parent_base,
6508 $file_name, $hash);
Kay Sievers161332a2005-08-07 19:49:46 +02006509}
Kay Sievers19806692005-08-07 20:26:27 +02006510
6511sub git_search {
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006512 gitweb_check_feature('search') or die_error(403, "Search is disabled");
Kay Sievers19806692005-08-07 20:26:27 +02006513 if (!defined $searchtext) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006514 die_error(400, "Text field is empty");
Kay Sievers19806692005-08-07 20:26:27 +02006515 }
6516 if (!defined $hash) {
Jakub Narebski847e01f2006-08-14 02:05:47 +02006517 $hash = git_get_head_hash($project);
Kay Sievers19806692005-08-07 20:26:27 +02006518 }
Jakub Narebski847e01f2006-08-14 02:05:47 +02006519 my %co = parse_commit($hash);
Kay Sievers19806692005-08-07 20:26:27 +02006520 if (!%co) {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006521 die_error(404, "Unknown commit object");
Kay Sievers19806692005-08-07 20:26:27 +02006522 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006523 if (!defined $page) {
6524 $page = 0;
6525 }
Jakub Narebski04f7a942006-09-11 00:29:27 +02006526
Petr Baudis88ad7292006-10-24 05:15:46 +02006527 $searchtype ||= 'commit';
6528 if ($searchtype eq 'pickaxe') {
Jakub Narebski04f7a942006-09-11 00:29:27 +02006529 # pickaxe may take all resources of your box and run for several minutes
6530 # with every query - so decide by yourself how public you make this feature
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006531 gitweb_check_feature('pickaxe')
Lea Wiemann074afaa2008-06-19 22:03:21 +02006532 or die_error(403, "Pickaxe is disabled");
Kay Sieversc994d622005-08-07 20:27:18 +02006533 }
Petr Baudise7738552007-05-17 04:31:12 +02006534 if ($searchtype eq 'grep') {
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006535 gitweb_check_feature('grep')
Lea Wiemann074afaa2008-06-19 22:03:21 +02006536 or die_error(403, "Grep is disabled");
Petr Baudise7738552007-05-17 04:31:12 +02006537 }
Petr Baudis88ad7292006-10-24 05:15:46 +02006538
Kay Sievers19806692005-08-07 20:26:27 +02006539 git_header_html();
Kay Sievers19806692005-08-07 20:26:27 +02006540
Petr Baudis88ad7292006-10-24 05:15:46 +02006541 if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
Robert Fitzsimons8e574fb2006-12-23 03:35:14 +00006542 my $greptype;
6543 if ($searchtype eq 'commit') {
6544 $greptype = "--grep=";
6545 } elsif ($searchtype eq 'author') {
6546 $greptype = "--author=";
6547 } elsif ($searchtype eq 'committer') {
6548 $greptype = "--committer=";
6549 }
Jakub Narebski0270cd02008-02-26 13:22:07 +01006550 $greptype .= $searchtext;
6551 my @commitlist = parse_commits($hash, 101, (100 * $page), undef,
Petr Baudis0e559912008-02-26 13:22:08 +01006552 $greptype, '--regexp-ignore-case',
6553 $search_use_regexp ? '--extended-regexp' : '--fixed-strings');
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006554
6555 my $paging_nav = '';
6556 if ($page > 0) {
6557 $paging_nav .=
6558 $cgi->a({-href => href(action=>"search", hash=>$hash,
Jakub Narebski0270cd02008-02-26 13:22:07 +01006559 searchtext=>$searchtext,
6560 searchtype=>$searchtype)},
Jakub Narebskia23f0a72007-04-01 22:21:38 +02006561 "first");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006562 $paging_nav .= " &sdot; " .
Jakub Narebski7afd77b2007-11-01 13:06:28 +01006563 $cgi->a({-href => href(-replay=>1, page=>$page-1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02006564 -accesskey => "p", -title => "Alt-p"}, "prev");
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006565 } else {
6566 $paging_nav .= "first";
6567 $paging_nav .= " &sdot; prev";
6568 }
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006569 my $next_link = '';
Robert Fitzsimons5ad66082006-12-24 14:31:46 +00006570 if ($#commitlist >= 100) {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006571 $next_link =
Jakub Narebski7afd77b2007-11-01 13:06:28 +01006572 $cgi->a({-href => href(-replay=>1, page=>$page+1),
Jakub Narebskia23f0a72007-04-01 22:21:38 +02006573 -accesskey => "n", -title => "Alt-n"}, "next");
Jakub Narebski7afd77b2007-11-01 13:06:28 +01006574 $paging_nav .= " &sdot; $next_link";
6575 } else {
6576 $paging_nav .= " &sdot; next";
6577 }
6578
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006579 git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
6580 git_print_header_div('commit', esc_html($co{'title'}), $hash);
Jonathan Nieder497d9c32010-08-07 16:56:47 -05006581 if ($page == 0 && !@commitlist) {
6582 print "<p>No match.</p>\n";
6583 } else {
6584 git_search_grep_body(\@commitlist, 0, 99, $next_link);
6585 }
Kay Sieversc994d622005-08-07 20:27:18 +02006586 }
6587
Petr Baudis88ad7292006-10-24 05:15:46 +02006588 if ($searchtype eq 'pickaxe') {
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006589 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6590 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6591
Jakub Narebski591ebf62007-11-19 14:16:11 +01006592 print "<table class=\"pickaxe search\">\n";
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006593 my $alternate = 1;
Jakub Narebski34122b52009-05-11 03:29:40 +02006594 local $/ = "\n";
Jakub Narebskic582aba2008-03-05 09:31:55 +01006595 open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts,
6596 '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext",
6597 ($search_use_regexp ? '--pickaxe-regex' : ());
Kay Sieversc994d622005-08-07 20:27:18 +02006598 undef %co;
6599 my @files;
6600 while (my $line = <$fd>) {
Jakub Narebskic582aba2008-03-05 09:31:55 +01006601 chomp $line;
6602 next unless $line;
6603
6604 my %set = parse_difftree_raw_line($line);
6605 if (defined $set{'commit'}) {
6606 # finish previous commit
Kay Sieversc994d622005-08-07 20:27:18 +02006607 if (%co) {
Kay Sieversc994d622005-08-07 20:27:18 +02006608 print "</td>\n" .
6609 "<td class=\"link\">" .
Martin Waitz756d2f02006-08-17 00:28:36 +02006610 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
Jakub Narebski952c65f2006-08-22 16:52:50 +02006611 " | " .
6612 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
Kay Sieversc994d622005-08-07 20:27:18 +02006613 print "</td>\n" .
6614 "</tr>\n";
6615 }
Jakub Narebskic582aba2008-03-05 09:31:55 +01006616
6617 if ($alternate) {
6618 print "<tr class=\"dark\">\n";
6619 } else {
6620 print "<tr class=\"light\">\n";
6621 }
6622 $alternate ^= 1;
6623 %co = parse_commit($set{'commit'});
6624 my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
6625 print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
6626 "<td><i>$author</i></td>\n" .
6627 "<td>" .
6628 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
6629 -class => "list subject"},
6630 chop_and_escape_str($co{'title'}, 50) . "<br/>");
6631 } elsif (defined $set{'to_id'}) {
6632 next if ($set{'to_id'} =~ m/^0{40}$/);
6633
6634 print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
6635 hash=>$set{'to_id'}, file_name=>$set{'to_file'}),
6636 -class => "list"},
6637 "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
6638 "<br/>\n";
Kay Sieversc994d622005-08-07 20:27:18 +02006639 }
6640 }
6641 close $fd;
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006642
Jakub Narebskic582aba2008-03-05 09:31:55 +01006643 # finish last commit (warning: repetition!)
6644 if (%co) {
6645 print "</td>\n" .
6646 "<td class=\"link\">" .
6647 $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
6648 " | " .
6649 $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
6650 print "</td>\n" .
6651 "</tr>\n";
6652 }
6653
Robert Fitzsimons8dbc0fc2006-12-23 14:57:12 +00006654 print "</table>\n";
Kay Sievers19806692005-08-07 20:26:27 +02006655 }
Petr Baudise7738552007-05-17 04:31:12 +02006656
6657 if ($searchtype eq 'grep') {
6658 git_print_page_nav('','', $hash,$co{'tree'},$hash);
6659 git_print_header_div('commit', esc_html($co{'title'}), $hash);
6660
Jakub Narebski591ebf62007-11-19 14:16:11 +01006661 print "<table class=\"grep_search\">\n";
Petr Baudise7738552007-05-17 04:31:12 +02006662 my $alternate = 1;
6663 my $matches = 0;
Jakub Narebski34122b52009-05-11 03:29:40 +02006664 local $/ = "\n";
Petr Baudis0e559912008-02-26 13:22:08 +01006665 open my $fd, "-|", git_cmd(), 'grep', '-n',
6666 $search_use_regexp ? ('-E', '-i') : '-F',
6667 $searchtext, $co{'tree'};
Petr Baudise7738552007-05-17 04:31:12 +02006668 my $lastfile = '';
6669 while (my $line = <$fd>) {
6670 chomp $line;
6671 my ($file, $lno, $ltext, $binary);
6672 last if ($matches++ > 1000);
6673 if ($line =~ /^Binary file (.+) matches$/) {
6674 $file = $1;
6675 $binary = 1;
6676 } else {
6677 (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
6678 }
6679 if ($file ne $lastfile) {
6680 $lastfile and print "</td></tr>\n";
6681 if ($alternate++) {
6682 print "<tr class=\"dark\">\n";
6683 } else {
6684 print "<tr class=\"light\">\n";
6685 }
6686 print "<td class=\"list\">".
6687 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
6688 file_name=>"$file"),
6689 -class => "list"}, esc_path($file));
6690 print "</td><td>\n";
6691 $lastfile = $file;
6692 }
6693 if ($binary) {
6694 print "<div class=\"binary\">Binary file</div>\n";
6695 } else {
6696 $ltext = untabify($ltext);
Petr Baudis0e559912008-02-26 13:22:08 +01006697 if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) {
Petr Baudise7738552007-05-17 04:31:12 +02006698 $ltext = esc_html($1, -nbsp=>1);
6699 $ltext .= '<span class="match">';
6700 $ltext .= esc_html($2, -nbsp=>1);
6701 $ltext .= '</span>';
6702 $ltext .= esc_html($3, -nbsp=>1);
6703 } else {
6704 $ltext = esc_html($ltext, -nbsp=>1);
6705 }
6706 print "<div class=\"pre\">" .
6707 $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
6708 file_name=>"$file").'#l'.$lno,
6709 -class => "linenr"}, sprintf('%4i', $lno))
6710 . ' ' . $ltext . "</div>\n";
6711 }
6712 }
6713 if ($lastfile) {
6714 print "</td></tr>\n";
6715 if ($matches > 1000) {
6716 print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
6717 }
6718 } else {
6719 print "<div class=\"diff nodifferences\">No matches found</div>\n";
6720 }
6721 close $fd;
6722
6723 print "</table>\n";
6724 }
Kay Sievers19806692005-08-07 20:26:27 +02006725 git_footer_html();
6726}
6727
Petr Baudis88ad7292006-10-24 05:15:46 +02006728sub git_search_help {
6729 git_header_html();
6730 git_print_page_nav('','', $hash,$hash,$hash);
6731 print <<EOT;
Petr Baudis0e559912008-02-26 13:22:08 +01006732<p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without
6733regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox,
6734the pattern entered is recognized as the POSIX extended
6735<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case
6736insensitive).</p>
Petr Baudis88ad7292006-10-24 05:15:46 +02006737<dl>
6738<dt><b>commit</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01006739<dd>The commit messages and authorship information will be scanned for the given pattern.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02006740EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006741 my $have_grep = gitweb_check_feature('grep');
Petr Baudise7738552007-05-17 04:31:12 +02006742 if ($have_grep) {
6743 print <<EOT;
6744<dt><b>grep</b></dt>
6745<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
Petr Baudis0e559912008-02-26 13:22:08 +01006746 a different one) are searched for the given pattern. On large trees, this search can take
6747a while and put some strain on the server, so please use it with some consideration. Note that
6748due to git-grep peculiarity, currently if regexp mode is turned off, the matches are
6749case-sensitive.</dd>
Petr Baudise7738552007-05-17 04:31:12 +02006750EOT
6751 }
6752 print <<EOT;
Petr Baudis88ad7292006-10-24 05:15:46 +02006753<dt><b>author</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01006754<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 +02006755<dt><b>committer</b></dt>
Petr Baudis0e559912008-02-26 13:22:08 +01006756<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 +02006757EOT
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006758 my $have_pickaxe = gitweb_check_feature('pickaxe');
Petr Baudis88ad7292006-10-24 05:15:46 +02006759 if ($have_pickaxe) {
6760 print <<EOT;
6761<dt><b>pickaxe</b></dt>
6762<dd>All commits that caused the string to appear or disappear from any file (changes that
6763added, removed or "modified" the string) will be listed. This search can take a while and
Petr Baudis0e559912008-02-26 13:22:08 +01006764takes a lot of strain on the server, so please use it wisely. Note that since you may be
6765interested even in changes just changing the case as well, this search is case sensitive.</dd>
Petr Baudis88ad7292006-10-24 05:15:46 +02006766EOT
6767 }
6768 print "</dl>\n";
6769 git_footer_html();
6770}
6771
Kay Sievers19806692005-08-07 20:26:27 +02006772sub git_shortlog {
Jakub Narebski69ca37d2009-11-13 02:02:14 +01006773 git_log_generic('shortlog', \&git_shortlog_body,
6774 $hash, $hash_parent);
Kay Sievers19806692005-08-07 20:26:27 +02006775}
Jakub Narebski717b8312006-07-31 21:22:15 +02006776
6777## ......................................................................
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006778## feeds (RSS, Atom; OPML)
Jakub Narebski717b8312006-07-31 21:22:15 +02006779
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006780sub git_feed {
6781 my $format = shift || 'atom';
Giuseppe Bilotta25b27902008-11-29 13:07:29 -08006782 my $have_blame = gitweb_check_feature('blame');
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006783
6784 # Atom: http://www.atomenabled.org/developers/syndication/
6785 # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
6786 if ($format ne 'rss' && $format ne 'atom') {
Lea Wiemann074afaa2008-06-19 22:03:21 +02006787 die_error(400, "Unknown web feed format");
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006788 }
6789
6790 # log/feed of current (HEAD) branch, log of given branch, history of file/directory
6791 my $head = $hash || 'HEAD';
Jakub Narebski311e5522008-02-26 13:22:06 +01006792 my @commitlist = parse_commits($head, 150, 0, $file_name);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006793
6794 my %latest_commit;
6795 my %latest_date;
6796 my $content_type = "application/$format+xml";
6797 if (defined $cgi->http('HTTP_ACCEPT') &&
6798 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
6799 # browser (feed reader) prefers text/xml
6800 $content_type = 'text/xml';
6801 }
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006802 if (defined($commitlist[0])) {
6803 %latest_commit = %{$commitlist[0]};
Giuseppe Bilottacd956c72009-01-26 12:50:16 +01006804 my $latest_epoch = $latest_commit{'committer_epoch'};
6805 %latest_date = parse_date($latest_epoch);
6806 my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
6807 if (defined $if_modified) {
6808 my $since;
6809 if (eval { require HTTP::Date; 1; }) {
6810 $since = HTTP::Date::str2time($if_modified);
6811 } elsif (eval { require Time::ParseDate; 1; }) {
6812 $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
6813 }
6814 if (defined $since && $latest_epoch <= $since) {
6815 print $cgi->header(
6816 -type => $content_type,
6817 -charset => 'utf-8',
6818 -last_modified => $latest_date{'rfc2822'},
6819 -status => '304 Not Modified');
6820 return;
6821 }
6822 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006823 print $cgi->header(
6824 -type => $content_type,
6825 -charset => 'utf-8',
6826 -last_modified => $latest_date{'rfc2822'});
6827 } else {
6828 print $cgi->header(
6829 -type => $content_type,
6830 -charset => 'utf-8');
6831 }
6832
6833 # Optimization: skip generating the body if client asks only
6834 # for Last-Modified date.
6835 return if ($cgi->request_method() eq 'HEAD');
6836
6837 # header variables
6838 my $title = "$site_name - $project/$action";
6839 my $feed_type = 'log';
6840 if (defined $hash) {
6841 $title .= " - '$hash'";
6842 $feed_type = 'branch log';
6843 if (defined $file_name) {
6844 $title .= " :: $file_name";
6845 $feed_type = 'history';
6846 }
6847 } elsif (defined $file_name) {
6848 $title .= " - $file_name";
6849 $feed_type = 'history';
6850 }
6851 $title .= " $feed_type";
6852 my $descr = git_get_project_description($project);
6853 if (defined $descr) {
6854 $descr = esc_html($descr);
6855 } else {
6856 $descr = "$project " .
6857 ($format eq 'rss' ? 'RSS' : 'Atom') .
6858 " feed";
6859 }
6860 my $owner = git_get_project_owner($project);
6861 $owner = esc_html($owner);
6862
6863 #header
6864 my $alt_url;
6865 if (defined $file_name) {
6866 $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
6867 } elsif (defined $hash) {
6868 $alt_url = href(-full=>1, action=>"log", hash=>$hash);
6869 } else {
6870 $alt_url = href(-full=>1, action=>"summary");
6871 }
6872 print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
6873 if ($format eq 'rss') {
6874 print <<XML;
Jakub Narebski59b9f612006-08-22 23:42:53 +02006875<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
6876<channel>
Jakub Narebski59b9f612006-08-22 23:42:53 +02006877XML
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006878 print "<title>$title</title>\n" .
6879 "<link>$alt_url</link>\n" .
6880 "<description>$descr</description>\n" .
Giuseppe Bilotta3ac109a2009-01-26 12:50:13 +01006881 "<language>en</language>\n" .
6882 # project owner is responsible for 'editorial' content
6883 "<managingEditor>$owner</managingEditor>\n";
Giuseppe Bilotta1ba68ce2009-01-26 12:50:11 +01006884 if (defined $logo || defined $favicon) {
6885 # prefer the logo to the favicon, since RSS
6886 # doesn't allow both
6887 my $img = esc_url($logo || $favicon);
6888 print "<image>\n" .
6889 "<url>$img</url>\n" .
6890 "<title>$title</title>\n" .
6891 "<link>$alt_url</link>\n" .
6892 "</image>\n";
6893 }
Giuseppe Bilotta0cf31282009-01-26 12:50:14 +01006894 if (%latest_date) {
6895 print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n";
6896 print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n";
6897 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01006898 print "<generator>gitweb v.$version/$git_version</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006899 } elsif ($format eq 'atom') {
6900 print <<XML;
6901<feed xmlns="http://www.w3.org/2005/Atom">
6902XML
6903 print "<title>$title</title>\n" .
6904 "<subtitle>$descr</subtitle>\n" .
6905 '<link rel="alternate" type="text/html" href="' .
6906 $alt_url . '" />' . "\n" .
6907 '<link rel="self" type="' . $content_type . '" href="' .
6908 $cgi->self_url() . '" />' . "\n" .
6909 "<id>" . href(-full=>1) . "</id>\n" .
6910 # use project owner for feed author
6911 "<author><name>$owner</name></author>\n";
6912 if (defined $favicon) {
6913 print "<icon>" . esc_url($favicon) . "</icon>\n";
6914 }
6915 if (defined $logo_url) {
6916 # not twice as wide as tall: 72 x 27 pixels
Jakub Narebskie1147262006-12-04 14:09:43 +01006917 print "<logo>" . esc_url($logo) . "</logo>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006918 }
6919 if (! %latest_date) {
6920 # dummy date to keep the feed valid until commits trickle in:
6921 print "<updated>1970-01-01T00:00:00Z</updated>\n";
6922 } else {
6923 print "<updated>$latest_date{'iso-8601'}</updated>\n";
6924 }
Giuseppe Bilottaad59a7a2009-01-26 12:50:12 +01006925 print "<generator version='$version/$git_version'>gitweb</generator>\n";
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006926 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006927
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006928 # contents
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006929 for (my $i = 0; $i <= $#commitlist; $i++) {
6930 my %co = %{$commitlist[$i]};
6931 my $commit = $co{'id'};
Jakub Narebski717b8312006-07-31 21:22:15 +02006932 # we read 150, we always show 30 and the ones more recent than 48 hours
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01006933 if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
Jakub Narebski717b8312006-07-31 21:22:15 +02006934 last;
6935 }
Jakub Narebski91fd2bf2006-11-25 15:54:34 +01006936 my %cd = parse_date($co{'author_epoch'});
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006937
6938 # get list of changed files
Robert Fitzsimonsb6093a52006-12-24 14:31:47 +00006939 open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
Jakub Narebskic906b182007-05-19 02:47:51 +02006940 $co{'parent'} || "--root",
6941 $co{'id'}, "--", (defined $file_name ? $file_name : ())
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02006942 or next;
Jakub Narebski717b8312006-07-31 21:22:15 +02006943 my @difftree = map { chomp; $_ } <$fd>;
Jakub Narebski6bcf4b42006-08-27 23:49:36 +02006944 close $fd
6945 or next;
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006946
6947 # print element (entry, item)
Florian La Rochee62a6412008-02-03 12:38:46 +01006948 my $co_url = href(-full=>1, action=>"commitdiff", hash=>$commit);
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006949 if ($format eq 'rss') {
6950 print "<item>\n" .
6951 "<title>" . esc_html($co{'title'}) . "</title>\n" .
6952 "<author>" . esc_html($co{'author'}) . "</author>\n" .
6953 "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
6954 "<guid isPermaLink=\"true\">$co_url</guid>\n" .
6955 "<link>$co_url</link>\n" .
6956 "<description>" . esc_html($co{'title'}) . "</description>\n" .
6957 "<content:encoded>" .
6958 "<![CDATA[\n";
6959 } elsif ($format eq 'atom') {
6960 print "<entry>\n" .
6961 "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
6962 "<updated>$cd{'iso-8601'}</updated>\n" .
Jakub Narebskiab23c192006-11-25 15:54:33 +01006963 "<author>\n" .
6964 " <name>" . esc_html($co{'author_name'}) . "</name>\n";
6965 if ($co{'author_email'}) {
6966 print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
6967 }
6968 print "</author>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006969 # use committer for contributor
Jakub Narebskiab23c192006-11-25 15:54:33 +01006970 "<contributor>\n" .
6971 " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
6972 if ($co{'committer_email'}) {
6973 print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
6974 }
6975 print "</contributor>\n" .
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006976 "<published>$cd{'iso-8601'}</published>\n" .
6977 "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
6978 "<id>$co_url</id>\n" .
6979 "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
6980 "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
6981 }
Jakub Narebski717b8312006-07-31 21:22:15 +02006982 my $comment = $co{'comment'};
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006983 print "<pre>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006984 foreach my $line (@$comment) {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006985 $line = esc_html($line);
6986 print "$line\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02006987 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01006988 print "</pre><ul>\n";
6989 foreach my $difftree_line (@difftree) {
6990 my %difftree = parse_difftree_raw_line($difftree_line);
6991 next if !$difftree{'from_id'};
6992
6993 my $file = $difftree{'file'} || $difftree{'to_file'};
6994
6995 print "<li>" .
6996 "[" .
6997 $cgi->a({-href => href(-full=>1, action=>"blobdiff",
6998 hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
6999 hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
7000 file_name=>$file, file_parent=>$difftree{'from_file'}),
7001 -title => "diff"}, 'D');
7002 if ($have_blame) {
7003 print $cgi->a({-href => href(-full=>1, action=>"blame",
7004 file_name=>$file, hash_base=>$commit),
7005 -title => "blame"}, 'B');
Jakub Narebski717b8312006-07-31 21:22:15 +02007006 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007007 # if this is not a feed of a file history
7008 if (!defined $file_name || $file_name ne $file) {
7009 print $cgi->a({-href => href(-full=>1, action=>"history",
7010 file_name=>$file, hash=>$commit),
7011 -title => "history"}, 'H');
7012 }
7013 $file = esc_path($file);
7014 print "] ".
7015 "$file</li>\n";
Jakub Narebski717b8312006-07-31 21:22:15 +02007016 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007017 if ($format eq 'rss') {
7018 print "</ul>]]>\n" .
7019 "</content:encoded>\n" .
7020 "</item>\n";
7021 } elsif ($format eq 'atom') {
7022 print "</ul>\n</div>\n" .
7023 "</content>\n" .
7024 "</entry>\n";
7025 }
Jakub Narebski717b8312006-07-31 21:22:15 +02007026 }
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007027
7028 # end of feed
7029 if ($format eq 'rss') {
7030 print "</channel>\n</rss>\n";
Jakub Narebski3278fbc2009-05-11 19:37:28 +02007031 } elsif ($format eq 'atom') {
Jakub Narebskiaf6feeb2006-11-19 15:05:22 +01007032 print "</feed>\n";
7033 }
7034}
7035
7036sub git_rss {
7037 git_feed('rss');
7038}
7039
7040sub git_atom {
7041 git_feed('atom');
Jakub Narebski717b8312006-07-31 21:22:15 +02007042}
7043
7044sub git_opml {
Jakub Narebski847e01f2006-08-14 02:05:47 +02007045 my @list = git_get_projects_list();
Jakub Narebski717b8312006-07-31 21:22:15 +02007046
Giuseppe Bilottaae357852009-01-02 13:49:30 +01007047 print $cgi->header(
7048 -type => 'text/xml',
7049 -charset => 'utf-8',
7050 -content_disposition => 'inline; filename="opml.xml"');
7051
Jakub Narebski59b9f612006-08-22 23:42:53 +02007052 print <<XML;
7053<?xml version="1.0" encoding="utf-8"?>
7054<opml version="1.0">
7055<head>
Petr Baudis8be28902006-10-24 05:18:39 +02007056 <title>$site_name OPML Export</title>
Jakub Narebski59b9f612006-08-22 23:42:53 +02007057</head>
7058<body>
7059<outline text="git RSS feeds">
7060XML
Jakub Narebski717b8312006-07-31 21:22:15 +02007061
7062 foreach my $pr (@list) {
7063 my %proj = %$pr;
Jakub Narebski847e01f2006-08-14 02:05:47 +02007064 my $head = git_get_head_hash($proj{'path'});
Jakub Narebski717b8312006-07-31 21:22:15 +02007065 if (!defined $head) {
7066 next;
7067 }
Dennis Stosberg25691fb2006-08-28 17:49:58 +02007068 $git_dir = "$projectroot/$proj{'path'}";
Jakub Narebski847e01f2006-08-14 02:05:47 +02007069 my %co = parse_commit($head);
Jakub Narebski717b8312006-07-31 21:22:15 +02007070 if (!%co) {
7071 next;
7072 }
7073
7074 my $path = esc_html(chop_str($proj{'path'}, 25, 5));
Giuseppe Bilottadf63fbb2009-01-02 13:15:28 +01007075 my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1);
7076 my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1);
Jakub Narebski717b8312006-07-31 21:22:15 +02007077 print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
7078 }
Jakub Narebski59b9f612006-08-22 23:42:53 +02007079 print <<XML;
7080</outline>
7081</body>
7082</opml>
7083XML
Jakub Narebski717b8312006-07-31 21:22:15 +02007084}